pull/56/head
Elias Fleckenstein 2022-05-17 22:12:00 +02:00
commit 21df26984d
No known key found for this signature in database
GPG Key ID: 06927A5199D6C9B2
530 changed files with 65481 additions and 22013 deletions

View File

@ -1,6 +1,7 @@
BasedOnStyle: LLVM BasedOnStyle: LLVM
IndentWidth: 8 IndentWidth: 4
UseTab: Always UseTab: Always
TabWidth: 4
BreakBeforeBraces: Custom BreakBeforeBraces: Custom
Standard: Cpp11 Standard: Cpp11
BraceWrapping: BraceWrapping:
@ -16,7 +17,7 @@ BraceWrapping:
FixNamespaceComments: false FixNamespaceComments: false
AllowShortIfStatementsOnASingleLine: false AllowShortIfStatementsOnASingleLine: false
IndentCaseLabels: false IndentCaseLabels: false
AccessModifierOffset: -8 AccessModifierOffset: -4
ColumnLimit: 90 ColumnLimit: 90
AllowShortFunctionsOnASingleLine: InlineOnly AllowShortFunctionsOnASingleLine: InlineOnly
SortIncludes: false SortIncludes: false
@ -26,7 +27,7 @@ IncludeCategories:
- Regex: '^<.*' - Regex: '^<.*'
Priority: 1 Priority: 1
AlignAfterOpenBracket: DontAlign AlignAfterOpenBracket: DontAlign
ContinuationIndentWidth: 16 ContinuationIndentWidth: 8
ConstructorInitializerIndentWidth: 16 ConstructorInitializerIndentWidth: 8
BreakConstructorInitializers: AfterColon BreakConstructorInitializers: AfterColon
AlwaysBreakTemplateDeclarations: Yes AlwaysBreakTemplateDeclarations: Yes

View File

@ -21,24 +21,22 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-18.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Install deps - name: Install deps
run: sudo apt-get update; sudo apt-get install -y --no-install-recommends gettext run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends gettext openjdk-11-jdk-headless
- name: Build with Gradle - name: Build with Gradle
run: cd android; ./gradlew assemblerelease run: cd android; ./gradlew assemblerelease
- name: Save armeabi artifact - name: Save armeabi artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: Minetest-armeabi-v7a.apk name: Minetest-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@v2 uses: actions/upload-artifact@v3
with: with:
name: Minetest-arm64-v8a.apk name: Minetest-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

View File

@ -30,53 +30,53 @@ on:
- '.dockerignore' - '.dockerignore'
jobs: jobs:
# This is our minor gcc compiler # Older gcc version (should be close to our minimum supported version)
gcc_6: gcc_5:
runs-on: ubuntu-18.04 runs-on: ubuntu-18.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install deps - name: Install deps
run: | run: |
source ./util/ci/common.sh source ./util/ci/common.sh
install_linux_deps g++-6 install_linux_deps g++-5
- name: Build - name: Build
run: | run: |
./util/ci/build.sh ./util/ci/build.sh
env: env:
CC: gcc-6 CC: gcc-5
CXX: g++-6 CXX: g++-5
- name: Test - name: Test
run: | run: |
./bin/minetest --run-unittests ./bin/minetest --run-unittests
# This is the current gcc compiler (available in bionic) # Current gcc version
gcc_8: gcc_10:
runs-on: ubuntu-18.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install deps - name: Install deps
run: | run: |
source ./util/ci/common.sh source ./util/ci/common.sh
install_linux_deps g++-8 install_linux_deps g++-10
- name: Build - name: Build
run: | run: |
./util/ci/build.sh ./util/ci/build.sh
env: env:
CC: gcc-8 CC: gcc-10
CXX: g++-8 CXX: g++-10
- name: Test - name: Test
run: | run: |
./bin/minetest --run-unittests ./bin/minetest --run-unittests
# This is our minor clang compiler # Older clang version (should be close to our minimum supported version)
clang_3_9: clang_3_9:
runs-on: ubuntu-18.04 runs-on: ubuntu-18.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install deps - name: Install deps
run: | run: |
source ./util/ci/common.sh source ./util/ci/common.sh
@ -93,26 +93,26 @@ jobs:
run: | run: |
./bin/minetest --run-unittests ./bin/minetest --run-unittests
- name: Integration test - name: Integration test + devtest
run: | run: |
./util/test_multiplayer.sh ./util/test_multiplayer.sh
# This is the current clang version # Current clang version
clang_9: clang_10:
runs-on: ubuntu-18.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install deps - name: Install deps
run: | run: |
source ./util/ci/common.sh source ./util/ci/common.sh
install_linux_deps clang-9 valgrind libluajit-5.1-dev install_linux_deps clang-10 valgrind libluajit-5.1-dev
- name: Build - name: Build
run: | run: |
./util/ci/build.sh ./util/ci/build.sh
env: env:
CC: clang-9 CC: clang-10
CXX: clang++-9 CXX: clang++-10
CMAKE_FLAGS: "-DREQUIRE_LUAJIT=1" CMAKE_FLAGS: "-DREQUIRE_LUAJIT=1"
- name: Test - name: Test
@ -126,9 +126,9 @@ jobs:
# Build with prometheus-cpp (server-only) # Build with prometheus-cpp (server-only)
clang_9_prometheus: clang_9_prometheus:
name: "clang_9 (PROMETHEUS=1)" name: "clang_9 (PROMETHEUS=1)"
runs-on: ubuntu-18.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install deps - name: Install deps
run: | run: |
source ./util/ci/common.sh source ./util/ci/common.sh
@ -150,34 +150,11 @@ jobs:
run: | run: |
./bin/minetestserver --run-unittests ./bin/minetestserver --run-unittests
# Build without freetype (client-only)
clang_9_no_freetype:
name: "clang_9 (FREETYPE=0)"
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps clang-9
- name: Build
run: |
./util/ci/build.sh
env:
CC: clang-9
CXX: clang++-9
CMAKE_FLAGS: "-DENABLE_FREETYPE=0 -DBUILD_SERVER=0"
- name: Test
run: |
./bin/minetest --run-unittests
docker: docker:
name: "Docker image" name: "Docker image"
runs-on: ubuntu-18.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Build docker image - name: Build docker image
run: | run: |
docker build . -t minetest:latest docker build . -t minetest:latest
@ -185,13 +162,13 @@ jobs:
win32: win32:
name: "MinGW cross-compiler (32-bit)" name: "MinGW cross-compiler (32-bit)"
runs-on: ubuntu-18.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install compiler - name: Install compiler
run: | run: |
sudo apt-get update -q && sudo apt-get install gettext -qyy sudo apt-get update && sudo apt-get install -y gettext
wget http://minetest.kitsunemimi.pw/mingw-w64-i686_9.2.0_ubuntu18.04.tar.xz -O mingw.tar.xz wget http://minetest.kitsunemimi.pw/mingw-w64-i686_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
sudo tar -xaf mingw.tar.xz -C /usr sudo tar -xaf mingw.tar.xz -C /usr
- name: Build - name: Build
@ -203,13 +180,13 @@ jobs:
win64: win64:
name: "MinGW cross-compiler (64-bit)" name: "MinGW cross-compiler (64-bit)"
runs-on: ubuntu-18.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install compiler - name: Install compiler
run: | run: |
sudo apt-get update -q && sudo apt-get install gettext -qyy sudo apt-get update && sudo apt-get install -y gettext
wget http://minetest.kitsunemimi.pw/mingw-w64-x86_64_9.2.0_ubuntu18.04.tar.xz -O mingw.tar.xz wget http://minetest.kitsunemimi.pw/mingw-w64-x86_64_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
sudo tar -xaf mingw.tar.xz -C /usr sudo tar -xaf mingw.tar.xz -C /usr
- name: Build - name: Build
@ -222,13 +199,10 @@ jobs:
msvc: msvc:
name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }} name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }}
runs-on: windows-2019 runs-on: windows-2019
#### Disabled due to Irrlicht switch
if: false
#### Disabled due to Irrlicht switch
env: env:
VCPKG_VERSION: 0bf3923f9fab4001c00f0f429682a0853b5749e0 VCPKG_VERSION: 5cf60186a241e84e8232641ee973395d4fde90e1
# 2020.11 # 2022.02
vcpkg_packages: irrlicht zlib zstd curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -249,11 +223,17 @@ jobs:
# Enable it, when working on the installer. # Enable it, when working on the installer.
steps: steps:
- name: Checkout - uses: actions/checkout@v3
uses: actions/checkout@v2
- name: Checkout IrrlichtMt
uses: actions/checkout@v3
with:
repository: minetest/irrlicht
path: lib/irrlichtmt/
ref: "1.9.0mt5"
- name: Restore from cache and run vcpkg - name: Restore from cache and run vcpkg
uses: lukka/run-vcpkg@v5 uses: lukka/run-vcpkg@v7
with: with:
vcpkgArguments: ${{env.vcpkg_packages}} vcpkgArguments: ${{env.vcpkg_packages}}
vcpkgDirectory: '${{ github.workspace }}\vcpkg' vcpkgDirectory: '${{ github.workspace }}\vcpkg'
@ -261,7 +241,7 @@ jobs:
vcpkgGitCommitId: ${{ env.VCPKG_VERSION }} vcpkgGitCommitId: ${{ env.VCPKG_VERSION }}
vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }} vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }}
- name: CMake - name: Minetest 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" `
@ -269,7 +249,7 @@ jobs:
-DENABLE_POSTGRESQL=OFF ` -DENABLE_POSTGRESQL=OFF `
-DRUN_IN_PLACE=${{ contains(matrix.type, 'portable') }} . -DRUN_IN_PLACE=${{ contains(matrix.type, 'portable') }} .
- name: Build - name: Build Minetest
run: cmake --build . --config Release run: cmake --build . --config Release
- name: CPack - name: CPack
@ -288,7 +268,7 @@ jobs:
- name: Package Clean - name: Package Clean
run: rm -r $env:GITHUB_WORKSPACE\Package\_CPack_Packages run: rm -r $env:GITHUB_WORKSPACE\Package\_CPack_Packages
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v3
with: with:
name: msvc-${{ matrix.config.arch }}-${{ matrix.type }} name: msvc-${{ matrix.config.arch }}-${{ matrix.type }}
path: .\Package\ path: .\Package\

View File

@ -26,12 +26,13 @@ on:
jobs: jobs:
# clang_format: # clang_format:
# runs-on: ubuntu-18.04 # runs-on: ubuntu-20.04
# steps: # steps:
# - uses: actions/checkout@v2 # - uses: actions/checkout@v3
# - name: Install clang-format # - name: Install clang-format
# run: | # run: |
# sudo apt-get install clang-format-9 -qyy # sudo apt-get update
# sudo apt-get install -y clang-format-9
# #
# - name: Run clang-format # - name: Run clang-format
# run: | # run: |
@ -41,14 +42,13 @@ jobs:
# CLANG_FORMAT: clang-format-9 # CLANG_FORMAT: clang-format-9
clang_tidy: clang_tidy:
runs-on: ubuntu-18.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install deps - name: Install deps
run: | run: |
sudo apt-get install clang-tidy-9 -qyy
source ./util/ci/common.sh source ./util/ci/common.sh
install_linux_deps install_linux_deps clang-tidy-9
- name: Run clang-tidy - name: Run clang-tidy
run: | run: |

61
.github/workflows/lua.yml vendored Normal file
View File

@ -0,0 +1,61 @@
name: lua_lint
# Lint on lua changes on builtin or if workflow changed
on:
push:
paths:
- 'builtin/**.lua'
- 'games/devtest/**.lua'
- '.github/workflows/**.yml'
pull_request:
paths:
- 'builtin/**.lua'
- 'games/devtest/**.lua'
- '.github/workflows/**.yml'
jobs:
# Note that the integration tests are also run build.yml, but only when C++ code is changed.
integration_tests:
name: "Compile and run multiplayer tests"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps clang-10 gdb libluajit-5.1-dev
- name: Build
run: |
./util/ci/build.sh
env:
CC: clang-10
CXX: clang++-10
CMAKE_FLAGS: "-DENABLE_GETTEXT=0 -DBUILD_SERVER=0"
- name: Integration test + devtest
run: |
./util/test_multiplayer.sh
luacheck:
name: "Builtin Luacheck and Unit Tests"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Install luarocks
run: |
sudo apt-get update && sudo apt-get install -y luarocks
- name: Install luarocks tools
run: |
luarocks install --local luacheck
luarocks install --local busted
- name: Run checks (builtin)
run: |
$HOME/.luarocks/bin/luacheck builtin
$HOME/.luarocks/bin/busted builtin
- name: Run checks (devtest)
run: |
$HOME/.luarocks/bin/luacheck --config=games/devtest/.luacheckrc games/devtest

View File

@ -1,32 +0,0 @@
name: lua_lint
# Lint on lua changes on builtin or if workflow changed
on:
push:
paths:
- 'builtin/**.lua'
- '.github/workflows/**.yml'
pull_request:
paths:
- 'builtin/**.lua'
- '.github/workflows/**.yml'
jobs:
luacheck:
name: "Builtin Luacheck and Unit Tests"
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Install luarocks
run: |
sudo apt-get install luarocks -qyy
- name: Install luarocks tools
run: |
luarocks install --local luacheck
luarocks install --local busted
- name: Run checks
run: |
$HOME/.luarocks/bin/luacheck builtin
$HOME/.luarocks/bin/busted builtin

View File

@ -22,7 +22,7 @@ on:
- '.github/workflows/macos.yml' - '.github/workflows/macos.yml'
env: env:
IRRLICHT_TAG: 1.9.0mt3 IRRLICHT_TAG: 1.9.0mt5
MINETEST_GAME_REPO: https://github.com/minetest/minetest_game.git MINETEST_GAME_REPO: https://github.com/minetest/minetest_game.git
MINETEST_GAME_BRANCH: master MINETEST_GAME_BRANCH: master
MINETEST_GAME_NAME: minetest_game MINETEST_GAME_NAME: minetest_game
@ -31,7 +31,7 @@ jobs:
build: build:
runs-on: macos-10.15 runs-on: macos-10.15
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Install deps - name: Install deps
run: | run: |
pkgs=(cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd) pkgs=(cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd)
@ -45,8 +45,8 @@ jobs:
git clone -b $MINETEST_GAME_BRANCH $MINETEST_GAME_REPO games/$MINETEST_GAME_NAME git clone -b $MINETEST_GAME_BRANCH $MINETEST_GAME_REPO games/$MINETEST_GAME_NAME
rm -rvf games/$MINETEST_GAME_NAME/.git rm -rvf games/$MINETEST_GAME_NAME/.git
git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG lib/irrlichtmt git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG lib/irrlichtmt
mkdir cmakebuild mkdir build
cd cmakebuild cd build
cmake .. \ cmake .. \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \
-DCMAKE_FIND_FRAMEWORK=LAST \ -DCMAKE_FIND_FRAMEWORK=LAST \
@ -60,7 +60,7 @@ jobs:
run: | run: |
./build/macos/minetest.app/Contents/MacOS/minetest --run-unittests ./build/macos/minetest.app/Contents/MacOS/minetest --run-unittests
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v3
with: with:
name: minetest-macos name: minetest-macos
path: ./build/macos/ path: ./build/macos/

7
.gitignore vendored
View File

@ -107,6 +107,13 @@ CMakeDoxy*
compile_commands.json compile_commands.json
*.apk *.apk
*.zip *.zip
# Visual Studio
*.vcxproj*
*.sln
.vs/
# Optional user provided library folder # Optional user provided library folder
lib/irrlichtmt lib/irrlichtmt
# Generated mod storage database
client/mod_storage.sqlite

View File

@ -9,7 +9,7 @@ stages:
- deploy - deploy
variables: variables:
IRRLICHT_TAG: "1.9.0mt3" IRRLICHT_TAG: "1.9.0mt5"
MINETEST_GAME_REPO: "https://github.com/minetest/minetest_game.git" MINETEST_GAME_REPO: "https://github.com/minetest/minetest_game.git"
CONTAINER_IMAGE: registry.gitlab.com/$CI_PROJECT_PATH CONTAINER_IMAGE: registry.gitlab.com/$CI_PROJECT_PATH
@ -20,11 +20,9 @@ variables:
- DEBIAN_FRONTEND=noninteractive apt-get -y install build-essential git cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libleveldb-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev - DEBIAN_FRONTEND=noninteractive apt-get -y install build-essential git cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libleveldb-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev
script: script:
- git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG lib/irrlichtmt - git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG lib/irrlichtmt
- mkdir cmakebuild - cmake -B build -DCMAKE_INSTALL_PREFIX=../artifact/minetest/usr/ -DCMAKE_BUILD_TYPE=Release -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE -DBUILD_SERVER=TRUE ..
- cd cmakebuild - cmake --build build --parallel $(($(nproc) + 1))
- cmake -DCMAKE_INSTALL_PREFIX=../artifact/minetest/usr/ -DCMAKE_BUILD_TYPE=Release -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE -DBUILD_SERVER=TRUE .. - cmake --install build
- make -j2
- make install
artifacts: artifacts:
when: on_success when: on_success
expire_in: 1h expire_in: 1h
@ -198,25 +196,12 @@ build:fedora-28:
before_script: before_script:
- apt-get update - apt-get update
- DEBIAN_FRONTEND=noninteractive apt-get install -y wget xz-utils unzip git cmake gettext - DEBIAN_FRONTEND=noninteractive apt-get install -y wget xz-utils unzip git cmake gettext
- wget -nv http://minetest.kitsunemimi.pw/mingw-w64-${WIN_ARCH}_9.2.0_ubuntu18.04.tar.xz -O mingw.tar.xz - wget -nv http://minetest.kitsunemimi.pw/mingw-w64-${WIN_ARCH}_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
- tar -xaf mingw.tar.xz -C /usr - tar -xaf mingw.tar.xz -C /usr
.build_win_template: .build_win_template:
extends: .generic_win_template extends: .generic_win_template
stage: build stage: build
artifacts:
expire_in: 1h
paths:
- build/build/*.zip
.package_win_template:
extends: .generic_win_template
stage: package
script:
- unzip build/build/*.zip
- cp -p /usr/${WIN_ARCH}-w64-mingw32/bin/libgcc*.dll minetest-*-win*/bin/
- cp -p /usr/${WIN_ARCH}-w64-mingw32/bin/libstdc++*.dll minetest-*-win*/bin/
- cp -p /usr/${WIN_ARCH}-w64-mingw32/bin/libwinpthread*.dll minetest-*-win*/bin/
artifacts: artifacts:
expire_in: 90 day expire_in: 90 day
paths: paths:
@ -226,28 +211,15 @@ build:win32:
extends: .build_win_template extends: .build_win_template
script: script:
- EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh build - EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh build
- unzip -q build/build/*.zip
variables: variables:
WIN_ARCH: "i686" WIN_ARCH: "i686"
package:win32:
extends: .package_win_template
needs:
- build:win32
variables:
WIN_ARCH: "i686"
build:win64: build:win64:
extends: .build_win_template extends: .build_win_template
script: script:
- EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh build - EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh build
variables: - unzip -q build/build/*.zip
WIN_ARCH: "x86_64"
package:win64:
extends: .package_win_template
needs:
- build:win64
variables: variables:
WIN_ARCH: "x86_64" WIN_ARCH: "x86_64"

View File

@ -11,13 +11,14 @@ endif()
project(minetest) project(minetest)
set(PROJECT_NAME_CAPITALIZED "Dragonfire") set(PROJECT_NAME_CAPITALIZED "Dragonfire")
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 14)
set(GCC_MINIMUM_VERSION "4.8") set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(CLANG_MINIMUM_VERSION "3.4") set(GCC_MINIMUM_VERSION "5.1")
set(CLANG_MINIMUM_VERSION "3.5")
# Also remember to set PROTOCOL_VERSION in network/networkprotocol.h when releasing # Also remember to set PROTOCOL_VERSION in network/networkprotocol.h when releasing
set(VERSION_MAJOR 5) set(VERSION_MAJOR 5)
set(VERSION_MINOR 5) set(VERSION_MINOR 6)
set(VERSION_PATCH 0) set(VERSION_PATCH 0)
set(VERSION_EXTRA "dragonfire" CACHE STRING "Stuff to append to version string") set(VERSION_EXTRA "dragonfire" CACHE STRING "Stuff to append to version string")
@ -26,7 +27,7 @@ set(DEVELOPMENT_BUILD FALSE)
set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
if(VERSION_EXTRA) if(VERSION_EXTRA)
set(VERSION_STRING ${VERSION_STRING}-${VERSION_EXTRA}) set(VERSION_STRING "${VERSION_STRING}-${VERSION_EXTRA}")
elseif(DEVELOPMENT_BUILD) elseif(DEVELOPMENT_BUILD)
set(VERSION_STRING "${VERSION_STRING}-dev") set(VERSION_STRING "${VERSION_STRING}-dev")
endif() endif()
@ -51,7 +52,7 @@ set(RUN_IN_PLACE ${DEFAULT_RUN_IN_PLACE} CACHE BOOL
set(BUILD_CLIENT TRUE CACHE BOOL "Build client") set(BUILD_CLIENT TRUE CACHE BOOL "Build client")
set(BUILD_SERVER FALSE CACHE BOOL "Build server") set(BUILD_SERVER FALSE CACHE BOOL "Build server")
set(BUILD_UNITTESTS TRUE CACHE BOOL "Build unittests") set(BUILD_UNITTESTS TRUE CACHE BOOL "Build unittests")
set(BUILD_BENCHMARKS FALSE CACHE BOOL "Build benchmarks")
set(WARN_ALL TRUE CACHE BOOL "Enable -Wall for Release build") set(WARN_ALL TRUE CACHE BOOL "Enable -Wall for Release build")
@ -64,8 +65,21 @@ endif()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
set(IRRLICHTMT_BUILD_DIR "" CACHE PATH "Path to IrrlichtMt build directory.")
if(NOT "${IRRLICHTMT_BUILD_DIR}" STREQUAL "")
find_package(IrrlichtMt QUIET
PATHS "${IRRLICHTMT_BUILD_DIR}"
NO_DEFAULT_PATH
)
if(NOT TARGET IrrlichtMt::IrrlichtMt)
# find_package() searches certain subdirectories. ${PATH}/cmake is not
# the only one, but it is the one where IrrlichtMt is supposed to export
# IrrlichtMtConfig.cmake
message(FATAL_ERROR "Could not find IrrlichtMtConfig.cmake in ${IRRLICHTMT_BUILD_DIR}/cmake.")
endif()
# This is done here so that relative search paths are more reasonable # This is done here so that relative search paths are more reasonable
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt") elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt")
message(STATUS "Using user-provided IrrlichtMt at subdirectory 'lib/irrlichtmt'") message(STATUS "Using user-provided IrrlichtMt at subdirectory 'lib/irrlichtmt'")
if(BUILD_CLIENT) if(BUILD_CLIENT)
# tell IrrlichtMt to create a static library # tell IrrlichtMt to create a static library
@ -101,11 +115,13 @@ else()
# Note that we can't use target_include_directories() since that doesn't work for IMPORTED targets before CMake 3.11 # Note that we can't use target_include_directories() since that doesn't work for IMPORTED targets before CMake 3.11
set_target_properties(IrrlichtMt::IrrlichtMt PROPERTIES set_target_properties(IrrlichtMt::IrrlichtMt PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${IRRLICHT_INCLUDE_DIR}") INTERFACE_INCLUDE_DIRECTORIES "${IRRLICHT_INCLUDE_DIR}")
else()
message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}")
endif() endif()
endif() endif()
if(TARGET IrrlichtMt::IrrlichtMt)
message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}")
endif()
# Installation # Installation
@ -135,15 +151,16 @@ elseif(UNIX) # Linux, BSD etc
set(ICONDIR "unix/icons") set(ICONDIR "unix/icons")
set(LOCALEDIR "locale") set(LOCALEDIR "locale")
else() else()
set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}") include(GNUInstallDirs)
set(BINDIR "${CMAKE_INSTALL_PREFIX}/bin") set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}")
set(DOCDIR "${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME}") set(BINDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}")
set(MANDIR "${CMAKE_INSTALL_PREFIX}/share/man") set(DOCDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DOCDIR}")
set(MANDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}")
set(EXAMPLE_CONF_DIR ${DOCDIR}) set(EXAMPLE_CONF_DIR ${DOCDIR})
set(XDG_APPS_DIR "${CMAKE_INSTALL_PREFIX}/share/applications") set(XDG_APPS_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/applications")
set(APPDATADIR "${CMAKE_INSTALL_PREFIX}/share/metainfo") set(APPDATADIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/metainfo")
set(ICONDIR "${CMAKE_INSTALL_PREFIX}/share/icons") set(ICONDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/icons")
set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/share/locale") set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LOCALEDIR}")
endif() endif()
endif() endif()
@ -246,10 +263,10 @@ endif()
find_package(GMP REQUIRED) find_package(GMP REQUIRED)
find_package(Json REQUIRED) find_package(Json REQUIRED)
find_package(Lua REQUIRED) find_package(Lua REQUIRED)
if(NOT USE_LUAJIT)
# JsonCpp doesn't compile well on GCC 4.8 set(LUA_BIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/bitop)
if(NOT USE_SYSTEM_JSONCPP) set(LUA_BIT_LIBRARY bitop)
set(GCC_MINIMUM_VERSION "4.9") add_subdirectory(lib/bitop)
endif() endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
@ -264,9 +281,12 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?Clang")
endif() endif()
endif() endif()
if(BUILD_BENCHMARKS)
add_subdirectory(lib/catch2)
endif()
# Subdirectories # Subdirectories
# Be sure to add all relevant definitions above this # Be sure to add all relevant definitions above this
add_subdirectory(src) add_subdirectory(src)

View File

@ -27,23 +27,20 @@ RUN apk add --no-cache git build-base cmake sqlite-dev curl-dev zlib-dev zstd-de
WORKDIR /usr/src/ WORKDIR /usr/src/
RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \ RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \
mkdir prometheus-cpp/build && \ cd prometheus-cpp && \
cd prometheus-cpp/build && \ cmake -B build \
cmake .. \
-DCMAKE_INSTALL_PREFIX=/usr/local \ -DCMAKE_INSTALL_PREFIX=/usr/local \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DENABLE_TESTING=0 \ -DENABLE_TESTING=0 \
-GNinja && \ -GNinja && \
ninja && \ cmake --build build && \
ninja install cmake --install build
RUN git clone --depth=1 https://github.com/minetest/irrlicht/ -b ${IRRLICHT_VERSION} && \ RUN git clone --depth=1 https://github.com/minetest/irrlicht/ -b ${IRRLICHT_VERSION} && \
cp -r irrlicht/include /usr/include/irrlichtmt cp -r irrlicht/include /usr/include/irrlichtmt
WORKDIR /usr/src/minetest WORKDIR /usr/src/minetest
RUN mkdir build && \ RUN cmake -B build \
cd build && \
cmake .. \
-DCMAKE_INSTALL_PREFIX=/usr/local \ -DCMAKE_INSTALL_PREFIX=/usr/local \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DBUILD_SERVER=TRUE \ -DBUILD_SERVER=TRUE \
@ -51,8 +48,8 @@ RUN mkdir build && \
-DBUILD_UNITTESTS=FALSE \ -DBUILD_UNITTESTS=FALSE \
-DBUILD_CLIENT=FALSE \ -DBUILD_CLIENT=FALSE \
-GNinja && \ -GNinja && \
ninja && \ cmake --build build && \
ninja install cmake --install build
ARG DOCKER_IMAGE=alpine:3.14 ARG DOCKER_IMAGE=alpine:3.14
FROM $DOCKER_IMAGE AS runtime FROM $DOCKER_IMAGE AS runtime

View File

@ -7,7 +7,7 @@ Minetest
Minetest is a free open-source voxel game engine with easy modding and game creation. Minetest is a free open-source voxel game engine with easy modding and game creation.
Copyright (C) 2010-2020 Perttu Ahola <celeron55@gmail.com> Copyright (C) 2010-2022 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)
In case you downloaded the source code In case you downloaded the source code
@ -132,10 +132,11 @@ Compiling
| Dependency | Version | Commentary | | Dependency | Version | Commentary |
|------------|---------|------------| |------------|---------|------------|
| GCC | 4.9+ | Can be replaced with Clang 3.4+ | | GCC | 5.1+ | or Clang 3.5+ |
| CMake | 3.5+ | | | CMake | 3.5+ | |
| IrrlichtMt | - | Custom version of Irrlicht, see https://github.com/minetest/irrlicht | | IrrlichtMt | - | Custom version of Irrlicht, see https://github.com/minetest/irrlicht |
| SQLite3 | 3.0+ | | | Freetype | 2.0+ | |
| SQLite3 | 3+ | |
| Zstd | 1.0+ | | | Zstd | 1.0+ | |
| LuaJIT | 2.0+ | Bundled Lua 5.1 is used if not present | | LuaJIT | 2.0+ | Bundled Lua 5.1 is used if not present |
| GMP | 5.0.0+ | Bundled mini-GMP is used if not present | | GMP | 5.0.0+ | Bundled mini-GMP is used if not present |
@ -143,12 +144,12 @@ Compiling
For Debian/Ubuntu users: For Debian/Ubuntu users:
sudo apt install g++ make libc6-dev cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev sudo apt install g++ make libc6-dev cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev libluajit-5.1-dev
For Fedora users: For Fedora users:
sudo dnf install make automake gcc gcc-c++ kernel-devel cmake libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel sudo dnf install make automake gcc gcc-c++ kernel-devel cmake libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel
For Arch users: For Arch users:
sudo pacman -S base-devel libcurl-gnutls cmake libxxf86vm libpng sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses zstd sudo pacman -S base-devel libcurl-gnutls cmake libxxf86vm libpng sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses zstd
@ -224,10 +225,13 @@ Run it:
- Debug build is slower, but gives much more useful output in a debugger. - Debug build is slower, but gives much more useful output in a debugger.
- If you build a bare server you don't need to have the Irrlicht or IrrlichtMt library installed. - If you build a bare server you don't need to have the Irrlicht or IrrlichtMt library installed.
- In that case use `-DIRRLICHT_INCLUDE_DIR=/some/where/irrlicht/include`. - In that case use `-DIRRLICHT_INCLUDE_DIR=/some/where/irrlicht/include`.
- IrrlichtMt can also be installed somewhere that is not a standard install path.
- In that case use `-DCMAKE_PREFIX_PATH=/path/to/install_prefix` - Minetest will use the IrrlichtMt package that is found first, given by the following order:
- The path must be set so that `$(CMAKE_PREFIX_PATH)/lib/cmake/IrrlichtMt` exists 1. Specified `IRRLICHTMT_BUILD_DIR` CMake variable
or that `$(CMAKE_PREFIX_PATH)` is the path of an IrrlichtMt build folder. 2. `${PROJECT_SOURCE_DIR}/lib/irrlichtmt` (if existent)
3. Installation of IrrlichtMt in the system-specific library paths
4. For server builds with disabled `BUILD_CLIENT` variable, the headers from `IRRLICHT_INCLUDE_DIR` will be used.
- NOTE: Changing the IrrlichtMt build directory (includes system installs) requires regenerating the CMake cache (`rm CMakeCache.txt`)
### CMake options ### CMake options
@ -236,6 +240,7 @@ General options and their default values:
BUILD_CLIENT=TRUE - Build Minetest client BUILD_CLIENT=TRUE - Build Minetest client
BUILD_SERVER=FALSE - Build Minetest server BUILD_SERVER=FALSE - Build Minetest server
BUILD_UNITTESTS=TRUE - Build unittest sources BUILD_UNITTESTS=TRUE - Build unittest sources
BUILD_BENCHMARKS=FALSE - Build benchmark sources
CMAKE_BUILD_TYPE=Release - Type of build (Release vs. Debug) CMAKE_BUILD_TYPE=Release - Type of build (Release vs. Debug)
Release - Release build Release - Release build
Debug - Debug build Debug - Debug build
@ -244,9 +249,8 @@ General options and their default values:
MinSizeRel - Release build with -Os passed to compiler to make executable as small as possible MinSizeRel - Release build with -Os passed to compiler to make executable as small as possible
ENABLE_CURL=ON - Build with cURL; Enables use of online mod repo, public serverlist and remote media fetching via http ENABLE_CURL=ON - Build with cURL; Enables use of online mod repo, public serverlist and remote media fetching via http
ENABLE_CURSES=ON - Build with (n)curses; Enables a server side terminal (command line option: --terminal) ENABLE_CURSES=ON - Build with (n)curses; Enables a server side terminal (command line option: --terminal)
ENABLE_FREETYPE=ON - Build with FreeType2; Allows using TTF fonts
ENABLE_GETTEXT=ON - Build with Gettext; Allows using translations ENABLE_GETTEXT=ON - Build with Gettext; Allows using translations
ENABLE_GLES=OFF - Build for OpenGL ES instead of OpenGL (requires support by IrrlichtMt) ENABLE_GLES=OFF - Enable extra support code for OpenGL ES (requires support by IrrlichtMt)
ENABLE_LEVELDB=ON - Build with LevelDB; Enables use of LevelDB map backend ENABLE_LEVELDB=ON - Build with LevelDB; Enables use of LevelDB map backend
ENABLE_POSTGRESQL=ON - Build with libpq; Enables use of PostgreSQL map backend (PostgreSQL 9.5 or greater recommended) ENABLE_POSTGRESQL=ON - Build with libpq; Enables use of PostgreSQL map backend (PostgreSQL 9.5 or greater recommended)
ENABLE_REDIS=ON - Build with libhiredis; Enables use of Redis map backend ENABLE_REDIS=ON - Build with libhiredis; Enables use of Redis map backend
@ -256,10 +260,10 @@ General options and their default values:
ENABLE_PROMETHEUS=OFF - Build with Prometheus metrics exporter (listens on tcp/30000 by default) ENABLE_PROMETHEUS=OFF - Build with Prometheus metrics exporter (listens on tcp/30000 by default)
ENABLE_SYSTEM_GMP=ON - Use GMP from system (much faster than bundled mini-gmp) ENABLE_SYSTEM_GMP=ON - Use GMP from system (much faster than bundled mini-gmp)
ENABLE_SYSTEM_JSONCPP=ON - Use JsonCPP from system ENABLE_SYSTEM_JSONCPP=ON - Use JsonCPP from system
OPENGL_GL_PREFERENCE=LEGACY - Linux client build only; See CMake Policy CMP0072 for reference
RUN_IN_PLACE=FALSE - Create a portable install (worlds, settings etc. in current directory) RUN_IN_PLACE=FALSE - Create a portable install (worlds, settings etc. in current directory)
USE_GPROF=FALSE - Enable profiling using GProf USE_GPROF=FALSE - Enable profiling using GProf
VERSION_EXTRA= - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar) VERSION_EXTRA= - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar)
ENABLE_TOUCH=FALSE - Enable Touchscreen support (requires support by IrrlichtMt)
Library specific options: Library specific options:
@ -268,10 +272,11 @@ Library specific options:
CURL_LIBRARY - Only if building with cURL; path to libcurl.a/libcurl.so/libcurl.lib CURL_LIBRARY - Only if building with cURL; path to libcurl.a/libcurl.so/libcurl.lib
EGL_INCLUDE_DIR - Only if building with GLES; directory that contains egl.h EGL_INCLUDE_DIR - Only if building with GLES; directory that contains egl.h
EGL_LIBRARY - Only if building with GLES; path to libEGL.a/libEGL.so EGL_LIBRARY - Only if building with GLES; path to libEGL.a/libEGL.so
FREETYPE_INCLUDE_DIR_freetype2 - Only if building with FreeType 2; directory that contains an freetype directory with files such as ftimage.h in it EXTRA_DLL - Only on Windows; optional paths to additional DLLs that should be packaged
FREETYPE_INCLUDE_DIR_ft2build - Only if building with FreeType 2; directory that contains ft2build.h FREETYPE_INCLUDE_DIR_freetype2 - Directory that contains files such as ftimage.h
FREETYPE_LIBRARY - Only if building with FreeType 2; path to libfreetype.a/libfreetype.so/freetype.lib FREETYPE_INCLUDE_DIR_ft2build - Directory that contains ft2build.h
FREETYPE_DLL - Only if building with FreeType 2 on Windows; path to libfreetype.dll FREETYPE_LIBRARY - Path to libfreetype.a/libfreetype.so/freetype.lib
FREETYPE_DLL - Only on Windows; path to libfreetype-6.dll
GETTEXT_DLL - Only when building with gettext on Windows; paths to libintl + libiconv DLLs GETTEXT_DLL - Only when building with gettext on Windows; paths to libintl + libiconv DLLs
GETTEXT_INCLUDE_DIR - Only when building with gettext; directory that contains iconv.h GETTEXT_INCLUDE_DIR - Only when building with gettext; directory that contains iconv.h
GETTEXT_LIBRARY - Only when building with gettext on Windows; path to libintl.dll.a GETTEXT_LIBRARY - Only when building with gettext on Windows; path to libintl.dll.a
@ -295,15 +300,12 @@ Library specific options:
OPENAL_DLL - Only if building with sound on Windows; path to OpenAL32.dll OPENAL_DLL - Only if building with sound on Windows; path to OpenAL32.dll
OPENAL_INCLUDE_DIR - Only if building with sound; directory where al.h is located OPENAL_INCLUDE_DIR - Only if building with sound; directory where al.h is located
OPENAL_LIBRARY - Only if building with sound; path to libopenal.a/libopenal.so/OpenAL32.lib OPENAL_LIBRARY - Only if building with sound; path to libopenal.a/libopenal.so/OpenAL32.lib
OPENGLES2_INCLUDE_DIR - Only if building with GLES; directory that contains gl2.h
OPENGLES2_LIBRARY - Only if building with GLES; path to libGLESv2.a/libGLESv2.so
SQLITE3_INCLUDE_DIR - Directory that contains sqlite3.h SQLITE3_INCLUDE_DIR - Directory that contains sqlite3.h
SQLITE3_LIBRARY - Path to libsqlite3.a/libsqlite3.so/sqlite3.lib SQLITE3_LIBRARY - Path to libsqlite3.a/libsqlite3.so/sqlite3.lib
VORBISFILE_LIBRARY - Only if building with sound; path to libvorbisfile.a/libvorbisfile.so/libvorbisfile.dll.a VORBISFILE_LIBRARY - Only if building with sound; path to libvorbisfile.a/libvorbisfile.so/libvorbisfile.dll.a
VORBIS_DLL - Only if building with sound on Windows; paths to vorbis DLLs VORBIS_DLL - Only if building with sound on Windows; paths to vorbis DLLs
VORBIS_INCLUDE_DIR - Only if building with sound; directory that contains a directory vorbis with vorbisenc.h inside VORBIS_INCLUDE_DIR - Only if building with sound; directory that contains a directory vorbis with vorbisenc.h inside
VORBIS_LIBRARY - Only if building with sound; path to libvorbis.a/libvorbis.so/libvorbis.dll.a VORBIS_LIBRARY - Only if building with sound; path to libvorbis.a/libvorbis.so/libvorbis.dll.a
XXF86VM_LIBRARY - Only on Linux; path to libXXf86vm.a/libXXf86vm.so
ZLIB_DLL - Only on Windows; path to zlib1.dll ZLIB_DLL - Only on Windows; path to zlib1.dll
ZLIB_INCLUDE_DIR - Directory that contains zlib.h ZLIB_INCLUDE_DIR - Directory that contains zlib.h
ZLIB_LIBRARY - Path to libz.a/libz.so/zlib.lib ZLIB_LIBRARY - Path to libz.a/libz.so/zlib.lib
@ -326,13 +328,12 @@ It is highly recommended to use vcpkg as package manager.
After you successfully built vcpkg you can easily install the required libraries: After you successfully built vcpkg you can easily install the required libraries:
```powershell ```powershell
vcpkg install zlib zstd curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit gmp jsoncpp --triplet x64-windows vcpkg install zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry --triplet x64-windows
``` ```
- **Don't forget about IrrlichtMt.** The easiest way is to clone it to `lib/irrlichtmt` as described in the Linux section. - **Don't forget about IrrlichtMt.** The easiest way is to clone it to `lib/irrlichtmt` as described in the Linux section.
- `curl` is optional, but required to read the serverlist, `curl[winssl]` is required to use the content store. - `curl` is optional, but required to read the serverlist, `curl[winssl]` is required to use the content store.
- `openal-soft`, `libvorbis` and `libogg` are optional, but required to use sound. - `openal-soft`, `libvorbis` and `libogg` are optional, but required to use sound.
- `freetype` is optional, it allows true-type font rendering.
- `luajit` is optional, it replaces the integrated Lua interpreter with a faster just-in-time interpreter. - `luajit` is optional, it replaces the integrated Lua interpreter with a faster just-in-time interpreter.
- `gmp` and `jsoncpp` are optional, otherwise the bundled versions will be compiled - `gmp` and `jsoncpp` are optional, otherwise the bundled versions will be compiled
@ -381,6 +382,60 @@ Build the binaries as described above, but make sure you unselect `RUN_IN_PLACE`
Open the generated project file with Visual Studio. Right-click **Package** and choose **Generate**. Open the generated project file with Visual Studio. Right-click **Package** and choose **Generate**.
It may take some minutes to generate the installer. It may take some minutes to generate the installer.
### Compiling on MacOS
#### Requirements
- [Homebrew](https://brew.sh/)
- [Git](https://git-scm.com/downloads)
Install dependencies with homebrew:
```
brew install cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd
```
#### Download
Download source (this is the URL to the latest of source repository, which might not work at all times) using Git:
```bash
git clone --depth 1 https://github.com/minetest/minetest.git
cd minetest
```
Download minetest_game (otherwise only the "Development Test" game is available) using Git:
```
git clone --depth 1 https://github.com/minetest/minetest_game.git games/minetest_game
```
Download Minetest's fork of Irrlicht:
```
git clone --depth 1 https://github.com/minetest/irrlicht.git lib/irrlichtmt
```
#### Build
```bash
mkdir build
cd build
cmake .. \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \
-DCMAKE_FIND_FRAMEWORK=LAST \
-DCMAKE_INSTALL_PREFIX=../build/macos/ \
-DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE
make -j$(sysctl -n hw.logicalcpu)
make install
```
#### Run
```
open ./build/macos/minetest.app
```
Docker Docker
------ ------

View File

@ -1,12 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 29 compileSdkVersion 30
buildToolsVersion '30.0.3' buildToolsVersion '30.0.3'
ndkVersion '22.0.7026061' ndkVersion "$ndk_version"
defaultConfig { defaultConfig {
applicationId 'net.minetest.minetest' applicationId 'net.minetest.minetest'
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 30
versionName "${versionMajor}.${versionMinor}.${versionPatch}" versionName "${versionMajor}.${versionMinor}.${versionPatch}"
versionCode project.versionCode versionCode project.versionCode
} }
@ -68,7 +68,7 @@ task prepareAssets() {
from "${projRoot}/client/shaders" into "${assetsFolder}/client/shaders" from "${projRoot}/client/shaders" into "${assetsFolder}/client/shaders"
} }
copy { copy {
from "../native/deps/Android/Irrlicht/shaders" into "${assetsFolder}/client/shaders/Irrlicht" from "../native/deps/armeabi-v7a/Irrlicht/Shaders" into "${assetsFolder}/client/shaders/Irrlicht"
} }
copy { copy {
from "${projRoot}/fonts" include "*.ttf" into "${assetsFolder}/fonts" from "${projRoot}/fonts" include "*.ttf" into "${assetsFolder}/fonts"
@ -112,5 +112,5 @@ android.applicationVariants.all { variant ->
dependencies { dependencies {
implementation project(':native') implementation project(':native')
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.3.1'
} }

View File

@ -30,7 +30,8 @@
android:configChanges="orientation|keyboardHidden|navigation|screenSize" android:configChanges="orientation|keyboardHidden|navigation|screenSize"
android:maxAspectRatio="3.0" android:maxAspectRatio="3.0"
android:screenOrientation="sensorLandscape" android:screenOrientation="sensorLandscape"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
@ -44,7 +45,8 @@
android:launchMode="singleTask" android:launchMode="singleTask"
android:maxAspectRatio="3.0" android:maxAspectRatio="3.0"
android:screenOrientation="sensorLandscape" android:screenOrientation="sensorLandscape"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
</intent-filter> </intent-filter>

View File

@ -1,82 +0,0 @@
/*
Minetest
Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik <MoNTE48@mail.ua>
Copyright (C) 2014-2020 ubulem, Bektur Mambetov <berkut87@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package net.minetest.minetest;
import android.content.Intent;
import android.os.AsyncTask;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
public class CopyZipTask extends AsyncTask<String, Void, String> {
private final WeakReference<AppCompatActivity> activityRef;
CopyZipTask(AppCompatActivity activity) {
activityRef = new WeakReference<>(activity);
}
protected String doInBackground(String... params) {
copyAsset(params[0]);
return params[0];
}
@Override
protected void onPostExecute(String result) {
startUnzipService(result);
}
private void copyAsset(String zipName) {
String filename = zipName.substring(zipName.lastIndexOf("/") + 1);
try (InputStream in = activityRef.get().getAssets().open(filename);
OutputStream out = new FileOutputStream(zipName)) {
copyFile(in, out);
} catch (IOException e) {
AppCompatActivity activity = activityRef.get();
if (activity != null) {
activity.runOnUiThread(() -> Toast.makeText(activityRef.get(), e.getLocalizedMessage(), Toast.LENGTH_LONG).show());
}
cancel(true);
}
}
private void copyFile(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1)
out.write(buffer, 0, read);
}
private void startUnzipService(String file) {
Intent intent = new Intent(activityRef.get(), UnzipService.class);
intent.putExtra(UnzipService.EXTRA_KEY_IN_FILE, file);
AppCompatActivity activity = activityRef.get();
if (activity != null) {
activity.startService(intent);
}
}
}

View File

@ -171,4 +171,12 @@ public class GameActivity extends NativeActivity {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri)); Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
startActivity(browserIntent); startActivity(browserIntent);
} }
public String getUserDataPath() {
return Utils.getUserDataDirectory(this).getAbsolutePath();
}
public String getCachePath() {
return Utils.getCacheDirectory(this).getAbsolutePath();
}
} }

View File

@ -29,12 +29,14 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.view.View; import android.view.View;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
@ -43,11 +45,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import static net.minetest.minetest.UnzipService.ACTION_FAILURE; import static net.minetest.minetest.UnzipService.*;
import static net.minetest.minetest.UnzipService.ACTION_PROGRESS;
import static net.minetest.minetest.UnzipService.ACTION_UPDATE;
import static net.minetest.minetest.UnzipService.FAILURE;
import static net.minetest.minetest.UnzipService.SUCCESS;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
private final static int versionCode = BuildConfig.VERSION_CODE; private final static int versionCode = BuildConfig.VERSION_CODE;
@ -56,26 +54,40 @@ public class MainActivity extends AppCompatActivity {
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
private static final String SETTINGS = "MinetestSettings"; private static final String SETTINGS = "MinetestSettings";
private static final String TAG_VERSION_CODE = "versionCode"; private static final String TAG_VERSION_CODE = "versionCode";
private ProgressBar mProgressBar; private ProgressBar mProgressBar;
private TextView mTextView; private TextView mTextView;
private SharedPreferences sharedPreferences; private SharedPreferences sharedPreferences;
private final BroadcastReceiver myReceiver = new BroadcastReceiver() { private final BroadcastReceiver myReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
int progress = 0; int progress = 0;
if (intent != null) @StringRes int message = 0;
if (intent != null) {
progress = intent.getIntExtra(ACTION_PROGRESS, 0); progress = intent.getIntExtra(ACTION_PROGRESS, 0);
if (progress >= 0) { message = intent.getIntExtra(ACTION_PROGRESS_MESSAGE, 0);
if (mProgressBar != null) { }
mProgressBar.setVisibility(View.VISIBLE);
mProgressBar.setProgress(progress); if (progress == FAILURE) {
}
mTextView.setVisibility(View.VISIBLE);
} else if (progress == FAILURE) {
Toast.makeText(MainActivity.this, intent.getStringExtra(ACTION_FAILURE), Toast.LENGTH_LONG).show(); Toast.makeText(MainActivity.this, intent.getStringExtra(ACTION_FAILURE), Toast.LENGTH_LONG).show();
finish(); finish();
} else if (progress == SUCCESS) } else if (progress == SUCCESS) {
startNative(); startNative();
} else {
if (mProgressBar != null) {
mProgressBar.setVisibility(View.VISIBLE);
if (progress == INDETERMINATE) {
mProgressBar.setIndeterminate(true);
} else {
mProgressBar.setIndeterminate(false);
mProgressBar.setProgress(progress);
}
}
mTextView.setVisibility(View.VISIBLE);
if (message != 0)
mTextView.setText(message);
}
} }
}; };
@ -88,7 +100,9 @@ public class MainActivity extends AppCompatActivity {
mProgressBar = findViewById(R.id.progressBar); mProgressBar = findViewById(R.id.progressBar);
mTextView = findViewById(R.id.textView); mTextView = findViewById(R.id.textView);
sharedPreferences = getSharedPreferences(SETTINGS, Context.MODE_PRIVATE); sharedPreferences = getSharedPreferences(SETTINGS, Context.MODE_PRIVATE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
checkPermission(); checkPermission();
else else
checkAppVersion(); checkAppVersion();
@ -120,6 +134,7 @@ public class MainActivity extends AppCompatActivity {
if (grantResult != PackageManager.PERMISSION_GRANTED) { if (grantResult != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, R.string.not_granted, Toast.LENGTH_LONG).show(); Toast.makeText(this, R.string.not_granted, Toast.LENGTH_LONG).show();
finish(); finish();
return;
} }
} }
checkAppVersion(); checkAppVersion();
@ -127,10 +142,27 @@ public class MainActivity extends AppCompatActivity {
} }
private void checkAppVersion() { private void checkAppVersion() {
if (sharedPreferences.getInt(TAG_VERSION_CODE, 0) == versionCode) if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
Toast.makeText(this, R.string.no_external_storage, Toast.LENGTH_LONG).show();
finish();
return;
}
if (UnzipService.getIsRunning()) {
mProgressBar.setVisibility(View.VISIBLE);
mProgressBar.setIndeterminate(true);
mTextView.setVisibility(View.VISIBLE);
} else if (sharedPreferences.getInt(TAG_VERSION_CODE, 0) == versionCode &&
Utils.isInstallValid(this)) {
startNative(); startNative();
else } else {
new CopyZipTask(this).execute(getCacheDir() + "/Minetest.zip"); mProgressBar.setVisibility(View.VISIBLE);
mProgressBar.setIndeterminate(true);
mTextView.setVisibility(View.VISIBLE);
Intent intent = new Intent(this, UnzipService.class);
startService(intent);
}
} }
private void startNative() { private void startNative() {

View File

@ -24,16 +24,22 @@ import android.app.IntentService;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
@ -42,32 +48,61 @@ import java.util.zip.ZipInputStream;
public class UnzipService extends IntentService { public class UnzipService extends IntentService {
public static final String ACTION_UPDATE = "net.minetest.minetest.UPDATE"; public static final String ACTION_UPDATE = "net.minetest.minetest.UPDATE";
public static final String ACTION_PROGRESS = "net.minetest.minetest.PROGRESS"; public static final String ACTION_PROGRESS = "net.minetest.minetest.PROGRESS";
public static final String ACTION_PROGRESS_MESSAGE = "net.minetest.minetest.PROGRESS_MESSAGE";
public static final String ACTION_FAILURE = "net.minetest.minetest.FAILURE"; public static final String ACTION_FAILURE = "net.minetest.minetest.FAILURE";
public static final String EXTRA_KEY_IN_FILE = "file";
public static final int SUCCESS = -1; public static final int SUCCESS = -1;
public static final int FAILURE = -2; public static final int FAILURE = -2;
public static final int INDETERMINATE = -3;
private final int id = 1; private final int id = 1;
private NotificationManager mNotifyManager; private NotificationManager mNotifyManager;
private boolean isSuccess = true; private boolean isSuccess = true;
private String failureMessage; private String failureMessage;
private static boolean isRunning = false;
public static synchronized boolean getIsRunning() {
return isRunning;
}
private static synchronized void setIsRunning(boolean v) {
isRunning = v;
}
public UnzipService() { public UnzipService() {
super("net.minetest.minetest.UnzipService"); super("net.minetest.minetest.UnzipService");
} }
private void isDir(String dir, String location) {
File f = new File(location, dir);
if (!f.isDirectory())
f.mkdirs();
}
@Override @Override
protected void onHandleIntent(Intent intent) { protected void onHandleIntent(Intent intent) {
createNotification(); Notification.Builder notificationBuilder = createNotification();
unzip(intent); final File zipFile = new File(getCacheDir(), "Minetest.zip");
try {
setIsRunning(true);
File userDataDirectory = Utils.getUserDataDirectory(this);
if (userDataDirectory == null) {
throw new IOException("Unable to find user data directory");
}
try (InputStream in = this.getAssets().open(zipFile.getName())) {
try (OutputStream out = new FileOutputStream(zipFile)) {
int readLen;
byte[] readBuffer = new byte[16384];
while ((readLen = in.read(readBuffer)) != -1) {
out.write(readBuffer, 0, readLen);
}
}
}
migrate(notificationBuilder, userDataDirectory);
unzip(notificationBuilder, zipFile, userDataDirectory);
} catch (IOException e) {
isSuccess = false;
failureMessage = e.getLocalizedMessage();
} finally {
setIsRunning(false);
zipFile.delete();
}
} }
private void createNotification() { private Notification.Builder createNotification() {
String name = "net.minetest.minetest"; String name = "net.minetest.minetest";
String channelId = "Minetest channel"; String channelId = "Minetest channel";
String description = "notifications from Minetest"; String description = "notifications from Minetest";
@ -92,66 +127,133 @@ public class UnzipService extends IntentService {
} else { } else {
builder = new Notification.Builder(this); builder = new Notification.Builder(this);
} }
Intent notificationIntent = new Intent(this, MainActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent intent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
builder.setContentTitle(getString(R.string.notification_title)) builder.setContentTitle(getString(R.string.notification_title))
.setSmallIcon(R.mipmap.ic_launcher) .setSmallIcon(R.mipmap.ic_launcher)
.setContentText(getString(R.string.notification_description)); .setContentText(getString(R.string.notification_description))
.setContentIntent(intent)
.setOngoing(true)
.setProgress(0, 0, true);
mNotifyManager.notify(id, builder.build()); mNotifyManager.notify(id, builder.build());
return builder;
} }
private void unzip(Intent intent) { private void unzip(Notification.Builder notificationBuilder, File zipFile, File userDataDirectory) throws IOException {
String zip = intent.getStringExtra(EXTRA_KEY_IN_FILE);
isDir("Minetest", Environment.getExternalStorageDirectory().toString());
String location = Environment.getExternalStorageDirectory() + File.separator + "Minetest" + File.separator;
int per = 0; int per = 0;
int size = getSummarySize(zip);
File zipFile = new File(zip); int size;
try (ZipFile zipSize = new ZipFile(zipFile)) {
size = zipSize.size();
}
int readLen; int readLen;
byte[] readBuffer = new byte[8192]; byte[] readBuffer = new byte[16384];
try (FileInputStream fileInputStream = new FileInputStream(zipFile); try (FileInputStream fileInputStream = new FileInputStream(zipFile);
ZipInputStream zipInputStream = new ZipInputStream(fileInputStream)) { ZipInputStream zipInputStream = new ZipInputStream(fileInputStream)) {
ZipEntry ze; ZipEntry ze;
while ((ze = zipInputStream.getNextEntry()) != null) { while ((ze = zipInputStream.getNextEntry()) != null) {
if (ze.isDirectory()) { if (ze.isDirectory()) {
++per; ++per;
isDir(ze.getName(), location); Utils.createDirs(userDataDirectory, ze.getName());
} else { continue;
publishProgress(100 * ++per / size); }
try (OutputStream outputStream = new FileOutputStream(location + ze.getName())) { publishProgress(notificationBuilder, R.string.loading, 100 * ++per / size);
while ((readLen = zipInputStream.read(readBuffer)) != -1) { try (OutputStream outputStream = new FileOutputStream(
outputStream.write(readBuffer, 0, readLen); new File(userDataDirectory, ze.getName()))) {
} while ((readLen = zipInputStream.read(readBuffer)) != -1) {
outputStream.write(readBuffer, 0, readLen);
} }
} }
zipFile.delete();
} }
} catch (IOException e) {
isSuccess = false;
failureMessage = e.getLocalizedMessage();
} }
} }
private void publishProgress(int progress) { void moveFileOrDir(@NonNull File src, @NonNull File dst) throws IOException {
try {
Process p = new ProcessBuilder("/system/bin/mv",
src.getAbsolutePath(), dst.getAbsolutePath()).start();
int exitcode = p.waitFor();
if (exitcode != 0)
throw new IOException("Move failed with exit code " + exitcode);
} catch (InterruptedException e) {
throw new IOException("Move operation interrupted");
}
}
boolean recursivelyDeleteDirectory(@NonNull File loc) {
try {
Process p = new ProcessBuilder("/system/bin/rm", "-rf",
loc.getAbsolutePath()).start();
return p.waitFor() == 0;
} catch (IOException | InterruptedException e) {
return false;
}
}
/**
* Migrates user data from deprecated external storage to app scoped storage
*/
private void migrate(Notification.Builder notificationBuilder, File newLocation) throws IOException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return;
}
File oldLocation = new File(Environment.getExternalStorageDirectory(), "Minetest");
if (!oldLocation.isDirectory())
return;
publishProgress(notificationBuilder, R.string.migrating, 0);
newLocation.mkdir();
String[] dirs = new String[] { "worlds", "games", "mods", "textures", "client" };
for (int i = 0; i < dirs.length; i++) {
publishProgress(notificationBuilder, R.string.migrating, 100 * i / dirs.length);
File dir = new File(oldLocation, dirs[i]), dir2 = new File(newLocation, dirs[i]);
if (dir.isDirectory() && !dir2.isDirectory()) {
moveFileOrDir(dir, dir2);
}
}
for (String filename : new String[] { "minetest.conf" }) {
File file = new File(oldLocation, filename), file2 = new File(newLocation, filename);
if (file.isFile() && !file2.isFile()) {
moveFileOrDir(file, file2);
}
}
recursivelyDeleteDirectory(oldLocation);
}
private void publishProgress(@Nullable Notification.Builder notificationBuilder, @StringRes int message, int progress) {
Intent intentUpdate = new Intent(ACTION_UPDATE); Intent intentUpdate = new Intent(ACTION_UPDATE);
intentUpdate.putExtra(ACTION_PROGRESS, progress); intentUpdate.putExtra(ACTION_PROGRESS, progress);
if (!isSuccess) intentUpdate.putExtra(ACTION_FAILURE, failureMessage); intentUpdate.putExtra(ACTION_PROGRESS_MESSAGE, message);
if (!isSuccess)
intentUpdate.putExtra(ACTION_FAILURE, failureMessage);
sendBroadcast(intentUpdate); sendBroadcast(intentUpdate);
}
private int getSummarySize(String zip) { if (notificationBuilder != null) {
int size = 0; notificationBuilder.setContentText(getString(message));
try { if (progress == INDETERMINATE) {
ZipFile zipSize = new ZipFile(zip); notificationBuilder.setProgress(100, 50, true);
size += zipSize.size(); } else {
} catch (IOException e) { notificationBuilder.setProgress(100, progress, false);
Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); }
mNotifyManager.notify(id, notificationBuilder.build());
} }
return size;
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
mNotifyManager.cancel(id); mNotifyManager.cancel(id);
publishProgress(isSuccess ? SUCCESS : FAILURE); publishProgress(null, R.string.loading, isSuccess ? SUCCESS : FAILURE);
} }
} }

View File

@ -0,0 +1,39 @@
package net.minetest.minetest;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.File;
public class Utils {
public static @NonNull File createDirs(File root, String dir) {
File f = new File(root, dir);
if (!f.isDirectory())
f.mkdirs();
return f;
}
public static @Nullable File getUserDataDirectory(Context context) {
File extDir = context.getExternalFilesDir(null);
if (extDir == null) {
return null;
}
return createDirs(extDir, "Minetest");
}
public static @Nullable File getCacheDirectory(Context context) {
return context.getCacheDir();
}
public static boolean isInstallValid(Context context) {
File userDataDirectory = getUserDataDirectory(context);
return userDataDirectory != null && userDataDirectory.isDirectory() &&
new File(userDataDirectory, "games").isDirectory() &&
new File(userDataDirectory, "builtin").isDirectory() &&
new File(userDataDirectory, "client").isDirectory() &&
new File(userDataDirectory, "textures").isDirectory();
}
}

View File

@ -1,4 +1,5 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main" android:id="@+id/activity_main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -14,7 +15,8 @@
android:layout_marginRight="90dp" android:layout_marginRight="90dp"
android:indeterminate="false" android:indeterminate="false"
android:max="100" android:max="100"
android:visibility="gone" /> android:visibility="gone"
tools:visibility="visible" />
<TextView <TextView
android:id="@+id/textView" android:id="@+id/textView"
@ -25,6 +27,7 @@
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:text="@string/loading" android:text="@string/loading"
android:textColor="#FEFEFE" android:textColor="#FEFEFE"
android:visibility="gone" /> android:visibility="gone"
tools:visibility="visible" />
</RelativeLayout> </RelativeLayout>

View File

@ -3,9 +3,11 @@
<string name="label">Minetest</string> <string name="label">Minetest</string>
<string name="loading">Loading&#8230;</string> <string name="loading">Loading&#8230;</string>
<string name="migrating">Migrating save data from old install&#8230; (this may take a while)</string>
<string name="not_granted">Required permission wasn\'t granted, Minetest can\'t run without it</string> <string name="not_granted">Required permission wasn\'t granted, Minetest can\'t run without it</string>
<string name="notification_title">Loading Minetest</string> <string name="notification_title">Loading Minetest</string>
<string name="notification_description">Less than 1 minute&#8230;</string> <string name="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_external_storage">External storage isn\'t available. If you use an SDCard, please reinsert it. Otherwise, try restarting your phone or contacting the Minetest developers</string>
</resources> </resources>

View File

@ -1,21 +1,22 @@
// 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", 5) // Version Minor project.ext.set("versionMinor", 6) // Version Minor
project.ext.set("versionPatch", 0) // Version Patch project.ext.set("versionPatch", 0) // Version Patch
project.ext.set("versionExtra", "-dev") // Version Extra project.ext.set("versionExtra", "-dev") // Version Extra
project.ext.set("versionCode", 32) // Android Version Code project.ext.set("versionCode", 38) // Android Version Code
// NOTE: +2 after each release! // NOTE: +2 after each release!
// +1 for ARM and +1 for ARM64 APK's, because // +1 for ARM and +1 for ARM64 APK's, because
// each APK must have a larger `versionCode` than the previous // each APK must have a larger `versionCode` than the previous
buildscript { buildscript {
ext.ndk_version = '23.0.7599858'
repositories { repositories {
google() google()
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.1' classpath 'com.android.tools.build:gradle:7.0.3'
classpath 'de.undercouch:gradle-download-task:4.1.1' classpath 'de.undercouch:gradle-download-task:4.1.1'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files

View File

@ -1,6 +1,5 @@
#Fri Jan 08 17:52:00 UTC 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip

2
android/gradlew vendored
View File

@ -98,7 +98,7 @@ location of your Java installation."
fi fi
else else
JAVACMD="java" JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. command -v java >/dev/null || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."

View File

@ -2,12 +2,12 @@ apply plugin: 'com.android.library'
apply plugin: 'de.undercouch.download' apply plugin: 'de.undercouch.download'
android { android {
compileSdkVersion 29 compileSdkVersion 30
buildToolsVersion '30.0.3' buildToolsVersion '30.0.3'
ndkVersion '22.0.7026061' ndkVersion "$ndk_version"
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 30
externalNativeBuild { externalNativeBuild {
ndkBuild { ndkBuild {
arguments '-j' + Runtime.getRuntime().availableProcessors(), arguments '-j' + Runtime.getRuntime().availableProcessors(),
@ -41,58 +41,28 @@ android {
arguments 'NDEBUG=1' arguments 'NDEBUG=1'
} }
} }
ndk {
debugSymbolLevel 'SYMBOL_TABLE'
}
} }
} }
} }
// get precompiled deps // get precompiled deps
def folder = 'minetest_android_deps_binaries'
task downloadDeps(type: Download) { task downloadDeps(type: Download) {
src 'https://github.com/minetest/' + folder + '/archive/master.zip' src 'https://github.com/minetest/minetest_android_deps/releases/download/latest/deps.zip'
dest new File(buildDir, 'deps.zip') dest new File(buildDir, 'deps.zip')
overwrite false overwrite false
} }
task getDeps(dependsOn: downloadDeps, type: Copy) { task getDeps(dependsOn: downloadDeps, type: Copy) {
def deps = file('deps') def deps = new File(buildDir.parent, 'deps')
def f = file("$buildDir/" + folder + "-master") if (!deps.exists()) {
deps.mkdir()
if (!deps.exists() && !f.exists()) {
from zipTree(downloadDeps.dest) from zipTree(downloadDeps.dest)
into buildDir into deps
}
doLast {
if (!deps.exists()) {
file(f).renameTo(file(deps))
}
}
}
// get sqlite
def sqlite_ver = '3340000'
task downloadSqlite(dependsOn: getDeps, type: Download) {
src 'https://www.sqlite.org/2020/sqlite-amalgamation-' + sqlite_ver + '.zip'
dest new File(buildDir, 'sqlite.zip')
overwrite false
}
task getSqlite(dependsOn: downloadSqlite, type: Copy) {
def sqlite = file('deps/Android/sqlite')
def f = file("$buildDir/sqlite-amalgamation-" + sqlite_ver)
if (!sqlite.exists() && !f.exists()) {
from zipTree(downloadSqlite.dest)
into buildDir
}
doLast {
if (!sqlite.exists()) {
file(f).renameTo(file(sqlite))
}
} }
} }
preBuild.dependsOn getDeps preBuild.dependsOn getDeps
preBuild.dependsOn getSqlite

View File

@ -4,62 +4,82 @@ LOCAL_PATH := $(call my-dir)/..
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := Curl LOCAL_MODULE := Curl
LOCAL_SRC_FILES := deps/Android/Curl/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libcurl.a LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libcurl.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libmbedcrypto
LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedcrypto.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libmbedtls
LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedtls.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libmbedx509
LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedx509.a
include $(PREBUILT_STATIC_LIBRARY) include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := Freetype LOCAL_MODULE := Freetype
LOCAL_SRC_FILES := deps/Android/Freetype/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libfreetype.a LOCAL_SRC_FILES := deps/$(APP_ABI)/Freetype/libfreetype.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := Iconv
LOCAL_SRC_FILES := deps/$(APP_ABI)/Iconv/libiconv.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libcharset
LOCAL_SRC_FILES := deps/$(APP_ABI)/Iconv/libcharset.a
include $(PREBUILT_STATIC_LIBRARY) include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := Irrlicht LOCAL_MODULE := Irrlicht
LOCAL_SRC_FILES := deps/Android/Irrlicht/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libIrrlichtMt.a LOCAL_SRC_FILES := deps/$(APP_ABI)/Irrlicht/libIrrlichtMt.a
include $(PREBUILT_STATIC_LIBRARY) include $(PREBUILT_STATIC_LIBRARY)
#include $(CLEAR_VARS)
#LOCAL_MODULE := LevelDB
#LOCAL_SRC_FILES := deps/Android/LevelDB/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libleveldb.a
#include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := LuaJIT LOCAL_MODULE := LuaJIT
LOCAL_SRC_FILES := deps/Android/LuaJIT/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libluajit.a LOCAL_SRC_FILES := deps/$(APP_ABI)/LuaJIT/libluajit.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := mbedTLS
LOCAL_SRC_FILES := deps/Android/mbedTLS/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libmbedtls.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := mbedx509
LOCAL_SRC_FILES := deps/Android/mbedTLS/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libmbedx509.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := mbedcrypto
LOCAL_SRC_FILES := deps/Android/mbedTLS/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libmbedcrypto.a
include $(PREBUILT_STATIC_LIBRARY) include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := OpenAL LOCAL_MODULE := OpenAL
LOCAL_SRC_FILES := deps/Android/OpenAL-Soft/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libopenal.a LOCAL_SRC_FILES := deps/$(APP_ABI)/OpenAL-Soft/libopenal.a
include $(PREBUILT_STATIC_LIBRARY) include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := GetText LOCAL_MODULE := Gettext
LOCAL_SRC_FILES := deps/Android/GetText/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libintl.a LOCAL_SRC_FILES := deps/$(APP_ABI)/Gettext/libintl.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := SQLite3
LOCAL_SRC_FILES := deps/$(APP_ABI)/SQLite/libsqlite3.a
include $(PREBUILT_STATIC_LIBRARY) include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := Vorbis LOCAL_MODULE := Vorbis
LOCAL_SRC_FILES := deps/Android/Vorbis/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libvorbis.a LOCAL_SRC_FILES := deps/$(APP_ABI)/Vorbis/libvorbis.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libvorbisfile
LOCAL_SRC_FILES := deps/$(APP_ABI)/Vorbis/libvorbisfile.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libogg
LOCAL_SRC_FILES := deps/$(APP_ABI)/Vorbis/libogg.a
include $(PREBUILT_STATIC_LIBRARY) include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := Zstd LOCAL_MODULE := Zstd
LOCAL_SRC_FILES := deps/Android/Zstd/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libzstd.a LOCAL_SRC_FILES := deps/$(APP_ABI)/Zstd/libzstd.a
include $(PREBUILT_STATIC_LIBRARY) include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS) include $(CLEAR_VARS)
@ -71,7 +91,6 @@ LOCAL_CFLAGS += \
-DENABLE_GLES=1 \ -DENABLE_GLES=1 \
-DUSE_CURL=1 \ -DUSE_CURL=1 \
-DUSE_SOUND=1 \ -DUSE_SOUND=1 \
-DUSE_FREETYPE=1 \
-DUSE_LEVELDB=0 \ -DUSE_LEVELDB=0 \
-DUSE_LUAJIT=1 \ -DUSE_LUAJIT=1 \
-DUSE_GETTEXT=1 \ -DUSE_GETTEXT=1 \
@ -96,18 +115,16 @@ LOCAL_C_INCLUDES := \
../../src/script \ ../../src/script \
../../lib/gmp \ ../../lib/gmp \
../../lib/jsoncpp \ ../../lib/jsoncpp \
deps/Android/Curl/include \ deps/$(APP_ABI)/Curl/include \
deps/Android/Freetype/include \ deps/$(APP_ABI)/Freetype/include/freetype2 \
deps/Android/Irrlicht/include \ deps/$(APP_ABI)/Irrlicht/include \
deps/Android/LevelDB/include \ deps/$(APP_ABI)/Gettext/include \
deps/Android/GetText/include \ deps/$(APP_ABI)/Iconv/include \
deps/Android/libiconv/include \ deps/$(APP_ABI)/LuaJIT/include \
deps/Android/libiconv/libcharset/include \ deps/$(APP_ABI)/OpenAL-Soft/include \
deps/Android/LuaJIT/src \ deps/$(APP_ABI)/SQLite/include \
deps/Android/OpenAL-Soft/include \ deps/$(APP_ABI)/Vorbis/include \
deps/Android/sqlite \ deps/$(APP_ABI)/Zstd/include
deps/Android/Vorbis/include \
deps/Android/Zstd/include
LOCAL_SRC_FILES := \ LOCAL_SRC_FILES := \
$(wildcard ../../src/client/*.cpp) \ $(wildcard ../../src/client/*.cpp) \
@ -190,24 +207,24 @@ LOCAL_SRC_FILES := \
../../src/voxel.cpp \ ../../src/voxel.cpp \
../../src/voxelalgorithms.cpp ../../src/voxelalgorithms.cpp
# LevelDB backend is disabled
# ../../src/database/database-leveldb.cpp
# GMP # GMP
LOCAL_SRC_FILES += ../../lib/gmp/mini-gmp.c LOCAL_SRC_FILES += ../../lib/gmp/mini-gmp.c
# JSONCPP # JSONCPP
LOCAL_SRC_FILES += ../../lib/jsoncpp/jsoncpp.cpp LOCAL_SRC_FILES += ../../lib/jsoncpp/jsoncpp.cpp
# iconv LOCAL_STATIC_LIBRARIES += \
LOCAL_SRC_FILES += \ Curl libmbedcrypto libmbedtls libmbedx509 \
deps/Android/libiconv/lib/iconv.c \ Freetype \
deps/Android/libiconv/libcharset/lib/localcharset.c Iconv libcharset \
Irrlicht \
# SQLite3 LuaJIT \
LOCAL_SRC_FILES += deps/Android/sqlite/sqlite3.c OpenAL \
Gettext \
LOCAL_STATIC_LIBRARIES += Curl Freetype Irrlicht OpenAL mbedTLS mbedx509 mbedcrypto Vorbis LuaJIT GetText Zstd android_native_app_glue $(PROFILER_LIBS) #LevelDB SQLite3 \
Vorbis libvorbisfile libogg \
Zstd
LOCAL_STATIC_LIBRARIES += android_native_app_glue $(PROFILER_LIBS)
LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES

View File

@ -5,22 +5,22 @@ NDK_TOOLCHAIN_VERSION := clang
APP_SHORT_COMMANDS := true APP_SHORT_COMMANDS := true
APP_MODULES := Minetest APP_MODULES := Minetest
APP_CPPFLAGS := -Ofast -fvisibility=hidden -fexceptions -Wno-deprecated-declarations -Wno-extra-tokens APP_CPPFLAGS := -O2 -fvisibility=hidden
ifeq ($(APP_ABI),armeabi-v7a) ifeq ($(APP_ABI),armeabi-v7a)
APP_CPPFLAGS += -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb APP_CPPFLAGS += -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb
endif endif
#ifeq ($(APP_ABI),x86) ifeq ($(APP_ABI),x86)
#APP_CPPFLAGS += -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32 -funroll-loops APP_CPPFLAGS += -mssse3 -mfpmath=sse -funroll-loops
#endif endif
ifndef NDEBUG ifndef NDEBUG
APP_CPPFLAGS := -g -D_DEBUG -O0 -fno-omit-frame-pointer -fexceptions APP_CPPFLAGS := -g -Og -fno-omit-frame-pointer
endif endif
APP_CFLAGS := $(APP_CPPFLAGS) -Wno-parentheses-equality #-Werror=shorten-64-to-32 APP_CFLAGS := $(APP_CPPFLAGS) -Wno-inconsistent-missing-override -Wno-parentheses-equality
APP_CXXFLAGS := $(APP_CPPFLAGS) -frtti -std=gnu++17 APP_CXXFLAGS := $(APP_CPPFLAGS) -fexceptions -frtti -std=gnu++14
APP_LDFLAGS := -Wl,--no-warn-mismatch,--gc-sections,--icf=safe APP_LDFLAGS := -Wl,--no-warn-mismatch,--gc-sections,--icf=safe
ifeq ($(APP_ABI),arm64-v8a) ifeq ($(APP_ABI),arm64-v8a)

46
builtin/async/game.lua Normal file
View File

@ -0,0 +1,46 @@
core.log("info", "Initializing asynchronous environment (game)")
local function pack2(...)
return {n=select('#', ...), ...}
end
-- Entrypoint to run async jobs, called by C++
function core.job_processor(func, params)
local retval = pack2(func(unpack(params, 1, params.n)))
return retval
end
-- Import a bunch of individual files from builtin/game/
local gamepath = core.get_builtin_path() .. "game" .. DIR_DELIM
local commonpath = core.get_builtin_path() .. "common" .. DIR_DELIM
dofile(gamepath .. "constants.lua")
dofile(gamepath .. "item_s.lua")
dofile(gamepath .. "misc_s.lua")
dofile(gamepath .. "features.lua")
dofile(commonpath .. "voxelarea.lua")
-- Transfer of globals
do
local all = assert(core.transferred_globals)
core.transferred_globals = nil
-- reassemble other tables
all.registered_nodes = {}
all.registered_craftitems = {}
all.registered_tools = {}
for k, v in pairs(all.registered_items) do
if v.type == "node" then
all.registered_nodes[k] = v
elseif v.type == "craftitem" then
all.registered_craftitems[k] = v
elseif v.type == "tool" then
all.registered_tools[k] = v
end
end
for k, v in pairs(all) do
core[k] = v
end
end

View File

@ -1,5 +1,4 @@
core.log("info", "Initializing asynchronous environment")
core.log("info", "Initializing Asynchronous environment")
function core.job_processor(func, serialized_param) function core.job_processor(func, serialized_param)
local param = core.deserialize(serialized_param) local param = core.deserialize(serialized_param)
@ -8,4 +7,3 @@ function core.job_processor(func, serialized_param)
return retval or core.serialize(nil) return retval or core.serialize(nil)
end end

View File

@ -1,4 +1,3 @@
core.callback_origins = {} core.callback_origins = {}
local getinfo = debug.getinfo local getinfo = debug.getinfo

View File

@ -37,7 +37,14 @@ function core.after(after, func, ...)
arg = {...}, arg = {...},
mod_origin = core.get_last_run_mod(), mod_origin = core.get_last_run_mod(),
} }
jobs[#jobs + 1] = new_job jobs[#jobs + 1] = new_job
time_next = math.min(time_next, expire) time_next = math.min(time_next, expire)
return { cancel = function() new_job.func = function() end end }
return {
cancel = function()
new_job.func = function() end
new_job.args = {}
end
}
end end

View File

@ -6,6 +6,42 @@ local S = core.get_translator("__builtin")
core.registered_chatcommands = {} core.registered_chatcommands = {}
-- Interpret the parameters of a command, separating options and arguments.
-- Input: command, param
-- command: name of command
-- param: parameters of command
-- Returns: opts, args
-- opts is a string of option letters, or false on error
-- args is an array with the non-option arguments in order, or an error message
-- Example: for this command line:
-- /command a b -cd e f -g
-- the function would receive:
-- a b -cd e f -g
-- and it would return:
-- "cdg", {"a", "b", "e", "f"}
-- Negative numbers are taken as arguments. Long options (--option) are
-- currently rejected as reserved.
local function getopts(command, param)
local opts = ""
local args = {}
for match in param:gmatch("%S+") do
if match:byte(1) == 45 then -- 45 = '-'
local second = match:byte(2)
if second == 45 then
return false, S("Invalid parameters (see /help @1).", command)
elseif second and (second < 48 or second > 57) then -- 48 = '0', 57 = '9'
opts = opts .. match:sub(2)
else
-- numeric, add it to args
args[#args + 1] = match
end
else
args[#args + 1] = match
end
end
return opts, args
end
function core.register_chatcommand(cmd, def) function core.register_chatcommand(cmd, def)
def = def or {} def = def or {}
def.params = def.params or "" def.params = def.params or ""
@ -78,22 +114,30 @@ if INIT == "client" then
end end
end end
local function do_help_cmd(name, param) local function format_help_line(cmd, def)
local function format_help_line(cmd, def) local cmd_marker = INIT == "client" and "." or "/"
local cmd_marker = "/" local msg = core.colorize("#00ffff", cmd_marker .. cmd)
if INIT == "client" then if def.params and def.params ~= "" then
cmd_marker = "." msg = msg .. " " .. def.params
end
local msg = core.colorize("#00ffff", cmd_marker .. cmd)
if def.params and def.params ~= "" then
msg = msg .. " " .. def.params
end
if def.description and def.description ~= "" then
msg = msg .. ": " .. def.description
end
return msg
end end
if param == "" then if def.description and def.description ~= "" then
msg = msg .. ": " .. def.description
end
return msg
end
local function do_help_cmd(name, param)
local opts, args = getopts("help", param)
if not opts then
return false, args
end
if #args > 1 then
return false, S("Too many arguments, try using just /help <command>")
end
local use_gui = INIT ~= "client" and core.get_player_by_name(name)
use_gui = use_gui and not opts:find("t")
if #args == 0 and not use_gui then
local cmds = {} local cmds = {}
for cmd, def in pairs(core.registered_chatcommands) do for cmd, def in pairs(core.registered_chatcommands) do
if INIT == "client" or core.check_player_privs(name, def.privs) then if INIT == "client" or core.check_player_privs(name, def.privs) then
@ -116,7 +160,10 @@ local function do_help_cmd(name, param)
.. "everything.") .. "everything.")
end end
return true, msg return true, msg
elseif param == "all" then elseif #args == 0 or (args[1] == "all" and use_gui) then
core.show_general_help_formspec(name)
return true
elseif args[1] == "all" then
local cmds = {} local cmds = {}
for cmd, def in pairs(core.registered_chatcommands) do for cmd, def in pairs(core.registered_chatcommands) do
if INIT == "client" or core.check_player_privs(name, def.privs) then if INIT == "client" or core.check_player_privs(name, def.privs) then
@ -131,7 +178,11 @@ local function do_help_cmd(name, param)
msg = core.gettext("Available commands:") msg = core.gettext("Available commands:")
end end
return true, msg.."\n"..table.concat(cmds, "\n") return true, msg.."\n"..table.concat(cmds, "\n")
elseif INIT == "game" and param == "privs" then elseif INIT == "game" and args[1] == "privs" then
if use_gui then
core.show_privs_help_formspec(name)
return true
end
local privs = {} local privs = {}
for priv, def in pairs(core.registered_privileges) do for priv, def in pairs(core.registered_privileges) do
privs[#privs + 1] = priv .. ": " .. def.description privs[#privs + 1] = priv .. ": " .. def.description
@ -139,7 +190,7 @@ local function do_help_cmd(name, param)
table.sort(privs) table.sort(privs)
return true, S("Available privileges:").."\n"..table.concat(privs, "\n") return true, S("Available privileges:").."\n"..table.concat(privs, "\n")
else else
local cmd = param local cmd = args[1]
local def = core.registered_chatcommands[cmd] local def = core.registered_chatcommands[cmd]
if not def then if not def then
local msg local msg
@ -165,8 +216,8 @@ if INIT == "client" then
}) })
else else
core.register_chatcommand("help", { core.register_chatcommand("help", {
params = S("[all | privs | <cmd>]"), params = S("[all | privs | <cmd>] [-t]"),
description = S("Get help for commands or list privileges"), description = S("Get help for commands or list privileges (-t: output in chat)"),
func = do_help_cmd, func = do_help_cmd,
}) })
end end

View File

@ -125,30 +125,12 @@ core.register_on_player_receive_fields(function(player, formname, fields)
end end
end) end)
function core.show_general_help_formspec(name)
local help_command = core.registered_chatcommands["help"] core.show_formspec(name, "__builtin:help_cmds",
local old_help_func = help_command.func build_chatcommands_formspec(name))
help_command.func = function(name, param)
local admin = core.settings:get("name")
-- If the admin ran help, put the output in the chat buffer as well to
-- work with the server terminal
if param == "privs" then
core.show_formspec(name, "__builtin:help_privs",
build_privs_formspec(name))
if name ~= admin then
return true
end
end
if param == "" or param == "all" then
core.show_formspec(name, "__builtin:help_cmds",
build_chatcommands_formspec(name))
if name ~= admin then
return true
end
end
return old_help_func(name, param)
end end
function core.show_privs_help_formspec(name)
core.show_formspec(name, "__builtin:help_privs",
build_privs_formspec(name))
end

View File

@ -543,7 +543,7 @@ if INIT == "mainmenu" then
end end
end end
if INIT == "client" or INIT == "mainmenu" then if core.gettext then -- for client and mainmenu
function fgettext_ne(text, ...) function fgettext_ne(text, ...)
text = core.gettext(text) text = core.gettext(text)
local arg = {n=select('#', ...), ...} local arg = {n=select('#', ...), ...}

View File

@ -1,8 +1,3 @@
-- Always warn when creating a global variable, even outside of a function.
-- This ignores mod namespaces (variables with the same name as the current mod).
local WARN_INIT = false
local getinfo = debug.getinfo local getinfo = debug.getinfo
function core.global_exists(name) function core.global_exists(name)
@ -33,11 +28,6 @@ function meta:__newindex(name, value)
end end
declared[name] = true declared[name] = true
end end
-- Ignore mod namespaces
if WARN_INIT and name ~= core.get_current_modname() then
core.log("warning", ("Global variable %q created at %s.")
:format(name, desc))
end
rawset(self, name, value) rawset(self, name, value)
end end
@ -54,4 +44,3 @@ function meta:__index(name)
end end
setmetatable(_G, meta) setmetatable(_G, meta)

View File

@ -1,4 +1,5 @@
_G.core = {} _G.core = {}
_G.vector = {metatable = {}}
dofile("builtin/common/vector.lua") dofile("builtin/common/vector.lua")
dofile("builtin/common/misc_helpers.lua") dofile("builtin/common/misc_helpers.lua")

View File

@ -1,4 +1,5 @@
_G.core = {} _G.core = {}
_G.vector = {metatable = {}}
_G.setfenv = require 'busted.compatibility'.setfenv _G.setfenv = require 'busted.compatibility'.setfenv

View File

@ -1,4 +1,4 @@
_G.vector = {} _G.vector = {metatable = {}}
dofile("builtin/common/vector.lua") dofile("builtin/common/vector.lua")
describe("vector", function() describe("vector", function()
@ -128,6 +128,14 @@ describe("vector", function()
assert.equal(vector.new(4.1, 5.9, 5.5), a:apply(f)) assert.equal(vector.new(4.1, 5.9, 5.5), a:apply(f))
end) end)
it("combine()", function()
local a = vector.new(1, 2, 3)
local b = vector.new(3, 2, 1)
assert.equal(vector.add(a, b), vector.combine(a, b, function(x, y) return x + y end))
assert.equal(vector.new(3, 2, 3), vector.combine(a, b, math.max))
assert.equal(vector.new(1, 2, 1), vector.combine(a, b, math.min))
end)
it("equals()", function() it("equals()", function()
local function assertE(a, b) local function assertE(a, b)
assert.is_true(vector.equals(a, b)) assert.is_true(vector.equals(a, b))
@ -300,6 +308,7 @@ describe("vector", function()
it("from_string()", function() it("from_string()", function()
local v = vector.new(1, 2, 3.14) local v = vector.new(1, 2, 3.14)
assert.is_true(vector.check(vector.from_string("(1, 2, 3.14)")))
assert.same({v, 13}, {vector.from_string("(1, 2, 3.14)")}) assert.same({v, 13}, {vector.from_string("(1, 2, 3.14)")})
assert.same({v, 12}, {vector.from_string("(1,2 ,3.14)")}) assert.same({v, 12}, {vector.from_string("(1,2 ,3.14)")})
assert.same({v, 12}, {vector.from_string("(1,2,3.14,)")}) assert.same({v, 12}, {vector.from_string("(1,2,3.14,)")})

View File

@ -6,10 +6,8 @@ Note: The vector.*-functions must be able to accept old vectors that had no meta
-- localize functions -- localize functions
local setmetatable = setmetatable local setmetatable = setmetatable
vector = {} -- vector.metatable is set by C++.
local metatable = vector.metatable
local metatable = {}
vector.metatable = metatable
local xyz = {"x", "y", "z"} local xyz = {"x", "y", "z"}
@ -61,7 +59,7 @@ function vector.from_string(s, init)
if not (x and y and z) then if not (x and y and z) then
return nil return nil
end end
return {x = x, y = y, z = z}, np return fast_new(x, y, z), np
end end
function vector.to_string(v) function vector.to_string(v)
@ -112,6 +110,14 @@ function vector.apply(v, func)
) )
end end
function vector.combine(a, b, func)
return fast_new(
func(a.x, b.x),
func(a.y, b.y),
func(a.z, b.z)
)
end
function vector.distance(a, b) function vector.distance(a, b)
local x = a.x - b.x local x = a.x - b.x
local y = a.y - b.y local y = a.y - b.y

View File

@ -63,7 +63,7 @@ function ui.update()
-- handle errors -- handle errors
if gamedata ~= nil and gamedata.reconnect_requested then if gamedata ~= nil and gamedata.reconnect_requested then
local error_message = core.formspec_escape( local error_message = core.formspec_escape(
gamedata.errormessage or "<none available>") gamedata.errormessage or fgettext("<none available>"))
formspec = { formspec = {
"size[14,8]", "size[14,8]",
"real_coordinates[true]", "real_coordinates[true]",

22
builtin/game/async.lua Normal file
View File

@ -0,0 +1,22 @@
core.async_jobs = {}
function core.async_event_handler(jobid, retval)
local callback = core.async_jobs[jobid]
assert(type(callback) == "function")
callback(unpack(retval, 1, retval.n))
core.async_jobs[jobid] = nil
end
function core.handle_async(func, callback, ...)
assert(type(func) == "function" and type(callback) == "function",
"Invalid minetest.handle_async invocation")
local args = {n = select("#", ...), ...}
local mod_origin = core.get_last_run_mod()
local jobid = core.do_async_callback(func, args, mod_origin)
core.async_jobs[jobid] = callback
return true
end

View File

@ -310,12 +310,7 @@ local function handle_revoke_command(caller, revokename, revokeprivstr)
and revokename == core.settings:get("name") and revokename == core.settings:get("name")
and revokename ~= "" and revokename ~= ""
if revokeprivstr == "all" then if revokeprivstr == "all" then
revokeprivs = privs revokeprivs = table.copy(privs)
privs = {}
else
for priv, _ in pairs(revokeprivs) do
privs[priv] = nil
end
end end
local privs_unknown = "" local privs_unknown = ""
@ -332,7 +327,10 @@ local function handle_revoke_command(caller, revokename, revokeprivstr)
end end
local def = core.registered_privileges[priv] local def = core.registered_privileges[priv]
if not def then if not def then
privs_unknown = privs_unknown .. S("Unknown privilege: @1", priv) .. "\n" -- Old/removed privileges might still be granted to certain players
if not privs[priv] then
privs_unknown = privs_unknown .. S("Unknown privilege: @1", priv) .. "\n"
end
elseif is_singleplayer and def.give_to_singleplayer then elseif is_singleplayer and def.give_to_singleplayer then
irrevokable[priv] = true irrevokable[priv] = true
elseif is_admin and def.give_to_admin then elseif is_admin and def.give_to_admin then
@ -359,19 +357,22 @@ local function handle_revoke_command(caller, revokename, revokeprivstr)
end end
local revokecount = 0 local revokecount = 0
for priv, _ in pairs(revokeprivs) do
privs[priv] = nil
revokecount = revokecount + 1
end
if revokecount == 0 then
return false, S("No privileges were revoked.")
end
core.set_player_privs(revokename, privs) core.set_player_privs(revokename, privs)
for priv, _ in pairs(revokeprivs) do for priv, _ in pairs(revokeprivs) do
-- call the on_revoke callbacks -- call the on_revoke callbacks
core.run_priv_callbacks(revokename, priv, caller, "revoke") core.run_priv_callbacks(revokename, priv, caller, "revoke")
revokecount = revokecount + 1
end end
local new_privs = core.get_player_privs(revokename) local new_privs = core.get_player_privs(revokename)
if revokecount == 0 then
return false, S("No privileges were revoked.")
end
core.log("action", caller..' revoked (' core.log("action", caller..' revoked ('
..core.privs_to_string(revokeprivs, ', ') ..core.privs_to_string(revokeprivs, ', ')
..') privileges from '..revokename) ..') privileges from '..revokename)
@ -524,7 +525,7 @@ end
-- Teleports player <name> to <p> if possible -- Teleports player <name> to <p> if possible
local function teleport_to_pos(name, p) local function teleport_to_pos(name, p)
local lm = 31000 local lm = 31007 -- equals MAX_MAP_GENERATION_LIMIT in C++
if p.x < -lm or p.x > lm or p.y < -lm or p.y > lm if p.x < -lm or p.x > lm or p.y < -lm or p.y > lm
or p.z < -lm or p.z > lm then or p.z < -lm or p.z > lm then
return false, S("Cannot teleport out of map bounds!") return false, S("Cannot teleport out of map bounds!")
@ -621,6 +622,10 @@ core.register_chatcommand("set", {
setname, setvalue = string.match(param, "([^ ]+) (.+)") setname, setvalue = string.match(param, "([^ ]+) (.+)")
if setname and setvalue then if setname and setvalue then
if setname:sub(1, 7) == "secure." then
return false, S("Failed. Cannot modify secure settings. "
.. "Edit the settings file manually.")
end
if not core.settings:get(setname) then if not core.settings:get(setname) then
return false, S("Failed. Use '/set -n <name> <value>' " return false, S("Failed. Use '/set -n <name> <value>' "
.. "to create a new setting.") .. "to create a new setting.")
@ -1034,12 +1039,11 @@ core.register_chatcommand("time", {
end end
local hour, minute = param:match("^(%d+):(%d+)$") local hour, minute = param:match("^(%d+):(%d+)$")
if not hour then if not hour then
local new_time = tonumber(param) local new_time = tonumber(param) or -1
if not new_time then if new_time ~= new_time or new_time < 0 or new_time > 24000 then
return false, S("Invalid time.") return false, S("Invalid time (must be between 0 and 24000).")
end end
-- Backward compatibility. core.set_timeofday(new_time / 24000)
core.set_timeofday((new_time % 24000) / 24000)
core.log("action", name .. " sets time to " .. new_time) core.log("action", name .. " sets time to " .. new_time)
return true, S("Time of day changed.") return true, S("Time of day changed.")
end end
@ -1283,7 +1287,7 @@ local function handle_kill_command(killer, victim)
return false, S("@1 is already dead.", victim) return false, S("@1 is already dead.", victim)
end end
end end
if not killer == victim then if killer ~= victim then
core.log("action", string.format("%s killed %s", killer, victim)) core.log("action", string.format("%s killed %s", killer, victim))
end end
-- Kill victim -- Kill victim

View File

@ -22,6 +22,7 @@ core.features = {
degrotate_240_steps = true, degrotate_240_steps = true,
abm_min_max_y = true, abm_min_max_y = true,
dynamic_add_media_table = true, dynamic_add_media_table = true,
get_sky_as_table = true,
} }
function core.has_feature(arg) function core.has_feature(arg)

View File

@ -8,6 +8,7 @@ local gamepath = scriptpath .. "game".. DIR_DELIM
local builtin_shared = {} local builtin_shared = {}
dofile(gamepath .. "constants.lua") dofile(gamepath .. "constants.lua")
dofile(gamepath .. "item_s.lua")
assert(loadfile(gamepath .. "item.lua"))(builtin_shared) assert(loadfile(gamepath .. "item.lua"))(builtin_shared)
dofile(gamepath .. "register.lua") dofile(gamepath .. "register.lua")
@ -19,6 +20,7 @@ dofile(commonpath .. "after.lua")
dofile(commonpath .. "voxelarea.lua") dofile(commonpath .. "voxelarea.lua")
dofile(gamepath .. "item_entity.lua") dofile(gamepath .. "item_entity.lua")
dofile(gamepath .. "deprecated.lua") dofile(gamepath .. "deprecated.lua")
dofile(gamepath .. "misc_s.lua")
dofile(gamepath .. "misc.lua") dofile(gamepath .. "misc.lua")
dofile(gamepath .. "privileges.lua") dofile(gamepath .. "privileges.lua")
dofile(gamepath .. "auth.lua") dofile(gamepath .. "auth.lua")
@ -32,5 +34,6 @@ dofile(gamepath .. "features.lua")
dofile(gamepath .. "forceloading.lua") dofile(gamepath .. "forceloading.lua")
dofile(gamepath .. "statbars.lua") dofile(gamepath .. "statbars.lua")
dofile(gamepath .. "knockback.lua") dofile(gamepath .. "knockback.lua")
dofile(gamepath .. "async.lua")
profiler = nil profiler = nil

View File

@ -15,144 +15,19 @@ end
-- Item definition helpers -- Item definition helpers
-- --
function core.dir_to_facedir(dir, is6d) function core.get_pointed_thing_position(pointed_thing, above)
--account for y if requested if pointed_thing.type == "node" then
if is6d and math.abs(dir.y) > math.abs(dir.x) and math.abs(dir.y) > math.abs(dir.z) then if above then
-- The position where a node would be placed
--from above return pointed_thing.above
if dir.y < 0 then
if math.abs(dir.x) > math.abs(dir.z) then
if dir.x < 0 then
return 19
else
return 13
end
else
if dir.z < 0 then
return 10
else
return 4
end
end
--from below
else
if math.abs(dir.x) > math.abs(dir.z) then
if dir.x < 0 then
return 15
else
return 17
end
else
if dir.z < 0 then
return 6
else
return 8
end
end
end
--otherwise, place horizontally
elseif math.abs(dir.x) > math.abs(dir.z) then
if dir.x < 0 then
return 3
else
return 1
end
else
if dir.z < 0 then
return 2
else
return 0
end end
-- The position where a node would be dug
return pointed_thing.under
elseif pointed_thing.type == "object" then
return pointed_thing.ref and pointed_thing.ref:get_pos()
end end
end end
-- Table of possible dirs
local facedir_to_dir = {
vector.new( 0, 0, 1),
vector.new( 1, 0, 0),
vector.new( 0, 0, -1),
vector.new(-1, 0, 0),
vector.new( 0, -1, 0),
vector.new( 0, 1, 0),
}
-- Mapping from facedir value to index in facedir_to_dir.
local facedir_to_dir_map = {
[0]=1, 2, 3, 4,
5, 2, 6, 4,
6, 2, 5, 4,
1, 5, 3, 6,
1, 6, 3, 5,
1, 4, 3, 2,
}
function core.facedir_to_dir(facedir)
return facedir_to_dir[facedir_to_dir_map[facedir % 32]]
end
function core.dir_to_wallmounted(dir)
if math.abs(dir.y) > math.max(math.abs(dir.x), math.abs(dir.z)) then
if dir.y < 0 then
return 1
else
return 0
end
elseif math.abs(dir.x) > math.abs(dir.z) then
if dir.x < 0 then
return 3
else
return 2
end
else
if dir.z < 0 then
return 5
else
return 4
end
end
end
-- table of dirs in wallmounted order
local wallmounted_to_dir = {
[0] = vector.new( 0, 1, 0),
vector.new( 0, -1, 0),
vector.new( 1, 0, 0),
vector.new(-1, 0, 0),
vector.new( 0, 0, 1),
vector.new( 0, 0, -1),
}
function core.wallmounted_to_dir(wallmounted)
return wallmounted_to_dir[wallmounted % 8]
end
function core.dir_to_yaw(dir)
return -math.atan2(dir.x, dir.z)
end
function core.yaw_to_dir(yaw)
return vector.new(-math.sin(yaw), 0, math.cos(yaw))
end
function core.is_colored_paramtype(ptype)
return (ptype == "color") or (ptype == "colorfacedir") or
(ptype == "colorwallmounted") or (ptype == "colordegrotate")
end
function core.strip_param2_color(param2, paramtype2)
if not core.is_colored_paramtype(paramtype2) then
return nil
end
if paramtype2 == "colorfacedir" then
param2 = math.floor(param2 / 32) * 32
elseif paramtype2 == "colorwallmounted" then
param2 = math.floor(param2 / 8) * 8
elseif paramtype2 == "colordegrotate" then
param2 = math.floor(param2 / 32) * 32
end
-- paramtype2 == "color" requires no modification.
return param2
end
local function has_all_groups(tbl, required_groups) local function has_all_groups(tbl, required_groups)
if type(required_groups) == "string" then if type(required_groups) == "string" then
return (tbl[required_groups] or 0) ~= 0 return (tbl[required_groups] or 0) ~= 0
@ -477,34 +352,41 @@ function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed
return result return result
end end
end end
-- read definition before potentially emptying the stack
local def = itemstack:get_definition() local def = itemstack:get_definition()
if itemstack:take_item() ~= nil then if itemstack:take_item():is_empty() then
user:set_hp(user:get_hp() + hp_change) return itemstack
end
if def and def.sound and def.sound.eat then if def and def.sound and def.sound.eat then
core.sound_play(def.sound.eat, { core.sound_play(def.sound.eat, {
pos = user:get_pos(), pos = user:get_pos(),
max_hear_distance = 16 max_hear_distance = 16
}, true) }, true)
end end
if replace_with_item then -- Changing hp might kill the player causing mods to do who-knows-what to the
if itemstack:is_empty() then -- inventory, so do this before set_hp().
itemstack:add_item(replace_with_item) if replace_with_item then
if itemstack:is_empty() then
itemstack:add_item(replace_with_item)
else
local inv = user:get_inventory()
-- Check if inv is null, since non-players don't have one
if inv and inv:room_for_item("main", {name=replace_with_item}) then
inv:add_item("main", replace_with_item)
else else
local inv = user:get_inventory() local pos = user:get_pos()
-- Check if inv is null, since non-players don't have one pos.y = math.floor(pos.y + 0.5)
if inv and inv:room_for_item("main", {name=replace_with_item}) then core.add_item(pos, replace_with_item)
inv:add_item("main", replace_with_item)
else
local pos = user:get_pos()
pos.y = math.floor(pos.y + 0.5)
core.add_item(pos, replace_with_item)
end
end end
end end
end end
return itemstack user:set_wielded_item(itemstack)
user:set_hp(user:get_hp() + hp_change)
return nil -- don't overwrite wield item a second time
end end
function core.item_eat(hp_change, replace_with_item) function core.item_eat(hp_change, replace_with_item)
@ -585,7 +467,7 @@ function core.node_dig(pos, node, digger)
if wielded then if wielded then
local wdef = wielded:get_definition() local wdef = wielded:get_definition()
local tp = wielded:get_tool_capabilities() local tp = wielded:get_tool_capabilities()
local dp = core.get_dig_params(def and def.groups, tp) local dp = core.get_dig_params(def and def.groups, tp, wielded:get_wear())
if wdef and wdef.after_use then if wdef and wdef.after_use then
wielded = wdef.after_use(wielded, digger, node, dp) or wielded wielded = wdef.after_use(wielded, digger, node, dp) or wielded
else else
@ -647,9 +529,7 @@ function core.node_dig(pos, node, digger)
-- Run script hook -- Run script hook
for _, callback in ipairs(core.registered_on_dignodes) do for _, callback in ipairs(core.registered_on_dignodes) do
local origin = core.callback_origins[callback] local origin = core.callback_origins[callback]
if origin then core.set_last_run_mod(origin.mod)
core.set_last_run_mod(origin.mod)
end
-- Copy pos and node because callback can modify them -- Copy pos and node because callback can modify them
local pos_copy = vector.new(pos) local pos_copy = vector.new(pos)

View File

@ -58,17 +58,21 @@ core.register_entity(":__builtin:item", {
local glow = def and def.light_source and local glow = def and def.light_source and
math.floor(def.light_source / 2 + 0.5) math.floor(def.light_source / 2 + 0.5)
local size_bias = 1e-3 * math.random() -- small random bias to counter Z-fighting
local c = {-size, -size, -size, size, size, size}
self.object:set_properties({ self.object:set_properties({
is_visible = true, is_visible = true,
visual = "wielditem", visual = "wielditem",
textures = {itemname}, textures = {itemname},
visual_size = {x = size, y = size}, visual_size = {x = size + size_bias, y = size + size_bias},
collisionbox = {-size, -size, -size, size, size, size}, collisionbox = c,
automatic_rotate = math.pi * 0.5 * 0.2 / size, automatic_rotate = math.pi * 0.5 * 0.2 / size,
wield_item = self.itemstring, wield_item = self.itemstring,
glow = glow, glow = glow,
}) })
-- cache for usage in on_step
self._collisionbox = c
end, end,
get_staticdata = function(self) get_staticdata = function(self)
@ -93,6 +97,7 @@ core.register_entity(":__builtin:item", {
self.object:set_armor_groups({immortal = 1}) self.object:set_armor_groups({immortal = 1})
self.object:set_velocity({x = 0, y = 2, z = 0}) self.object:set_velocity({x = 0, y = 2, z = 0})
self.object:set_acceleration({x = 0, y = -gravity, z = 0}) self.object:set_acceleration({x = 0, y = -gravity, z = 0})
self._collisionbox = self.initial_properties.collisionbox
self:set_item() self:set_item()
end, end,
@ -163,7 +168,7 @@ core.register_entity(":__builtin:item", {
local pos = self.object:get_pos() local pos = self.object:get_pos()
local node = core.get_node_or_nil({ local node = core.get_node_or_nil({
x = pos.x, x = pos.x,
y = pos.y + self.object:get_properties().collisionbox[2] - 0.05, y = pos.y + self._collisionbox[2] - 0.05,
z = pos.z z = pos.z
}) })
-- Delete in 'ignore' nodes -- Delete in 'ignore' nodes
@ -176,7 +181,7 @@ core.register_entity(":__builtin:item", {
if self.force_out then if self.force_out then
-- This code runs after the entity got a push from the is_stuck code. -- This code runs after the entity got a push from the is_stuck code.
-- It makes sure the entity is entirely outside the solid node -- It makes sure the entity is entirely outside the solid node
local c = self.object:get_properties().collisionbox local c = self._collisionbox
local s = self.force_out_start local s = self.force_out_start
local f = self.force_out local f = self.force_out
local ok = (f.x > 0 and pos.x + c[1] > s.x + 0.5) or local ok = (f.x > 0 and pos.x + c[1] > s.x + 0.5) or

156
builtin/game/item_s.lua Normal file
View File

@ -0,0 +1,156 @@
-- Minetest: builtin/item_s.lua
-- The distinction of what goes here is a bit tricky, basically it's everything
-- that does not (directly or indirectly) need access to ServerEnvironment,
-- Server or writable access to IGameDef on the engine side.
-- (The '_s' stands for standalone.)
--
-- Item definition helpers
--
function core.inventorycube(img1, img2, img3)
img2 = img2 or img1
img3 = img3 or img1
return "[inventorycube"
.. "{" .. img1:gsub("%^", "&")
.. "{" .. img2:gsub("%^", "&")
.. "{" .. img3:gsub("%^", "&")
end
function core.dir_to_facedir(dir, is6d)
--account for y if requested
if is6d and math.abs(dir.y) > math.abs(dir.x) and math.abs(dir.y) > math.abs(dir.z) then
--from above
if dir.y < 0 then
if math.abs(dir.x) > math.abs(dir.z) then
if dir.x < 0 then
return 19
else
return 13
end
else
if dir.z < 0 then
return 10
else
return 4
end
end
--from below
else
if math.abs(dir.x) > math.abs(dir.z) then
if dir.x < 0 then
return 15
else
return 17
end
else
if dir.z < 0 then
return 6
else
return 8
end
end
end
--otherwise, place horizontally
elseif math.abs(dir.x) > math.abs(dir.z) then
if dir.x < 0 then
return 3
else
return 1
end
else
if dir.z < 0 then
return 2
else
return 0
end
end
end
-- Table of possible dirs
local facedir_to_dir = {
vector.new( 0, 0, 1),
vector.new( 1, 0, 0),
vector.new( 0, 0, -1),
vector.new(-1, 0, 0),
vector.new( 0, -1, 0),
vector.new( 0, 1, 0),
}
-- Mapping from facedir value to index in facedir_to_dir.
local facedir_to_dir_map = {
[0]=1, 2, 3, 4,
5, 2, 6, 4,
6, 2, 5, 4,
1, 5, 3, 6,
1, 6, 3, 5,
1, 4, 3, 2,
}
function core.facedir_to_dir(facedir)
return facedir_to_dir[facedir_to_dir_map[facedir % 32]]
end
function core.dir_to_wallmounted(dir)
if math.abs(dir.y) > math.max(math.abs(dir.x), math.abs(dir.z)) then
if dir.y < 0 then
return 1
else
return 0
end
elseif math.abs(dir.x) > math.abs(dir.z) then
if dir.x < 0 then
return 3
else
return 2
end
else
if dir.z < 0 then
return 5
else
return 4
end
end
end
-- table of dirs in wallmounted order
local wallmounted_to_dir = {
[0] = vector.new( 0, 1, 0),
vector.new( 0, -1, 0),
vector.new( 1, 0, 0),
vector.new(-1, 0, 0),
vector.new( 0, 0, 1),
vector.new( 0, 0, -1),
}
function core.wallmounted_to_dir(wallmounted)
return wallmounted_to_dir[wallmounted % 8]
end
function core.dir_to_yaw(dir)
return -math.atan2(dir.x, dir.z)
end
function core.yaw_to_dir(yaw)
return vector.new(-math.sin(yaw), 0, math.cos(yaw))
end
function core.is_colored_paramtype(ptype)
return (ptype == "color") or (ptype == "colorfacedir") or
(ptype == "colorwallmounted") or (ptype == "colordegrotate")
end
function core.strip_param2_color(param2, paramtype2)
if not core.is_colored_paramtype(paramtype2) then
return nil
end
if paramtype2 == "colorfacedir" then
param2 = math.floor(param2 / 32) * 32
elseif paramtype2 == "colorwallmounted" then
param2 = math.floor(param2 / 8) * 8
elseif paramtype2 == "colordegrotate" then
param2 = math.floor(param2 / 32) * 32
end
-- paramtype2 == "color" requires no modification.
return param2
end

View File

@ -6,6 +6,16 @@ local S = core.get_translator("__builtin")
-- Misc. API functions -- Misc. API functions
-- --
-- @spec core.kick_player(String, String) :: Boolean
function core.kick_player(player_name, reason)
if type(reason) == "string" then
reason = "Kicked: " .. reason
else
reason = "Kicked."
end
return core.disconnect_player(player_name, reason)
end
function core.check_player_privs(name, ...) function core.check_player_privs(name, ...)
if core.is_player(name) then if core.is_player(name) then
name = name:get_player_name() name = name:get_player_name()
@ -111,53 +121,6 @@ function core.get_player_radius_area(player_name, radius)
end end
function core.hash_node_position(pos)
return (pos.z + 32768) * 65536 * 65536
+ (pos.y + 32768) * 65536
+ pos.x + 32768
end
function core.get_position_from_hash(hash)
local x = (hash % 65536) - 32768
hash = math.floor(hash / 65536)
local y = (hash % 65536) - 32768
hash = math.floor(hash / 65536)
local z = (hash % 65536) - 32768
return vector.new(x, y, z)
end
function core.get_item_group(name, group)
if not core.registered_items[name] or not
core.registered_items[name].groups[group] then
return 0
end
return core.registered_items[name].groups[group]
end
function core.get_node_group(name, group)
core.log("deprecated", "Deprecated usage of get_node_group, use get_item_group instead")
return core.get_item_group(name, group)
end
function core.setting_get_pos(name)
local value = core.settings:get(name)
if not value then
return nil
end
return core.string_to_pos(value)
end
-- See l_env.cpp for the other functions
function core.get_artificial_light(param1)
return math.floor(param1 / 16)
end
-- To be overriden by protection mods -- To be overriden by protection mods
function core.is_protected(pos, name) function core.is_protected(pos, name)
@ -240,7 +203,7 @@ end
-- HTTP callback interface -- HTTP callback interface
function core.http_add_fetch(httpenv) core.set_http_api_lua(function(httpenv)
httpenv.fetch = function(req, callback) httpenv.fetch = function(req, callback)
local handle = httpenv.fetch_async(req) local handle = httpenv.fetch_async(req)
@ -256,7 +219,8 @@ function core.http_add_fetch(httpenv)
end end
return httpenv return httpenv
end end)
core.set_http_api_lua = nil
function core.close_formspec(player_name, formname) function core.close_formspec(player_name, formname)
@ -273,40 +237,30 @@ end
core.dynamic_media_callbacks = {} core.dynamic_media_callbacks = {}
-- PNG encoder safety wrapper -- Transfer of certain globals into async environment
-- see builtin/async/game.lua for the other side
local o_encode_png = core.encode_png local function copy_filtering(t, seen)
function core.encode_png(width, height, data, compression) if type(t) == "userdata" or type(t) == "function" then
if type(width) ~= "number" then return true -- don't use nil so presence can still be detected
error("Incorrect type for 'width', expected number, got " .. type(width)) elseif type(t) ~= "table" then
return t
end end
if type(height) ~= "number" then local n = {}
error("Incorrect type for 'height', expected number, got " .. type(height)) seen = seen or {}
seen[t] = n
for k, v in pairs(t) do
local k_ = seen[k] or copy_filtering(k, seen)
local v_ = seen[v] or copy_filtering(v, seen)
n[k_] = v_
end end
return n
local expected_byte_count = width * height * 4 end
if type(data) ~= "table" and type(data) ~= "string" then function core.get_globals_to_transfer()
error("Incorrect type for 'height', expected table or string, got " .. type(height)) local all = {
end registered_items = copy_filtering(core.registered_items),
registered_aliases = core.registered_aliases,
local data_length = type(data) == "table" and #data * 4 or string.len(data) }
return all
if data_length ~= expected_byte_count then
error(string.format(
"Incorrect length of 'data', width and height imply %d bytes but %d were provided",
expected_byte_count,
data_length
))
end
if type(data) == "table" then
local dataBuf = {}
for i = 1, #data do
dataBuf[i] = core.colorspec_to_bytes(data[i])
end
data = table.concat(dataBuf)
end
return o_encode_png(width, height, data, compression or 6)
end end

93
builtin/game/misc_s.lua Normal file
View File

@ -0,0 +1,93 @@
-- Minetest: builtin/misc_s.lua
-- The distinction of what goes here is a bit tricky, basically it's everything
-- that does not (directly or indirectly) need access to ServerEnvironment,
-- Server or writable access to IGameDef on the engine side.
-- (The '_s' stands for standalone.)
--
-- Misc. API functions
--
function core.hash_node_position(pos)
return (pos.z + 32768) * 65536 * 65536
+ (pos.y + 32768) * 65536
+ pos.x + 32768
end
function core.get_position_from_hash(hash)
local x = (hash % 65536) - 32768
hash = math.floor(hash / 65536)
local y = (hash % 65536) - 32768
hash = math.floor(hash / 65536)
local z = (hash % 65536) - 32768
return vector.new(x, y, z)
end
function core.get_item_group(name, group)
if not core.registered_items[name] or not
core.registered_items[name].groups[group] then
return 0
end
return core.registered_items[name].groups[group]
end
function core.get_node_group(name, group)
core.log("deprecated", "Deprecated usage of get_node_group, use get_item_group instead")
return core.get_item_group(name, group)
end
function core.setting_get_pos(name)
local value = core.settings:get(name)
if not value then
return nil
end
return core.string_to_pos(value)
end
-- See l_env.cpp for the other functions
function core.get_artificial_light(param1)
return math.floor(param1 / 16)
end
-- PNG encoder safety wrapper
local o_encode_png = core.encode_png
function core.encode_png(width, height, data, compression)
if type(width) ~= "number" then
error("Incorrect type for 'width', expected number, got " .. type(width))
end
if type(height) ~= "number" then
error("Incorrect type for 'height', expected number, got " .. type(height))
end
local expected_byte_count = width * height * 4
if type(data) ~= "table" and type(data) ~= "string" then
error("Incorrect type for 'data', expected table or string, got " .. type(data))
end
local data_length = type(data) == "table" and #data * 4 or string.len(data)
if data_length ~= expected_byte_count then
error(string.format(
"Incorrect length of 'data', width and height imply %d bytes but %d were provided",
expected_byte_count,
data_length
))
end
if type(data) == "table" then
local dataBuf = {}
for i = 1, #data do
dataBuf[i] = core.colorspec_to_bytes(data[i])
end
data = table.concat(dataBuf)
end
return o_encode_png(width, height, data, compression or 6)
end

View File

@ -97,10 +97,6 @@ core.register_privilege("rollback", {
description = S("Can use the rollback functionality"), description = S("Can use the rollback functionality"),
give_to_singleplayer = false, give_to_singleplayer = false,
}) })
core.register_privilege("basic_debug", {
description = S("Can view more debug info that might give a gameplay advantage"),
give_to_singleplayer = false,
})
core.register_privilege("debug", { core.register_privilege("debug", {
description = S("Can enable wireframe"), description = S("Can enable wireframe"),
give_to_singleplayer = false, give_to_singleplayer = false,

View File

@ -403,8 +403,14 @@ function core.override_item(name, redefinition)
register_item_raw(item) register_item_raw(item)
end end
do
core.callback_origins = {} local default = {mod = "??", name = "??"}
core.callback_origins = setmetatable({}, {
__index = function()
return default
end
})
end
function core.run_callbacks(callbacks, mode, ...) function core.run_callbacks(callbacks, mode, ...)
assert(type(callbacks) == "table") assert(type(callbacks) == "table")
@ -419,9 +425,7 @@ function core.run_callbacks(callbacks, mode, ...)
local ret = nil local ret = nil
for i = 1, cb_len do for i = 1, cb_len do
local origin = core.callback_origins[callbacks[i]] local origin = core.callback_origins[callbacks[i]]
if origin then core.set_last_run_mod(origin.mod)
core.set_last_run_mod(origin.mod)
end
local cb_ret = callbacks[i](...) local cb_ret = callbacks[i](...)
if mode == 0 and i == 1 then if mode == 0 and i == 1 then

View File

@ -1,39 +1,39 @@
-- cache setting -- cache setting
local enable_damage = core.settings:get_bool("enable_damage") local enable_damage = core.settings:get_bool("enable_damage")
local health_bar_definition = { local bar_definitions = {
hud_elem_type = "statbar", hp = {
position = {x = 0.5, y = 1}, hud_elem_type = "statbar",
text = "heart.png", position = {x = 0.5, y = 1},
text2 = "heart_gone.png", text = "heart.png",
number = core.PLAYER_MAX_HP_DEFAULT, text2 = "heart_gone.png",
item = core.PLAYER_MAX_HP_DEFAULT, number = core.PLAYER_MAX_HP_DEFAULT,
direction = 0, item = core.PLAYER_MAX_HP_DEFAULT,
size = {x = 24, y = 24}, direction = 0,
offset = {x = (-10 * 24) - 25, y = -(48 + 24 + 16)}, size = {x = 24, y = 24},
} offset = {x = (-10 * 24) - 25, y = -(48 + 24 + 16)},
},
local breath_bar_definition = { breath = {
hud_elem_type = "statbar", hud_elem_type = "statbar",
position = {x = 0.5, y = 1}, position = {x = 0.5, y = 1},
text = "bubble.png", text = "bubble.png",
text2 = "bubble_gone.png", text2 = "bubble_gone.png",
number = core.PLAYER_MAX_BREATH_DEFAULT, number = core.PLAYER_MAX_BREATH_DEFAULT * 2,
item = core.PLAYER_MAX_BREATH_DEFAULT * 2, item = core.PLAYER_MAX_BREATH_DEFAULT * 2,
direction = 0, direction = 0,
size = {x = 24, y = 24}, size = {x = 24, y = 24},
offset = {x = 25, y= -(48 + 24 + 16)}, offset = {x = 25, y= -(48 + 24 + 16)},
},
} }
local hud_ids = {} local hud_ids = {}
local function scaleToDefault(player, field) local function scaleToHudMax(player, field)
-- Scale "hp" or "breath" to the default dimensions -- Scale "hp" or "breath" to the hud maximum dimensions
local current = player["get_" .. field](player) local current = player["get_" .. field](player)
local nominal = core["PLAYER_MAX_" .. field:upper() .. "_DEFAULT"] local nominal = bar_definitions[field].item
local max_display = math.max(nominal, local max_display = math.max(player:get_properties()[field .. "_max"], current)
math.max(player:get_properties()[field .. "_max"], current)) return math.ceil(current / max_display * nominal)
return current / max_display * nominal
end end
local function update_builtin_statbars(player) local function update_builtin_statbars(player)
@ -55,9 +55,9 @@ local function update_builtin_statbars(player)
local immortal = player:get_armor_groups().immortal == 1 local immortal = player:get_armor_groups().immortal == 1
if flags.healthbar and enable_damage and not immortal then if flags.healthbar and enable_damage and not immortal then
local number = scaleToDefault(player, "hp") local number = scaleToHudMax(player, "hp")
if hud.id_healthbar == nil then if hud.id_healthbar == nil then
local hud_def = table.copy(health_bar_definition) local hud_def = table.copy(bar_definitions.hp)
hud_def.number = number hud_def.number = number
hud.id_healthbar = player:hud_add(hud_def) hud.id_healthbar = player:hud_add(hud_def)
else else
@ -73,9 +73,9 @@ local function update_builtin_statbars(player)
local breath = player:get_breath() local breath = player:get_breath()
local breath_max = player:get_properties().breath_max local breath_max = player:get_properties().breath_max
if show_breathbar and breath <= breath_max then if show_breathbar and breath <= breath_max then
local number = 2 * scaleToDefault(player, "breath") local number = scaleToHudMax(player, "breath")
if not hud.id_breathbar and breath < breath_max then if not hud.id_breathbar and breath < breath_max then
local hud_def = table.copy(breath_bar_definition) local hud_def = table.copy(bar_definitions.breath)
hud_def.number = number hud_def.number = number
hud.id_breathbar = player:hud_add(hud_def) hud.id_breathbar = player:hud_add(hud_def)
elseif hud.id_breathbar then elseif hud.id_breathbar then
@ -145,7 +145,7 @@ function core.hud_replace_builtin(hud_name, definition)
end end
if hud_name == "health" then if hud_name == "health" then
health_bar_definition = definition bar_definitions.hp = definition
for name, ids in pairs(hud_ids) do for name, ids in pairs(hud_ids) do
local player = core.get_player_by_name(name) local player = core.get_player_by_name(name)
@ -159,7 +159,7 @@ function core.hud_replace_builtin(hud_name, definition)
end end
if hud_name == "breath" then if hud_name == "breath" then
breath_bar_definition = definition bar_definitions.breath = definition
for name, ids in pairs(hud_ids) do for name, ids in pairs(hud_ids) do
local player = core.get_player_by_name(name) local player = core.get_player_by_name(name)

View File

@ -55,8 +55,10 @@ elseif INIT == "mainmenu" then
if not custom_loaded then if not custom_loaded then
dofile(core.get_mainmenu_path() .. DIR_DELIM .. "init.lua") dofile(core.get_mainmenu_path() .. DIR_DELIM .. "init.lua")
end end
elseif INIT == "async" then elseif INIT == "async" then
dofile(asyncpath .. "init.lua") dofile(asyncpath .. "mainmenu.lua")
elseif INIT == "async_game" then
dofile(asyncpath .. "game.lua")
elseif INIT == "client" then elseif INIT == "client" then
dofile(clientpath .. "init.lua") dofile(clientpath .. "init.lua")
else else

View File

@ -139,7 +139,7 @@ This command was disabled by a mod or game.=Dieser Befehl wurde von einer Mod od
Show or set time of day=Tageszeit anzeigen oder setzen Show or set time of day=Tageszeit anzeigen oder setzen
Current time is @1:@2.=Es ist jetzt @1:@2 Uhr. Current time is @1:@2.=Es ist jetzt @1:@2 Uhr.
You don't have permission to run this command (missing privilege: @1).=Sie haben nicht die Erlaubnis, diesen Befehl auszuführen (fehlendes Privileg: @1). You don't have permission to run this command (missing privilege: @1).=Sie haben nicht die Erlaubnis, diesen Befehl auszuführen (fehlendes Privileg: @1).
Invalid time.=Ungültige Zeit. Invalid time (must be between 0 and 24000).=Ungültige Zeit (muss zwischen 0 und 24000 liegen).
Time of day changed.=Tageszeit geändert. Time of day changed.=Tageszeit geändert.
Invalid hour (must be between 0 and 23 inclusive).=Ungültige Stunde (muss zwischen 0 und 23 inklusive liegen). Invalid hour (must be between 0 and 23 inclusive).=Ungültige Stunde (muss zwischen 0 und 23 inklusive liegen).
Invalid minute (must be between 0 and 59 inclusive).=Ungültige Minute (muss zwischen 0 und 59 inklusive liegen). Invalid minute (must be between 0 and 59 inclusive).=Ungültige Minute (muss zwischen 0 und 59 inklusive liegen).
@ -187,12 +187,14 @@ You are already dead.=Sie sind schon tot.
@1 is already dead.=@1 ist bereits tot. @1 is already dead.=@1 ist bereits tot.
@1 has been killed.=@1 wurde getötet. @1 has been killed.=@1 wurde getötet.
Kill player or yourself=Einen Spieler oder Sie selbst töten Kill player or yourself=Einen Spieler oder Sie selbst töten
Invalid parameters (see /help @1).=Ungültige Parameter (siehe „/help @1“).
Too many arguments, try using just /help <command>=Zu viele Argumente. Probieren Sie es mit „/help <Befehl>“
Available commands: @1=Verfügbare Befehle: @1 Available commands: @1=Verfügbare Befehle: @1
Use '/help <cmd>' to get more information, or '/help all' to list everything.=„/help <Befehl>“ benutzen, um mehr Informationen zu erhalten, oder „/help all“, um alles aufzulisten. Use '/help <cmd>' to get more information, or '/help all' to list everything.=„/help <Befehl>“ benutzen, um mehr Informationen zu erhalten, oder „/help all“, um alles aufzulisten.
Available commands:=Verfügbare Befehle: Available commands:=Verfügbare Befehle:
Command not available: @1=Befehl nicht verfügbar: @1 Command not available: @1=Befehl nicht verfügbar: @1
[all | privs | <cmd>]=[all | privs | <Befehl>] [all | privs | <cmd>] [-t]=[all | privs | <Befehl>] [-t]
Get help for commands or list privileges=Hilfe für Befehle erhalten oder Privilegien auflisten Get help for commands or list privileges (-t: output in chat)=Hilfe für Befehle erhalten oder Privilegien auflisten (-t: Ausgabe im Chat)
Available privileges:=Verfügbare Privilegien: Available privileges:=Verfügbare Privilegien:
Command=Befehl Command=Befehl
Parameters=Parameter Parameters=Parameter
@ -230,7 +232,8 @@ Can use fly mode=Kann den Flugmodus benutzen
Can use fast mode=Kann den Schnellmodus benutzen Can use fast mode=Kann den Schnellmodus benutzen
Can fly through solid nodes using noclip mode=Kann durch feste Blöcke mit dem Geistmodus fliegen Can fly through solid nodes using noclip mode=Kann durch feste Blöcke mit dem Geistmodus fliegen
Can use the rollback functionality=Kann die Rollback-Funktionalität benutzen Can use the rollback functionality=Kann die Rollback-Funktionalität benutzen
Allows enabling various debug options that may affect gameplay=Erlaubt die Aktivierung diverser Debugoptionen, die das Spielgeschehen beeinflussen könnten Can view more debug info that might give a gameplay advantage=Kann zusätzliche Debuginformationen betrachten, welche einen spielerischen Vorteil geben könnten
Can enable wireframe=Kann Drahtmodell aktivieren
Unknown Item=Unbekannter Gegenstand Unknown Item=Unbekannter Gegenstand
Air=Luft Air=Luft
Ignore=Ignorieren Ignore=Ignorieren

View File

@ -139,7 +139,7 @@ This command was disabled by a mod or game.=Questo comando è stato disabilitato
Show or set time of day=Mostra o imposta l'orario della giornata Show or set time of day=Mostra o imposta l'orario della giornata
Current time is @1:@2.=Orario corrente: @1:@2. Current time is @1:@2.=Orario corrente: @1:@2.
You don't have permission to run this command (missing privilege: @1).=Non hai il permesso di eseguire questo comando (privilegio mancante: @1) You don't have permission to run this command (missing privilege: @1).=Non hai il permesso di eseguire questo comando (privilegio mancante: @1)
Invalid time.=Orario non valido. Invalid time (must be between 0 and 24000).=
Time of day changed.=Orario della giornata cambiato. Time of day changed.=Orario della giornata cambiato.
Invalid hour (must be between 0 and 23 inclusive).=Ora non valida (deve essere tra 0 e 23 inclusi) Invalid hour (must be between 0 and 23 inclusive).=Ora non valida (deve essere tra 0 e 23 inclusi)
Invalid minute (must be between 0 and 59 inclusive).=Minuto non valido (deve essere tra 0 e 59 inclusi) Invalid minute (must be between 0 and 59 inclusive).=Minuto non valido (deve essere tra 0 e 59 inclusi)
@ -187,12 +187,14 @@ You are already dead.=Sei già mortǝ.
@1 is already dead.=@1 è già mortǝ. @1 is already dead.=@1 è già mortǝ.
@1 has been killed.=@1 è stato uccisǝ. @1 has been killed.=@1 è stato uccisǝ.
Kill player or yourself=Uccide un giocatore o te stessǝ Kill player or yourself=Uccide un giocatore o te stessǝ
Invalid parameters (see /help @1).=
Too many arguments, try using just /help <command>=
Available commands: @1=Comandi disponibili: @1 Available commands: @1=Comandi disponibili: @1
Use '/help <cmd>' to get more information, or '/help all' to list everything.=Usa '/help <comando>' per ottenere più informazioni, o '/help all' per elencare tutti i comandi. Use '/help <cmd>' to get more information, or '/help all' to list everything.=Usa '/help <comando>' per ottenere più informazioni, o '/help all' per elencare tutti i comandi.
Available commands:=Comandi disponibili: Available commands:=Comandi disponibili:
Command not available: @1=Comando non disponibile: @1 Command not available: @1=Comando non disponibile: @1
[all | privs | <cmd>]=[all | privs | <comando>] [all | privs | <cmd>] [-t]=
Get help for commands or list privileges=Richiama la finestra d'aiuto dei comandi o dei privilegi Get help for commands or list privileges (-t: output in chat)=
Available privileges:=Privilegi disponibili: Available privileges:=Privilegi disponibili:
Command=Comando Command=Comando
Parameters=Parametri Parameters=Parametri
@ -230,7 +232,8 @@ Can use fly mode=Si può usare la modalità volo
Can use fast mode=Si può usare la modalità rapida Can use fast mode=Si può usare la modalità rapida
Can fly through solid nodes using noclip mode=Si può volare attraverso i nodi solidi con la modalità incorporea Can fly through solid nodes using noclip mode=Si può volare attraverso i nodi solidi con la modalità incorporea
Can use the rollback functionality=Si può usare la funzione di rollback Can use the rollback functionality=Si può usare la funzione di rollback
Allows enabling various debug options that may affect gameplay=Permette di abilitare varie opzioni di debug che potrebbero influenzare l'esperienza di gioco Can view more debug info that might give a gameplay advantage=
Can enable wireframe=
Unknown Item=Oggetto sconosciuto Unknown Item=Oggetto sconosciuto
Air=Aria Air=Aria
Ignore=Ignora Ignore=Ignora
@ -244,6 +247,10 @@ Profile saved to @1=
##### not used anymore ##### ##### not used anymore #####
Invalid time.=Orario non valido.
[all | privs | <cmd>]=[all | privs | <comando>]
Get help for commands or list privileges=Richiama la finestra d'aiuto dei comandi o dei privilegi
Allows enabling various debug options that may affect gameplay=Permette di abilitare varie opzioni di debug che potrebbero influenzare l'esperienza di gioco
[<delay_in_seconds> | -1] [reconnect] [<message>]=[<ritardo_in_secondi> | -1] [reconnect] [<messaggio>] [<delay_in_seconds> | -1] [reconnect] [<message>]=[<ritardo_in_secondi> | -1] [reconnect] [<messaggio>]
Shutdown server (-1 cancels a delayed shutdown)=Arresta il server (-1 annulla un arresto programmato) Shutdown server (-1 cancels a delayed shutdown)=Arresta il server (-1 annulla un arresto programmato)
<name> (<privilege> | all)=<nome> (<privilegio> | all) <name> (<privilege> | all)=<nome> (<privilegio> | all)

View File

@ -139,7 +139,7 @@ This command was disabled by a mod or game.=
Show or set time of day= Show or set time of day=
Current time is @1:@2.= Current time is @1:@2.=
You don't have permission to run this command (missing privilege: @1).= You don't have permission to run this command (missing privilege: @1).=
Invalid time.= Invalid time (must be between 0 and 24000).=
Time of day changed.= Time of day changed.=
Invalid hour (must be between 0 and 23 inclusive).= Invalid hour (must be between 0 and 23 inclusive).=
Invalid minute (must be between 0 and 59 inclusive).= Invalid minute (must be between 0 and 59 inclusive).=
@ -187,12 +187,14 @@ You are already dead.=
@1 is already dead.= @1 is already dead.=
@1 has been killed.= @1 has been killed.=
Kill player or yourself= Kill player or yourself=
Invalid parameters (see /help @1).=
Too many arguments, try using just /help <command>=
Available commands: @1= Available commands: @1=
Use '/help <cmd>' to get more information, or '/help all' to list everything.= Use '/help <cmd>' to get more information, or '/help all' to list everything.=
Available commands:= Available commands:=
Command not available: @1= Command not available: @1=
[all | privs | <cmd>]= [all | privs | <cmd>] [-t]=
Get help for commands or list privileges= Get help for commands or list privileges (-t: output in chat)=
Available privileges:= Available privileges:=
Command= Command=
Parameters= Parameters=
@ -230,7 +232,8 @@ Can use fly mode=
Can use fast mode= Can use fast mode=
Can fly through solid nodes using noclip mode= Can fly through solid nodes using noclip mode=
Can use the rollback functionality= Can use the rollback functionality=
Allows enabling various debug options that may affect gameplay= Can view more debug info that might give a gameplay advantage=
Can enable wireframe=
Unknown Item= Unknown Item=
Air= Air=
Ignore= Ignore=

View File

@ -119,31 +119,27 @@ function render_serverlist_row(spec)
return table.concat(details, ",") return table.concat(details, ",")
end end
---------------------------------------------------------------------------------
--------------------------------------------------------------------------------
os.tempfolder = function()
local temp = core.get_temp_path()
return temp .. DIR_DELIM .. "MT_" .. math.random(0, 10000)
end
os.tmpname = function() os.tmpname = function()
local path = os.tempfolder() error('do not use') -- instead use core.get_temp_path()
io.open(path, "w"):close()
return path
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function menu_render_worldlist() function menu_render_worldlist(show_gameid)
local retval = "" local retval = {}
local current_worldlist = menudata.worldlist:get_list() local current_worldlist = menudata.worldlist:get_list()
local row
for i, v in ipairs(current_worldlist) do for i, v in ipairs(current_worldlist) do
if retval ~= "" then retval = retval .. "," end row = v.name
retval = retval .. core.formspec_escape(v.name) .. if show_gameid == nil or show_gameid == true then
" \\[" .. core.formspec_escape(v.gameid) .. "\\]" row = row .. " [" .. v.gameid .. "]"
end
retval[#retval+1] = core.formspec_escape(row)
end end
return retval return table.concat(retval, ",")
end end
function menu_handle_key_up_down(fields, textlist, settingname) function menu_handle_key_up_down(fields, textlist, settingname)

View File

@ -163,10 +163,13 @@ local function get_formspec(data)
"button[8.95,0.125;2.5,0.5;btn_enable_all_mods;" .. "button[8.95,0.125;2.5,0.5;btn_enable_all_mods;" ..
fgettext("Enable all") .. "]" fgettext("Enable all") .. "]"
end end
local use_technical_names = core.settings:get_bool("show_technical_names")
return retval .. return retval ..
"tablecolumns[color;tree;text]" .. "tablecolumns[color;tree;text]" ..
"table[5.5,0.75;5.75,6;world_config_modlist;" .. "table[5.5,0.75;5.75,6;world_config_modlist;" ..
pkgmgr.render_packagelist(data.list) .. ";" .. data.selected_mod .."]" pkgmgr.render_packagelist(data.list, use_technical_names) .. ";" .. data.selected_mod .."]"
end end
local function handle_buttons(this, fields) local function handle_buttons(this, fields)
@ -205,14 +208,19 @@ local function handle_buttons(this, fields)
local mods = worldfile:to_table() local mods = worldfile:to_table()
local rawlist = this.data.list:get_raw_list() local rawlist = this.data.list:get_raw_list()
local was_set = {}
for i = 1, #rawlist do for i = 1, #rawlist do
local mod = rawlist[i] local mod = rawlist[i]
if not mod.is_modpack and if not mod.is_modpack and
not mod.is_game_content then not mod.is_game_content then
if modname_valid(mod.name) then if modname_valid(mod.name) then
worldfile:set("load_mod_" .. mod.name, if mod.enabled then
mod.enabled and "true" or "false") worldfile:set("load_mod_" .. mod.name, mod.virtual_path)
was_set[mod.name] = true
elseif not was_set[mod.name] then
worldfile:set("load_mod_" .. mod.name, "false")
end
elseif mod.enabled then elseif mod.enabled then
gamedata.errormessage = fgettext_ne("Failed to enable mo" .. gamedata.errormessage = fgettext_ne("Failed to enable mo" ..
"d \"$1\" as it contains disallowed characters. " .. "d \"$1\" as it contains disallowed characters. " ..
@ -256,12 +264,26 @@ local function handle_buttons(this, fields)
if fields.btn_enable_all_mods then if fields.btn_enable_all_mods then
local list = this.data.list:get_raw_list() local list = this.data.list:get_raw_list()
-- When multiple copies of a mod are installed, we need to avoid enabling multiple of them
-- at a time. So lets first collect all the enabled mods, and then use this to exclude
-- multiple enables.
local was_enabled = {}
for i = 1, #list do for i = 1, #list do
if not list[i].is_game_content if not list[i].is_game_content
and not list[i].is_modpack then and not list[i].is_modpack and list[i].enabled then
list[i].enabled = true was_enabled[list[i].name] = true
end end
end end
for i = 1, #list do
if not list[i].is_game_content and not list[i].is_modpack and
not was_enabled[list[i].name] then
list[i].enabled = true
was_enabled[list[i].name] = true
end
end
enabled_all = true enabled_all = true
return true return true
end end

View File

@ -25,7 +25,7 @@ end
-- Unordered preserves the original order of the ContentDB API, -- Unordered preserves the original order of the ContentDB API,
-- before the package list is ordered based on installed state. -- before the package list is ordered based on installed state.
local store = { packages = {}, packages_full = {}, packages_full_unordered = {} } local store = { packages = {}, packages_full = {}, packages_full_unordered = {}, aliases = {} }
local http = core.get_http_api() local http = core.get_http_api()
@ -62,9 +62,19 @@ local REASON_UPDATE = "update"
local REASON_DEPENDENCY = "dependency" local REASON_DEPENDENCY = "dependency"
-- encodes for use as URL parameter or path component
local function urlencode(str)
return str:gsub("[^%a%d()._~-]", function(char)
return string.format("%%%02X", string.byte(char))
end)
end
assert(urlencode("sample text?") == "sample%20text%3F")
local function get_download_url(package, reason) local function get_download_url(package, reason)
local base_url = core.settings:get("contentdb_url") local base_url = core.settings:get("contentdb_url")
local ret = base_url .. ("/packages/%s/%s/releases/%d/download/"):format(package.author, package.name, package.release) local ret = base_url .. ("/packages/%s/releases/%d/download/"):format(
package.url_part, package.release)
if reason then if reason then
ret = ret .. "?reason=" .. reason ret = ret .. "?reason=" .. reason
end end
@ -72,34 +82,52 @@ local function get_download_url(package, reason)
end end
local function download_package(param) local function download_and_extract(param)
if core.download_file(param.url, param.filename) then local package = param.package
local filename = core.get_temp_path(true)
if filename == "" or not core.download_file(param.url, filename) then
core.log("error", "Downloading " .. dump(param.url) .. " failed")
return { return {
filename = param.filename, msg = fgettext("Failed to download $1", package.name)
successful = true,
}
else
core.log("error", "downloading " .. dump(param.url) .. " failed")
return {
successful = false,
} }
end end
local tempfolder = core.get_temp_path()
if tempfolder ~= "" then
tempfolder = tempfolder .. DIR_DELIM .. "MT_" .. math.random(1, 1024000)
if not core.extract_zip(filename, tempfolder) then
tempfolder = nil
end
else
tempfolder = nil
end
os.remove(filename)
if not tempfolder then
return {
msg = fgettext("Install: Unsupported file type or broken archive"),
}
end
return {
path = tempfolder
}
end end
local function start_install(package, reason) local function start_install(package, reason)
local params = { local params = {
package = package, package = package,
url = get_download_url(package, reason), url = get_download_url(package, reason),
filename = os.tempfolder() .. "_MODNAME_" .. package.name .. ".zip",
} }
number_downloading = number_downloading + 1 number_downloading = number_downloading + 1
local function callback(result) local function callback(result)
if result.successful then if result.msg then
local path, msg = pkgmgr.install(package.type, gamedata.errormessage = result.msg
result.filename, package.name, else
package.path) local path, msg = pkgmgr.install_dir(package.type, result.path, package.name, package.path)
core.delete_dir(result.path)
if not path then if not path then
gamedata.errormessage = msg gamedata.errormessage = msg
else else
@ -137,9 +165,6 @@ local function start_install(package, reason)
conf:write() conf:write()
end end
end end
os.remove(result.filename)
else
gamedata.errormessage = fgettext("Failed to download $1", package.name)
end end
package.downloading = false package.downloading = false
@ -159,7 +184,7 @@ local function start_install(package, reason)
package.queued = false package.queued = false
package.downloading = true package.downloading = true
if not core.handle_async(download_package, params, callback) then if not core.handle_async(download_and_extract, params, callback) then
core.log("error", "ERROR: async event failed") core.log("error", "ERROR: async event failed")
gamedata.errormessage = fgettext("Failed to download $1", package.name) gamedata.errormessage = fgettext("Failed to download $1", package.name)
return return
@ -184,7 +209,7 @@ local function get_raw_dependencies(package)
local url_fmt = "/api/packages/%s/dependencies/?only_hard=1&protocol_version=%s&engine_version=%s" local url_fmt = "/api/packages/%s/dependencies/?only_hard=1&protocol_version=%s&engine_version=%s"
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 .. url_fmt:format(package.id, core.get_max_supp_proto(), version.string) local url = base_url .. url_fmt:format(package.url_part, core.get_max_supp_proto(), urlencode(version.string))
local response = http.fetch_sync({ url = url }) local response = http.fetch_sync({ url = url })
if not response.succeeded then if not response.succeeded then
@ -559,17 +584,16 @@ function store.load()
local base_url = core.settings:get("contentdb_url") local base_url = core.settings:get("contentdb_url")
local url = base_url .. local url = base_url ..
"/api/packages/?type=mod&type=game&type=txp&protocol_version=" .. "/api/packages/?type=mod&type=game&type=txp&protocol_version=" ..
core.get_max_supp_proto() .. "&engine_version=" .. version.string core.get_max_supp_proto() .. "&engine_version=" .. urlencode(version.string)
for _, item in pairs(core.settings:get("contentdb_flag_blacklist"):split(",")) do for _, item in pairs(core.settings:get("contentdb_flag_blacklist"):split(",")) do
item = item:trim() item = item:trim()
if item ~= "" then if item ~= "" then
url = url .. "&hide=" .. item url = url .. "&hide=" .. urlencode(item)
end end
end end
local timeout = tonumber(core.settings:get("curl_file_download_timeout")) local response = http.fetch_sync({ url = url })
local response = http.fetch_sync({ url = url, timeout = timeout })
if not response.succeeded then if not response.succeeded then
return return
end end
@ -579,12 +603,16 @@ function store.load()
for _, package in pairs(store.packages_full) do for _, package in pairs(store.packages_full) do
local name_len = #package.name local name_len = #package.name
-- This must match what store.update_paths() does!
package.id = package.author:lower() .. "/"
if package.type == "game" and name_len > 5 and package.name:sub(name_len - 4) == "_game" then if package.type == "game" and name_len > 5 and package.name:sub(name_len - 4) == "_game" then
package.id = package.author:lower() .. "/" .. package.name:sub(1, name_len - 5) package.id = package.id .. package.name:sub(1, name_len - 5)
else else
package.id = package.author:lower() .. "/" .. package.name package.id = package.id .. package.name
end end
package.url_part = urlencode(package.author) .. "/" .. urlencode(package.name)
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
@ -834,8 +862,7 @@ function store.get_formspec(dlgdata)
formspec[#formspec + 1] = "cdb_downloading.png;3;400;]" formspec[#formspec + 1] = "cdb_downloading.png;3;400;]"
elseif package.queued then elseif package.queued then
formspec[#formspec + 1] = left_base formspec[#formspec + 1] = left_base
formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir) formspec[#formspec + 1] = "cdb_queued.png;queued;]"
formspec[#formspec + 1] = "cdb_queued.png;queued]"
elseif not package.path then elseif not package.path then
local elem_name = "install_" .. i .. ";" local elem_name = "install_" .. i .. ";"
formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#71aa34]" formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#71aa34]"
@ -998,9 +1025,9 @@ function store.handle_submit(this, fields)
end end
if fields["view_" .. i] then if fields["view_" .. i] then
local url = ("%s/packages/%s/%s?protocol_version=%d"):format( local url = ("%s/packages/%s?protocol_version=%d"):format(
core.settings:get("contentdb_url"), core.settings:get("contentdb_url"), package.url_part,
package.author, package.name, core.get_max_supp_proto()) core.get_max_supp_proto())
core.open_url(url) core.open_url(url)
return true return true
end end

View File

@ -15,7 +15,8 @@
--with this program; if not, write to the Free Software Foundation, Inc., --with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local worldname = "" -- cf. tab_local, the gamebar already provides game selection so we hide the list from here
local hide_gamelist = PLATFORM ~= "Android"
local function table_to_flags(ftable) local function table_to_flags(ftable)
-- Convert e.g. { jungles = true, caves = false } to "jungles,nocaves" -- Convert e.g. { jungles = true, caves = false } to "jungles,nocaves"
@ -31,9 +32,8 @@ local function strflag(flags, flag)
return (flags[flag] == true) and "true" or "false" return (flags[flag] == true) and "true" or "false"
end end
local cb_caverns = { "caverns", fgettext("Caverns"), "caverns", local cb_caverns = { "caverns", fgettext("Caverns"),
fgettext("Very large caverns deep in the underground") } fgettext("Very large caverns deep in the underground") }
local tt_sea_rivers = fgettext("Sea level rivers")
local flag_checkboxes = { local flag_checkboxes = {
v5 = { v5 = {
@ -41,39 +41,38 @@ local flag_checkboxes = {
}, },
v7 = { v7 = {
cb_caverns, cb_caverns,
{ "ridges", fgettext("Rivers"), "ridges", tt_sea_rivers }, { "ridges", fgettext("Rivers"), fgettext("Sea level rivers") },
{ "mountains", fgettext("Mountains"), "mountains" }, { "mountains", fgettext("Mountains") },
{ "floatlands", fgettext("Floatlands (experimental)"), "floatlands", { "floatlands", fgettext("Floatlands (experimental)"),
fgettext("Floating landmasses in the sky") }, fgettext("Floating landmasses in the sky") },
}, },
carpathian = { carpathian = {
cb_caverns, cb_caverns,
{ "rivers", fgettext("Rivers"), "rivers", tt_sea_rivers }, { "rivers", fgettext("Rivers"), fgettext("Sea level rivers") },
}, },
valleys = { valleys = {
{ "altitude-chill", fgettext("Altitude chill"), "altitude_chill", { "altitude_chill", fgettext("Altitude chill"),
fgettext("Reduces heat with altitude") }, fgettext("Reduces heat with altitude") },
{ "altitude-dry", fgettext("Altitude dry"), "altitude_dry", { "altitude_dry", fgettext("Altitude dry"),
fgettext("Reduces humidity with altitude") }, fgettext("Reduces humidity with altitude") },
{ "humid-rivers", fgettext("Humid rivers"), "humid_rivers", { "humid_rivers", fgettext("Humid rivers"),
fgettext("Increases humidity around rivers") }, fgettext("Increases humidity around rivers") },
{ "vary-river-depth", fgettext("Vary river depth"), "vary_river_depth", { "vary_river_depth", fgettext("Vary river depth"),
fgettext("Low humidity and high heat causes shallow or dry rivers") }, fgettext("Low humidity and high heat causes shallow or dry rivers") },
}, },
flat = { flat = {
cb_caverns, cb_caverns,
{ "hills", fgettext("Hills"), "hills" }, { "hills", fgettext("Hills") },
{ "lakes", fgettext("Lakes"), "lakes" }, { "lakes", fgettext("Lakes") },
}, },
fractal = { fractal = {
{ "terrain", fgettext("Additional terrain"), "terrain", { "terrain", fgettext("Additional terrain"),
fgettext("Generate non-fractal terrain: Oceans and underground") }, fgettext("Generate non-fractal terrain: Oceans and underground") },
}, },
v6 = { v6 = {
{ "trees", fgettext("Trees and jungle grass"), "trees" }, { "trees", fgettext("Trees and jungle grass") },
{ "flat", fgettext("Flat terrain"), "flat" }, { "flat", fgettext("Flat terrain") },
{ "mudflow", fgettext("Mud flow"), "mudflow", { "mudflow", fgettext("Mud flow"), fgettext("Terrain surface erosion") },
fgettext("Terrain surface erosion") },
-- Biome settings are in mgv6_biomes below -- Biome settings are in mgv6_biomes below
}, },
} }
@ -105,38 +104,26 @@ local function create_world_formspec(dialogdata)
"button[4.75,2.5;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]" "button[4.75,2.5;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]"
end end
local current_mg = dialogdata.mg
local mapgens = core.get_mapgen_names() local mapgens = core.get_mapgen_names()
local current_seed = core.settings:get("fixed_map_seed") or ""
local current_mg = core.settings:get("mg_name")
local gameid = core.settings:get("menu_last_game") local gameid = core.settings:get("menu_last_game")
local flags = { local flags = dialogdata.flags
main = core.settings:get_flags("mg_flags"),
v5 = core.settings:get_flags("mgv5_spflags"),
v6 = core.settings:get_flags("mgv6_spflags"),
v7 = core.settings:get_flags("mgv7_spflags"),
fractal = core.settings:get_flags("mgfractal_spflags"),
carpathian = core.settings:get_flags("mgcarpathian_spflags"),
valleys = core.settings:get_flags("mgvalleys_spflags"),
flat = core.settings:get_flags("mgflat_spflags"),
}
local gameidx = 0 local game, gameidx = pkgmgr.find_by_gameid(gameid)
if gameid ~= nil then if game == nil and hide_gamelist then
local _ -- should never happen but just pick the first game
_, gameidx = pkgmgr.find_by_gameid(gameid) game = pkgmgr.get_game(1)
gameidx = 1
if gameidx == nil then core.settings:set("menu_last_game", game.id)
gameidx = 0 elseif game == nil then
end gameidx = 0
end end
local game_by_gameidx = core.get_game(gameidx)
local disallowed_mapgen_settings = {} local disallowed_mapgen_settings = {}
if game_by_gameidx ~= nil then if game ~= nil then
local gamepath = game_by_gameidx.path local gameconfig = Settings(game.path.."/game.conf")
local gameconfig = Settings(gamepath.."/game.conf")
local allowed_mapgens = (gameconfig:get("allowed_mapgens") or ""):split() local allowed_mapgens = (gameconfig:get("allowed_mapgens") or ""):split()
for key, value in pairs(allowed_mapgens) do for key, value in pairs(allowed_mapgens) do
@ -156,7 +143,7 @@ local function create_world_formspec(dialogdata)
end end
end end
if disallowed_mapgens then if #disallowed_mapgens > 0 then
for i = #mapgens, 1, -1 do for i = #mapgens, 1, -1 do
if table.indexof(disallowed_mapgens, mapgens[i]) > 0 then if table.indexof(disallowed_mapgens, mapgens[i]) > 0 then
table.remove(mapgens, i) table.remove(mapgens, i)
@ -172,23 +159,29 @@ local function create_world_formspec(dialogdata)
local mglist = "" local mglist = ""
local selindex local selindex
local i = 1 do -- build the list of mapgens
local first_mg local i = 1
for k,v in pairs(mapgens) do local first_mg
if not first_mg then for k, v in pairs(mapgens) do
first_mg = v if not first_mg then
first_mg = v
end
if current_mg == v then
selindex = i
end
i = i + 1
mglist = mglist .. core.formspec_escape(v) .. ","
end end
if current_mg == v then if not selindex then
selindex = i selindex = 1
current_mg = first_mg
end end
i = i + 1 mglist = mglist:sub(1, -2)
mglist = mglist .. v .. ","
end end
if not selindex then
selindex = 1 -- The logic of the flag element IDs is as follows:
current_mg = first_mg -- "flag_main_foo-bar-baz" controls dialogdata.flags["main"]["foo_bar_baz"]
end -- see the buttonhandler for the implementation of this
mglist = mglist:sub(1, -2)
local mg_main_flags = function(mapgen, y) local mg_main_flags = function(mapgen, y)
if mapgen == "singlenode" then if mapgen == "singlenode" then
@ -198,11 +191,11 @@ local function create_world_formspec(dialogdata)
return "", y return "", y
end end
local form = "checkbox[0," .. y .. ";flag_mg_caves;" .. local form = "checkbox[0," .. y .. ";flag_main_caves;" ..
fgettext("Caves") .. ";"..strflag(flags.main, "caves").."]" fgettext("Caves") .. ";"..strflag(flags.main, "caves").."]"
y = y + 0.5 y = y + 0.5
form = form .. "checkbox[0,"..y..";flag_mg_dungeons;" .. form = form .. "checkbox[0,"..y..";flag_main_dungeons;" ..
fgettext("Dungeons") .. ";"..strflag(flags.main, "dungeons").."]" fgettext("Dungeons") .. ";"..strflag(flags.main, "dungeons").."]"
y = y + 0.5 y = y + 0.5
@ -213,7 +206,7 @@ local function create_world_formspec(dialogdata)
else else
d_tt = fgettext("Structures appearing on the terrain, typically trees and plants") d_tt = fgettext("Structures appearing on the terrain, typically trees and plants")
end end
form = form .. "checkbox[0,"..y..";flag_mg_decorations;" .. form = form .. "checkbox[0,"..y..";flag_main_decorations;" ..
d_name .. ";" .. d_name .. ";" ..
strflag(flags.main, "decorations").."]" .. strflag(flags.main, "decorations").."]" ..
"tooltip[flag_mg_decorations;" .. "tooltip[flag_mg_decorations;" ..
@ -221,7 +214,7 @@ local function create_world_formspec(dialogdata)
"]" "]"
y = y + 0.5 y = y + 0.5
form = form .. "tooltip[flag_mg_caves;" .. form = form .. "tooltip[flag_main_caves;" ..
fgettext("Network of tunnels and caves") fgettext("Network of tunnels and caves")
.. "]" .. "]"
return form, y return form, y
@ -235,13 +228,13 @@ local function create_world_formspec(dialogdata)
return "", y return "", y
end end
local form = "" local form = ""
for _,tab in pairs(flag_checkboxes[mapgen]) do for _, tab in pairs(flag_checkboxes[mapgen]) do
local id = "flag_mg"..mapgen.."_"..tab[1] local id = "flag_"..mapgen.."_"..tab[1]:gsub("_", "-")
form = form .. ("checkbox[0,%f;%s;%s;%s]"): form = form .. ("checkbox[0,%f;%s;%s;%s]"):
format(y, id, tab[2], strflag(flags[mapgen], tab[3])) format(y, id, tab[2], strflag(flags[mapgen], tab[1]))
if tab[4] then if tab[3] then
form = form .. "tooltip["..id..";"..tab[4].."]" form = form .. "tooltip["..id..";"..tab[3].."]"
end end
y = y + 0.5 y = y + 0.5
end end
@ -277,16 +270,14 @@ local function create_world_formspec(dialogdata)
-- biomeblend -- biomeblend
y = y + 0.55 y = y + 0.55
form = form .. "checkbox[0,"..y..";flag_mgv6_biomeblend;" .. form = form .. "checkbox[0,"..y..";flag_v6_biomeblend;" ..
fgettext("Biome blending") .. ";"..strflag(flags.v6, "biomeblend").."]" .. fgettext("Biome blending") .. ";"..strflag(flags.v6, "biomeblend").."]" ..
"tooltip[flag_mgv6_biomeblend;" .. "tooltip[flag_v6_biomeblend;" ..
fgettext("Smooth transition between biomes") .. "]" fgettext("Smooth transition between biomes") .. "]"
return form, y return form, y
end end
current_seed = core.formspec_escape(current_seed)
local y_start = 0.0 local y_start = 0.0
local y = y_start local y = y_start
local str_flags, str_spflags local str_flags, str_spflags
@ -323,21 +314,32 @@ local function create_world_formspec(dialogdata)
"container[0,0]".. "container[0,0]"..
"field[0.3,0.6;6,0.5;te_world_name;" .. "field[0.3,0.6;6,0.5;te_world_name;" ..
fgettext("World name") .. fgettext("World name") ..
";" .. core.formspec_escape(worldname) .. "]" .. ";" .. core.formspec_escape(dialogdata.worldname) .. "]" ..
"set_focus[te_world_name;false]"
"field[0.3,1.7;6,0.5;te_seed;" .. if not disallowed_mapgen_settings["seed"] then
fgettext("Seed") ..
";".. current_seed .. "]" ..
retval = retval .. "field[0.3,1.7;6,0.5;te_seed;" ..
fgettext("Seed") ..
";".. core.formspec_escape(dialogdata.seed) .. "]"
end
retval = retval ..
"label[0,2;" .. fgettext("Mapgen") .. "]".. "label[0,2;" .. fgettext("Mapgen") .. "]"..
"dropdown[0,2.5;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]" .. "dropdown[0,2.5;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]"
"label[0,3.35;" .. fgettext("Game") .. "]".. if not hide_gamelist or devtest_only ~= "" then
"textlist[0,3.85;5.8,"..gamelist_height..";games;" .. retval = retval ..
pkgmgr.gamelist() .. ";" .. gameidx .. ";false]" .. "label[0,3.35;" .. fgettext("Game") .. "]"..
"container[0,4.5]" .. "textlist[0,3.85;5.8,"..gamelist_height..";games;" ..
devtest_only .. pkgmgr.gamelist() .. ";" .. gameidx .. ";false]" ..
"container_end[]" .. "container[0,4.5]" ..
devtest_only ..
"container_end[]"
end
retval = retval ..
"container_end[]" .. "container_end[]" ..
-- Right side -- Right side
@ -360,9 +362,20 @@ local function create_world_buttonhandler(this, fields)
fields["key_enter"] then fields["key_enter"] then
local worldname = fields["te_world_name"] local worldname = fields["te_world_name"]
local gameindex = core.get_textlist_index("games") local game, gameindex
if hide_gamelist then
game, gameindex = pkgmgr.find_by_gameid(core.settings:get("menu_last_game"))
else
gameindex = core.get_textlist_index("games")
game = pkgmgr.get_game(gameindex)
end
if gameindex ~= nil then local message
if game == nil then
message = fgettext("No game selected")
end
if message == nil then
-- For unnamed worlds use the generated name 'world<number>', -- For unnamed worlds use the generated name 'world<number>',
-- where the number increments: it is set to 1 larger than the largest -- where the number increments: it is set to 1 larger than the largest
-- generated name number found. -- generated name number found.
@ -377,36 +390,48 @@ local function create_world_buttonhandler(this, fields)
worldname = "world" .. worldnum_max + 1 worldname = "world" .. worldnum_max + 1
end end
core.settings:set("fixed_map_seed", fields["te_seed"]) if menudata.worldlist:uid_exists_raw(worldname) then
local message
if not menudata.worldlist:uid_exists_raw(worldname) then
core.settings:set("mg_name",fields["dd_mapgen"])
message = core.create_world(worldname,gameindex)
else
message = fgettext("A world named \"$1\" already exists", worldname) message = fgettext("A world named \"$1\" already exists", worldname)
end end
if message ~= nil then
gamedata.errormessage = message
else
core.settings:set("menu_last_game",pkgmgr.games[gameindex].id)
if this.data.update_worldlist_filter then
menudata.worldlist:set_filtercriteria(pkgmgr.games[gameindex].id)
mm_texture.update("singleplayer", pkgmgr.games[gameindex].id)
end
menudata.worldlist:refresh()
core.settings:set("mainmenu_last_selected_world",
menudata.worldlist:raw_index_by_uid(worldname))
end
else
gamedata.errormessage = fgettext("No game selected")
end end
if message == nil then
this.data.seed = fields["te_seed"] or ""
this.data.mg = fields["dd_mapgen"]
-- actual names as used by engine
local settings = {
fixed_map_seed = this.data.seed,
mg_name = this.data.mg,
mg_flags = table_to_flags(this.data.flags.main),
mgv5_spflags = table_to_flags(this.data.flags.v5),
mgv6_spflags = table_to_flags(this.data.flags.v6),
mgv7_spflags = table_to_flags(this.data.flags.v7),
mgfractal_spflags = table_to_flags(this.data.flags.fractal),
mgcarpathian_spflags = table_to_flags(this.data.flags.carpathian),
mgvalleys_spflags = table_to_flags(this.data.flags.valleys),
mgflat_spflags = table_to_flags(this.data.flags.flat),
}
message = core.create_world(worldname, gameindex, settings)
end
if message == nil then
core.settings:set("menu_last_game", game.id)
if this.data.update_worldlist_filter then
menudata.worldlist:set_filtercriteria(game.id)
end
menudata.worldlist:refresh()
core.settings:set("mainmenu_last_selected_world",
menudata.worldlist:raw_index_by_uid(worldname))
end
gamedata.errormessage = message
this:delete() this:delete()
return true return true
end end
worldname = fields.te_world_name this.data.worldname = fields["te_world_name"]
this.data.seed = fields["te_seed"] or ""
if fields["games"] then if fields["games"] then
local gameindex = core.get_textlist_index("games") local gameindex = core.get_textlist_index("games")
@ -417,22 +442,11 @@ local function create_world_buttonhandler(this, fields)
for k,v in pairs(fields) do for k,v in pairs(fields) do
local split = string.split(k, "_", nil, 3) local split = string.split(k, "_", nil, 3)
if split and split[1] == "flag" then if split and split[1] == "flag" then
local setting
if split[2] == "mg" then
setting = "mg_flags"
else
setting = split[2].."_spflags"
end
-- We replaced the underscore of flag names with a dash. -- We replaced the underscore of flag names with a dash.
local flag = string.gsub(split[3], "-", "_") local flag = string.gsub(split[3], "-", "_")
local ftable = core.settings:get_flags(setting) local ftable = this.data.flags[split[2]]
if v == "true" then assert(ftable)
ftable[flag] = true ftable[flag] = v == "true"
else
ftable[flag] = false
end
local flags = table_to_flags(ftable)
core.settings:set(setting, flags)
return true return true
end end
end end
@ -446,18 +460,16 @@ local function create_world_buttonhandler(this, fields)
local entry = core.formspec_escape(fields["mgv6_biomes"]) local entry = core.formspec_escape(fields["mgv6_biomes"])
for b=1, #mgv6_biomes do for b=1, #mgv6_biomes do
if entry == mgv6_biomes[b][1] then if entry == mgv6_biomes[b][1] then
local ftable = core.settings:get_flags("mgv6_spflags") local ftable = this.data.flags.v6
ftable.jungles = mgv6_biomes[b][2].jungles ftable.jungles = mgv6_biomes[b][2].jungles
ftable.snowbiomes = mgv6_biomes[b][2].snowbiomes ftable.snowbiomes = mgv6_biomes[b][2].snowbiomes
local flags = table_to_flags(ftable)
core.settings:set("mgv6_spflags", flags)
return true return true
end end
end end
end end
if fields["dd_mapgen"] then if fields["dd_mapgen"] then
core.settings:set("mg_name", fields["dd_mapgen"]) this.data.mg = fields["dd_mapgen"]
return true return true
end end
@ -466,12 +478,27 @@ end
function create_create_world_dlg(update_worldlistfilter) function create_create_world_dlg(update_worldlistfilter)
worldname = ""
local retval = dialog_create("sp_create_world", local retval = dialog_create("sp_create_world",
create_world_formspec, create_world_formspec,
create_world_buttonhandler, create_world_buttonhandler,
nil) nil)
retval.update_worldlist_filter = update_worldlistfilter retval.data = {
update_worldlist_filter = update_worldlistfilter,
worldname = "",
-- settings the world is created with:
seed = core.settings:get("fixed_map_seed") or "",
mg = core.settings:get("mg_name"),
flags = {
main = core.settings:get_flags("mg_flags"),
v5 = core.settings:get_flags("mgv5_spflags"),
v6 = core.settings:get_flags("mgv6_spflags"),
v7 = core.settings:get_flags("mgv7_spflags"),
fractal = core.settings:get_flags("mgfractal_spflags"),
carpathian = core.settings:get_flags("mgcarpathian_spflags"),
valleys = core.settings:get_flags("mgvalleys_spflags"),
flat = core.settings:get_flags("mgflat_spflags"),
}
}
return retval return retval
end end

View File

@ -378,7 +378,7 @@ local function parse_config_file(read_all, parse_mods)
-- Parse mods -- Parse mods
local mods_category_initialized = false local mods_category_initialized = false
local mods = {} local mods = {}
get_mods(core.get_modpath(), mods) get_mods(core.get_modpath(), "mods", mods)
for _, mod in ipairs(mods) do for _, mod in ipairs(mods) do
local path = mod.path .. DIR_DELIM .. FILENAME local path = mod.path .. DIR_DELIM .. FILENAME
local file = io.open(path, "r") local file = io.open(path, "r")
@ -395,6 +395,7 @@ local function parse_config_file(read_all, parse_mods)
table.insert(settings, { table.insert(settings, {
name = mod.name, name = mod.name,
readable_name = mod.title,
level = 1, level = 1,
type = "category", type = "category",
}) })
@ -408,7 +409,7 @@ local function parse_config_file(read_all, parse_mods)
-- Parse clientmods -- Parse clientmods
local clientmods_category_initialized = false local clientmods_category_initialized = false
local clientmods = {} local clientmods = {}
get_mods(core.get_clientmodpath(), clientmods) get_mods(core.get_clientmodpath(), "clientmods", clientmods)
for _, clientmod in ipairs(clientmods) do for _, clientmod in ipairs(clientmods) do
local path = clientmod.path .. DIR_DELIM .. FILENAME local path = clientmod.path .. DIR_DELIM .. FILENAME
local file = io.open(path, "r") local file = io.open(path, "r")
@ -527,44 +528,40 @@ end
local function get_current_np_group(setting) local function get_current_np_group(setting)
local value = core.settings:get_np_group(setting.name) local value = core.settings:get_np_group(setting.name)
local t = {}
if value == nil then if value == nil then
t = setting.values return setting.values
else
table.insert(t, value.offset)
table.insert(t, value.scale)
table.insert(t, value.spread.x)
table.insert(t, value.spread.y)
table.insert(t, value.spread.z)
table.insert(t, value.seed)
table.insert(t, value.octaves)
table.insert(t, value.persistence)
table.insert(t, value.lacunarity)
table.insert(t, value.flags)
end end
return t local p = "%g"
return {
p:format(value.offset),
p:format(value.scale),
p:format(value.spread.x),
p:format(value.spread.y),
p:format(value.spread.z),
p:format(value.seed),
p:format(value.octaves),
p:format(value.persistence),
p:format(value.lacunarity),
value.flags
}
end end
local function get_current_np_group_as_string(setting) local function get_current_np_group_as_string(setting)
local value = core.settings:get_np_group(setting.name) local value = core.settings:get_np_group(setting.name)
local t
if value == nil then if value == nil then
t = setting.default return setting.default
else
t = value.offset .. ", " ..
value.scale .. ", (" ..
value.spread.x .. ", " ..
value.spread.y .. ", " ..
value.spread.z .. "), " ..
value.seed .. ", " ..
value.octaves .. ", " ..
value.persistence .. ", " ..
value.lacunarity
if value.flags ~= "" then
t = t .. ", " .. value.flags
end
end end
return t return ("%g, %g, (%g, %g, %g), %g, %g, %g, %g"):format(
value.offset,
value.scale,
value.spread.x,
value.spread.y,
value.spread.z,
value.seed,
value.octaves,
value.persistence,
value.lacunarity
) .. (value.flags ~= "" and (", " .. value.flags) or "")
end end
local checkboxes = {} -- handle checkboxes events local checkboxes = {} -- handle checkboxes events
@ -697,7 +694,7 @@ local function create_change_setting_formspec(dialogdata)
elseif setting.type == "v3f" then elseif setting.type == "v3f" then
local val = get_current_value(setting) local val = get_current_value(setting)
local v3f = {} local v3f = {}
for line in val:gmatch("[+-]?[%d.-e]+") do -- All numeric characters for line in val:gmatch("[+-]?[%d.+-eE]+") do -- All numeric characters
table.insert(v3f, line) table.insert(v3f, line)
end end
@ -990,7 +987,7 @@ local function create_settings_formspec(tabview, _, tabdata)
local current_level = 0 local current_level = 0
for _, entry in ipairs(settings) do for _, entry in ipairs(settings) do
local name local name
if not core.settings:get_bool("main_menu_technical_settings") and entry.readable_name then if not core.settings:get_bool("show_technical_names") and entry.readable_name then
name = fgettext_ne(entry.readable_name) name = fgettext_ne(entry.readable_name)
else else
name = entry.name name = entry.name
@ -1031,7 +1028,7 @@ local function create_settings_formspec(tabview, _, tabdata)
"button[10,4.9;2,1;btn_edit;" .. fgettext("Edit") .. "]" .. "button[10,4.9;2,1;btn_edit;" .. fgettext("Edit") .. "]" ..
"button[7,4.9;3,1;btn_restore;" .. fgettext("Restore Default") .. "]" .. "button[7,4.9;3,1;btn_restore;" .. fgettext("Restore Default") .. "]" ..
"checkbox[0,4.3;cb_tech_settings;" .. fgettext("Show technical names") .. ";" "checkbox[0,4.3;cb_tech_settings;" .. fgettext("Show technical names") .. ";"
.. dump(core.settings:get_bool("main_menu_technical_settings")) .. "]" .. dump(core.settings:get_bool("show_technical_names")) .. "]"
return formspec return formspec
end end
@ -1114,7 +1111,7 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
end end
if fields["cb_tech_settings"] then if fields["cb_tech_settings"] then
core.settings:set("main_menu_technical_settings", fields["cb_tech_settings"]) core.settings:set("show_technical_names", fields["cb_tech_settings"])
core.settings:write() core.settings:write()
core.update_formspec(this:get_formspec()) core.update_formspec(this:get_formspec())
return true return true

View File

@ -16,23 +16,25 @@
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
mm_texture = {} mm_game_theme = {}
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function mm_texture.init() function mm_game_theme.init()
mm_texture.defaulttexturedir = core.get_texturepath() .. DIR_DELIM .. "base" .. mm_game_theme.defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" ..
DIR_DELIM .. "pack" .. DIR_DELIM DIR_DELIM .. "pack" .. DIR_DELIM
mm_texture.basetexturedir = mm_texture.defaulttexturedir mm_game_theme.basetexturedir = mm_game_theme.defaulttexturedir
mm_texture.texturepack = core.settings:get("texture_path") mm_game_theme.texturepack = core.settings:get("texture_path")
mm_texture.gameid = nil mm_game_theme.gameid = nil
mm_game_theme.music_handle = nil
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function mm_texture.update(tab,gamedetails) function mm_game_theme.update(tab,gamedetails)
if tab ~= "singleplayer" then if tab ~= "singleplayer" then
mm_texture.reset() mm_game_theme.reset()
return return
end end
@ -40,50 +42,54 @@ function mm_texture.update(tab,gamedetails)
return return
end end
mm_texture.update_game(gamedetails) mm_game_theme.update_game(gamedetails)
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function mm_texture.reset() function mm_game_theme.reset()
mm_texture.gameid = nil mm_game_theme.gameid = nil
local have_bg = false local have_bg = false
local have_overlay = mm_texture.set_generic("overlay") local have_overlay = mm_game_theme.set_generic("overlay")
if not have_overlay then if not have_overlay then
have_bg = mm_texture.set_generic("background") have_bg = mm_game_theme.set_generic("background")
end end
mm_texture.clear("header") mm_game_theme.clear("header")
mm_texture.clear("footer") mm_game_theme.clear("footer")
core.set_clouds(false) core.set_clouds(false)
mm_texture.set_generic("footer") mm_game_theme.set_generic("footer")
mm_texture.set_generic("header") mm_game_theme.set_generic("header")
if not have_bg then if not have_bg then
if core.settings:get_bool("menu_clouds") then if core.settings:get_bool("menu_clouds") then
core.set_clouds(true) core.set_clouds(true)
else else
mm_texture.set_dirt_bg() mm_game_theme.set_dirt_bg()
end end
end end
if mm_game_theme.music_handle ~= nil then
core.sound_stop(mm_game_theme.music_handle)
end
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function mm_texture.update_game(gamedetails) function mm_game_theme.update_game(gamedetails)
if mm_texture.gameid == gamedetails.id then if mm_game_theme.gameid == gamedetails.id then
return return
end end
local have_bg = false local have_bg = false
local have_overlay = mm_texture.set_game("overlay",gamedetails) local have_overlay = mm_game_theme.set_game("overlay",gamedetails)
if not have_overlay then if not have_overlay then
have_bg = mm_texture.set_game("background",gamedetails) have_bg = mm_game_theme.set_game("background",gamedetails)
end end
mm_texture.clear("header") mm_game_theme.clear("header")
mm_texture.clear("footer") mm_game_theme.clear("footer")
core.set_clouds(false) core.set_clouds(false)
if not have_bg then if not have_bg then
@ -91,34 +97,34 @@ function mm_texture.update_game(gamedetails)
if core.settings:get_bool("menu_clouds") then if core.settings:get_bool("menu_clouds") then
core.set_clouds(true) core.set_clouds(true)
else else
mm_texture.set_dirt_bg() mm_game_theme.set_dirt_bg()
end end
end end
mm_texture.set_game("footer",gamedetails) mm_game_theme.set_game("footer",gamedetails)
mm_texture.set_game("header",gamedetails) mm_game_theme.set_game("header",gamedetails)
mm_texture.gameid = gamedetails.id mm_game_theme.gameid = gamedetails.id
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function mm_texture.clear(identifier) function mm_game_theme.clear(identifier)
core.set_background(identifier,"") core.set_background(identifier,"")
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function mm_texture.set_generic(identifier) function mm_game_theme.set_generic(identifier)
--try texture pack first --try texture pack first
if mm_texture.texturepack ~= nil then if mm_game_theme.texturepack ~= nil then
local path = mm_texture.texturepack .. DIR_DELIM .."menu_" .. local path = mm_game_theme.texturepack .. DIR_DELIM .."menu_" ..
identifier .. ".png" identifier .. ".png"
if core.set_background(identifier,path) then if core.set_background(identifier,path) then
return true return true
end end
end end
if mm_texture.defaulttexturedir ~= nil then if mm_game_theme.defaulttexturedir ~= nil then
local path = mm_texture.defaulttexturedir .. DIR_DELIM .."menu_" .. local path = mm_game_theme.defaulttexturedir .. DIR_DELIM .."menu_" ..
identifier .. ".png" identifier .. ".png"
if core.set_background(identifier,path) then if core.set_background(identifier,path) then
return true return true
@ -129,14 +135,16 @@ function mm_texture.set_generic(identifier)
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function mm_texture.set_game(identifier, gamedetails) function mm_game_theme.set_game(identifier, gamedetails)
if gamedetails == nil then if gamedetails == nil then
return false return false
end end
if mm_texture.texturepack ~= nil then mm_game_theme.set_music(gamedetails)
local path = mm_texture.texturepack .. DIR_DELIM ..
if mm_game_theme.texturepack ~= nil then
local path = mm_game_theme.texturepack .. DIR_DELIM ..
gamedetails.id .. "_menu_" .. identifier .. ".png" gamedetails.id .. "_menu_" .. identifier .. ".png"
if core.set_background(identifier, path) then if core.set_background(identifier, path) then
return true return true
@ -171,9 +179,10 @@ function mm_texture.set_game(identifier, gamedetails)
return false return false
end end
function mm_texture.set_dirt_bg() --------------------------------------------------------------------------------
if mm_texture.texturepack ~= nil then function mm_game_theme.set_dirt_bg()
local path = mm_texture.texturepack .. DIR_DELIM .."default_dirt.png" if mm_game_theme.texturepack ~= nil then
local path = mm_game_theme.texturepack .. DIR_DELIM .."default_dirt.png"
if core.set_background("background", path, true, 128) then if core.set_background("background", path, true, 128) then
return true return true
end end
@ -183,3 +192,12 @@ function mm_texture.set_dirt_bg()
local minimalpath = defaulttexturedir .. "menu_bg.png" local minimalpath = defaulttexturedir .. "menu_bg.png"
core.set_background("background", minimalpath, true, 128) core.set_background("background", minimalpath, true, 128)
end end
--------------------------------------------------------------------------------
function mm_game_theme.set_music(gamedetails)
if mm_game_theme.music_handle ~= nil then
core.sound_stop(mm_game_theme.music_handle)
end
local music_path = gamedetails.path .. DIR_DELIM .. "menu" .. DIR_DELIM .. "theme"
mm_game_theme.music_handle = core.sound_play(music_path, true)
end

View File

@ -31,7 +31,7 @@ local group_format_template = [[
# octaves = %s, # octaves = %s,
# persistence = %s, # persistence = %s,
# lacunarity = %s, # lacunarity = %s,
# flags = %s # flags =%s
# } # }
]] ]]
@ -55,7 +55,11 @@ local function create_minetest_conf_example()
end end
if entry.comment ~= "" then if entry.comment ~= "" then
for _, comment_line in ipairs(entry.comment:split("\n", true)) do for _, comment_line in ipairs(entry.comment:split("\n", true)) do
insert(result, "# " .. comment_line .. "\n") if comment_line == "" then
insert(result, "#\n")
else
insert(result, "# " .. comment_line .. "\n")
end
end end
end end
insert(result, "# type: " .. entry.type) insert(result, "# type: " .. entry.type)
@ -73,10 +77,14 @@ local function create_minetest_conf_example()
end end
insert(result, "\n") insert(result, "\n")
if group_format == true then if group_format == true then
local flags = entry.values[10]
if flags ~= "" then
flags = " "..flags
end
insert(result, sprintf(group_format_template, entry.name, entry.values[1], insert(result, sprintf(group_format_template, entry.name, entry.values[1],
entry.values[2], entry.values[3], entry.values[4], entry.values[5], entry.values[2], entry.values[3], entry.values[4], entry.values[5],
entry.values[6], entry.values[7], entry.values[8], entry.values[9], entry.values[6], entry.values[7], entry.values[8], entry.values[9],
entry.values[10])) flags))
else else
local append local append
if entry.default ~= "" then if entry.default ~= "" then
@ -91,7 +99,7 @@ end
local translation_file_header = [[ local translation_file_header = [[
// This file is automatically generated // This file is automatically generated
// It conatins a bunch of fake gettext calls, to tell xgettext about the strings in config files // It contains a bunch of fake gettext calls, to tell xgettext about the strings in config files
// To update it, refer to the bottom of builtin/mainmenu/dlg_settings_advanced.lua // To update it, refer to the bottom of builtin/mainmenu/dlg_settings_advanced.lua
fake_function() {]] fake_function() {]]
@ -126,4 +134,3 @@ file = assert(io.open("src/settings_translation_file.cpp", "w"))
--file = assert(io.open("settings_translation_file.cpp", "w")) --file = assert(io.open("settings_translation_file.cpp", "w"))
file:write(create_translation_file()) file:write(create_translation_file())
file:close() file:close()

View File

@ -35,7 +35,7 @@ dofile(menupath .. DIR_DELIM .. "async_event.lua")
dofile(menupath .. DIR_DELIM .. "common.lua") dofile(menupath .. DIR_DELIM .. "common.lua")
dofile(menupath .. DIR_DELIM .. "pkgmgr.lua") dofile(menupath .. DIR_DELIM .. "pkgmgr.lua")
dofile(menupath .. DIR_DELIM .. "serverlistmgr.lua") dofile(menupath .. DIR_DELIM .. "serverlistmgr.lua")
dofile(menupath .. DIR_DELIM .. "textures.lua") dofile(menupath .. DIR_DELIM .. "game_theme.lua")
dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua") dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua")
dofile(menupath .. DIR_DELIM .. "dlg_settings_advanced.lua") dofile(menupath .. DIR_DELIM .. "dlg_settings_advanced.lua")
@ -87,7 +87,7 @@ local function init_globals()
core.settings:set("menu_last_game", default_game) core.settings:set("menu_last_game", default_game)
end end
mm_texture.init() mm_game_theme.init()
-- Create main tabview -- Create main tabview
local tv_main = tabview_create("maintab", {x = 12, y = 5.4}, {x = 0, y = 0}) local tv_main = tabview_create("maintab", {x = 12, y = 5.4}, {x = 0, y = 0})
@ -113,7 +113,7 @@ local function init_globals()
if tv_main.current_tab == "local" then if tv_main.current_tab == "local" then
local game = pkgmgr.find_by_gameid(core.settings:get("menu_last_game")) local game = pkgmgr.find_by_gameid(core.settings:get("menu_last_game"))
if game == nil then if game == nil then
mm_texture.reset() mm_game_theme.reset()
end end
end end
@ -121,8 +121,6 @@ local function init_globals()
tv_main:show() tv_main:show()
ui.update() ui.update()
core.sound_play("main_menu", true)
end end
init_globals() init_globals()

View File

@ -78,34 +78,35 @@ local function load_texture_packs(txtpath, retval)
for _, item in ipairs(list) do for _, item in ipairs(list) do
if item ~= "base" then if item ~= "base" then
local name = item
local path = txtpath .. DIR_DELIM .. item .. DIR_DELIM local path = txtpath .. DIR_DELIM .. item .. DIR_DELIM
if path == current_texture_path then
name = fgettext("$1 (Enabled)", name)
end
local conf = Settings(path .. "texture_pack.conf") local conf = Settings(path .. "texture_pack.conf")
local enabled = path == current_texture_path
local title = conf:get("title") or item
-- list_* is only used if non-nil, else the regular versions are used.
retval[#retval + 1] = { retval[#retval + 1] = {
name = item, name = item,
title = title,
list_name = enabled and fgettext("$1 (Enabled)", item) or nil,
list_title = enabled and fgettext("$1 (Enabled)", title) or nil,
author = conf:get("author"), author = conf:get("author"),
release = tonumber(conf:get("release")) or 0, release = tonumber(conf:get("release")) or 0,
list_name = name,
type = "txp", type = "txp",
path = path, path = path,
enabled = path == current_texture_path, enabled = enabled,
} }
end end
end end
end end
function get_mods(path,retval,modpack) function get_mods(path, virtual_path, retval, modpack)
local mods = core.get_dir_list(path, true) local mods = core.get_dir_list(path, true)
for _, name in ipairs(mods) do for _, name in ipairs(mods) do
if name:sub(1, 1) ~= "." then if name:sub(1, 1) ~= "." then
local prefix = path .. DIR_DELIM .. name local mod_path = path .. DIR_DELIM .. name
local mod_virtual_path = virtual_path .. "/" .. name
local toadd = { local toadd = {
dir_name = name, dir_name = name,
parent_dir = path, parent_dir = path,
@ -114,18 +115,18 @@ function get_mods(path,retval,modpack)
-- Get config file -- Get config file
local mod_conf local mod_conf
local modpack_conf = io.open(prefix .. DIR_DELIM .. "modpack.conf") local modpack_conf = io.open(mod_path .. DIR_DELIM .. "modpack.conf")
if modpack_conf then if modpack_conf then
toadd.is_modpack = true toadd.is_modpack = true
modpack_conf:close() modpack_conf:close()
mod_conf = Settings(prefix .. DIR_DELIM .. "modpack.conf"):to_table() mod_conf = Settings(mod_path .. DIR_DELIM .. "modpack.conf"):to_table()
if mod_conf.name then if mod_conf.name then
name = mod_conf.name name = mod_conf.name
toadd.is_name_explicit = true toadd.is_name_explicit = true
end end
else else
mod_conf = Settings(prefix .. DIR_DELIM .. "mod.conf"):to_table() mod_conf = Settings(mod_path .. DIR_DELIM .. "mod.conf"):to_table()
if mod_conf.name then if mod_conf.name then
name = mod_conf.name name = mod_conf.name
toadd.is_name_explicit = true toadd.is_name_explicit = true
@ -134,14 +135,16 @@ function get_mods(path,retval,modpack)
-- Read from config -- Read from config
toadd.name = name toadd.name = name
toadd.title = mod_conf.title
toadd.author = mod_conf.author toadd.author = mod_conf.author
toadd.release = tonumber(mod_conf.release) or 0 toadd.release = tonumber(mod_conf.release) or 0
toadd.path = prefix toadd.path = mod_path
toadd.virtual_path = mod_virtual_path
toadd.type = "mod" toadd.type = "mod"
-- Check modpack.txt -- Check modpack.txt
-- Note: modpack.conf is already checked above -- Note: modpack.conf is already checked above
local modpackfile = io.open(prefix .. DIR_DELIM .. "modpack.txt") local modpackfile = io.open(mod_path .. DIR_DELIM .. "modpack.txt")
if modpackfile then if modpackfile then
modpackfile:close() modpackfile:close()
toadd.is_modpack = true toadd.is_modpack = true
@ -153,7 +156,7 @@ function get_mods(path,retval,modpack)
elseif toadd.is_modpack then elseif toadd.is_modpack then
toadd.type = "modpack" toadd.type = "modpack"
toadd.is_modpack = true toadd.is_modpack = true
get_mods(prefix, retval, name) get_mods(mod_path, mod_virtual_path, retval, name)
end end
end end
end end
@ -181,21 +184,6 @@ function pkgmgr.get_texture_packs()
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function pkgmgr.extract(modfile)
if modfile.type == "zip" then
local tempfolder = os.tempfolder()
if tempfolder ~= nil and
tempfolder ~= "" then
core.create_dir(tempfolder)
if core.extract_zip(modfile.name,tempfolder) then
return tempfolder
end
end
end
return nil
end
function pkgmgr.get_folder_type(path) function pkgmgr.get_folder_type(path)
local testfile = io.open(path .. DIR_DELIM .. "init.lua","r") local testfile = io.open(path .. DIR_DELIM .. "init.lua","r")
if testfile ~= nil then if testfile ~= nil then
@ -349,7 +337,7 @@ function pkgmgr.identify_modname(modpath,filename)
return nil return nil
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function pkgmgr.render_packagelist(render_list) function pkgmgr.render_packagelist(render_list, use_technical_names)
if not render_list then if not render_list then
if not pkgmgr.global_mods then if not pkgmgr.global_mods then
pkgmgr.refresh_globals() pkgmgr.refresh_globals()
@ -385,7 +373,12 @@ function pkgmgr.render_packagelist(render_list)
else else
retval[#retval + 1] = "0" retval[#retval + 1] = "0"
end end
retval[#retval + 1] = core.formspec_escape(v.list_name or v.name)
if use_technical_names then
retval[#retval + 1] = core.formspec_escape(v.list_name or v.name)
else
retval[#retval + 1] = core.formspec_escape(v.list_title or v.list_name or v.title or v.name)
end
end end
return table.concat(retval, ",") return table.concat(retval, ",")
@ -412,6 +405,14 @@ function pkgmgr.is_modpack_entirely_enabled(data, name)
return true return true
end end
local function disable_all_by_name(list, name, except)
for i=1, #list do
if list[i].name == name and list[i] ~= except then
list[i].enabled = false
end
end
end
---------- toggles or en/disables a mod or modpack and its dependencies -------- ---------- toggles or en/disables a mod or modpack and its dependencies --------
local function toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mod) local function toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mod)
if not mod.is_modpack then if not mod.is_modpack then
@ -420,13 +421,16 @@ local function toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mo
toset = not mod.enabled toset = not mod.enabled
end end
if mod.enabled ~= toset then if mod.enabled ~= toset then
mod.enabled = toset
toggled_mods[#toggled_mods+1] = mod.name toggled_mods[#toggled_mods+1] = mod.name
end end
if toset then if toset then
-- Mark this mod for recursive dependency traversal -- Mark this mod for recursive dependency traversal
enabled_mods[mod.name] = true enabled_mods[mod.name] = true
-- Disable other mods with the same name
disable_all_by_name(list, mod.name, mod)
end end
mod.enabled = toset
else else
-- Toggle or en/disable every mod in the modpack, -- Toggle or en/disable every mod in the modpack,
-- interleaved unsupported -- interleaved unsupported
@ -451,7 +455,7 @@ function pkgmgr.enable_mod(this, toset)
local enabled_mods = {} local enabled_mods = {}
toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mod) toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mod)
if not toset then if next(enabled_mods) == nil then
-- Mod(s) were disabled, so no dependencies need to be enabled -- Mod(s) were disabled, so no dependencies need to be enabled
table.sort(toggled_mods) table.sort(toggled_mods)
core.log("info", "Following mods were disabled: " .. core.log("info", "Following mods were disabled: " ..
@ -461,11 +465,16 @@ function pkgmgr.enable_mod(this, toset)
-- Enable mods' depends after activation -- Enable mods' depends after activation
-- Make a list of mod ids indexed by their names -- Make a list of mod ids indexed by their names. Among mods with the
-- same name, enabled mods take precedence, after which game mods take
-- precedence, being last in the mod list.
local mod_ids = {} local mod_ids = {}
for id, mod2 in pairs(list) do for id, mod2 in pairs(list) do
if mod2.type == "mod" and not mod2.is_modpack then if mod2.type == "mod" and not mod2.is_modpack then
mod_ids[mod2.name] = id local prev_id = mod_ids[mod2.name]
if not prev_id or not list[prev_id].enabled then
mod_ids[mod2.name] = id
end
end end
end end
@ -482,6 +491,7 @@ function pkgmgr.enable_mod(this, toset)
end end
end end
end end
-- If sp is 0, every dependency is already activated -- If sp is 0, every dependency is already activated
while sp > 0 do while sp > 0 do
local name = to_enable[sp] local name = to_enable[sp]
@ -494,14 +504,14 @@ function pkgmgr.enable_mod(this, toset)
core.log("warning", "Mod dependency \"" .. name .. core.log("warning", "Mod dependency \"" .. name ..
"\" not found!") "\" not found!")
else else
if mod_to_enable.enabled == false then if not mod_to_enable.enabled then
mod_to_enable.enabled = true mod_to_enable.enabled = true
toggled_mods[#toggled_mods+1] = mod_to_enable.name toggled_mods[#toggled_mods+1] = mod_to_enable.name
end end
-- Push the dependencies of the dependency onto the stack -- Push the dependencies of the dependency onto the stack
local depends = pkgmgr.get_dependencies(mod_to_enable.path) local depends = pkgmgr.get_dependencies(mod_to_enable.path)
for i = 1, #depends do for i = 1, #depends do
if not enabled_mods[name] then if not enabled_mods[depends[i]] then
sp = sp+1 sp = sp+1
to_enable[sp] = depends[i] to_enable[sp] = depends[i]
end end
@ -561,11 +571,10 @@ function pkgmgr.install_dir(type, path, basename, targetpath)
local from = basefolder and basefolder.path or path local from = basefolder and basefolder.path or path
if targetpath then if targetpath then
core.delete_dir(targetpath) core.delete_dir(targetpath)
core.create_dir(targetpath)
else else
targetpath = core.get_texturepath() .. DIR_DELIM .. basename targetpath = core.get_texturepath() .. DIR_DELIM .. basename
end end
if not core.copy_dir(from, targetpath) then if not core.copy_dir(from, targetpath, false) then
return nil, return nil,
fgettext("Failed to install $1 to $2", basename, targetpath) fgettext("Failed to install $1 to $2", basename, targetpath)
end end
@ -586,7 +595,6 @@ function pkgmgr.install_dir(type, path, basename, targetpath)
-- Get destination name for modpack -- Get destination name for modpack
if targetpath then if targetpath then
core.delete_dir(targetpath) core.delete_dir(targetpath)
core.create_dir(targetpath)
else else
local clean_path = nil local clean_path = nil
if basename ~= nil then if basename ~= nil then
@ -610,7 +618,6 @@ function pkgmgr.install_dir(type, path, basename, targetpath)
if targetpath then if targetpath then
core.delete_dir(targetpath) core.delete_dir(targetpath)
core.create_dir(targetpath)
else else
local targetfolder = basename local targetfolder = basename
if targetfolder == nil then if targetfolder == nil then
@ -636,14 +643,13 @@ function pkgmgr.install_dir(type, path, basename, targetpath)
if targetpath then if targetpath then
core.delete_dir(targetpath) core.delete_dir(targetpath)
core.create_dir(targetpath)
else else
targetpath = core.get_gamepath() .. DIR_DELIM .. basename targetpath = core.get_gamepath() .. DIR_DELIM .. basename
end end
end end
-- Copy it -- Copy it
if not core.copy_dir(basefolder.path, targetpath) then if not core.copy_dir(basefolder.path, targetpath, false) then
return nil, return nil,
fgettext("Failed to install $1 to $2", basename, targetpath) fgettext("Failed to install $1 to $2", basename, targetpath)
end end
@ -657,23 +663,6 @@ function pkgmgr.install_dir(type, path, basename, targetpath)
return targetpath, nil return targetpath, nil
end end
--------------------------------------------------------------------------------
function pkgmgr.install(type, modfilename, basename, dest)
local archive_info = pkgmgr.identify_filetype(modfilename)
local path = pkgmgr.extract(archive_info)
if path == nil then
return nil,
fgettext("Install: file: \"$1\"", archive_info.name) .. "\n" ..
fgettext("Install: Unsupported file type \"$1\" or broken archive",
archive_info.type)
end
local targetpath, msg = pkgmgr.install_dir(type, path, basename, dest)
core.delete_dir(path)
return targetpath, msg
end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function pkgmgr.prepareclientmodlist(data) function pkgmgr.prepareclientmodlist(data)
local retval = {} local retval = {}
@ -683,9 +672,8 @@ function pkgmgr.prepareclientmodlist(data)
--read clientmods --read clientmods
local modpath = core.get_clientmodpath() local modpath = core.get_clientmodpath()
if modpath ~= nil and if modpath ~= nil and modpath ~= "" then
modpath ~= "" then get_mods(modpath, "clientmods", clientmods)
get_mods(modpath,clientmods)
end end
for i=1,#clientmods,1 do for i=1,#clientmods,1 do
@ -730,16 +718,15 @@ function pkgmgr.preparemodlist(data)
local game_mods = {} local game_mods = {}
--read global mods --read global mods
local modpath = core.get_modpath() local modpaths = core.get_modpaths()
for key, modpath in pairs(modpaths) do
if modpath ~= nil and get_mods(modpath, key, global_mods)
modpath ~= "" then
get_mods(modpath,global_mods)
end end
for i=1,#global_mods,1 do for i=1,#global_mods,1 do
global_mods[i].type = "mod" global_mods[i].type = "mod"
global_mods[i].loc = "global" global_mods[i].loc = "global"
global_mods[i].enabled = false
retval[#retval + 1] = global_mods[i] retval[#retval + 1] = global_mods[i]
end end
@ -773,22 +760,37 @@ function pkgmgr.preparemodlist(data)
DIR_DELIM .. "world.mt" DIR_DELIM .. "world.mt"
local worldfile = Settings(filename) local worldfile = Settings(filename)
for key, value in pairs(worldfile:to_table()) do
for key,value in pairs(worldfile:to_table()) do
if key:sub(1, 9) == "load_mod_" then if key:sub(1, 9) == "load_mod_" then
key = key:sub(10) key = key:sub(10)
local element = nil local mod_found = false
for i=1,#retval,1 do
local fallback_found = false
local fallback_mod = nil
for i=1, #retval do
if retval[i].name == key and if retval[i].name == key and
not retval[i].is_modpack then not retval[i].is_modpack then
element = retval[i] if core.is_yes(value) or retval[i].virtual_path == value then
break retval[i].enabled = true
mod_found = true
break
elseif fallback_found then
-- Only allow fallback if only one mod matches
fallback_mod = nil
else
fallback_found = true
fallback_mod = retval[i]
end
end end
end end
if element ~= nil then
element.enabled = value ~= "false" and value ~= "nil" and value if not mod_found then
else if fallback_mod and value:find("/") then
core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found") fallback_mod.enabled = true
else
core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
end
end end
end end
end end
@ -871,45 +873,6 @@ function pkgmgr.refresh_globals()
pkgmgr.clientmods:set_sortmode("alphabetic") pkgmgr.clientmods:set_sortmode("alphabetic")
end end
--------------------------------------------------------------------------------
function pkgmgr.identify_filetype(name)
if name:sub(-3):lower() == "zip" then
return {
name = name,
type = "zip"
}
end
if name:sub(-6):lower() == "tar.gz" or
name:sub(-3):lower() == "tgz"then
return {
name = name,
type = "tgz"
}
end
if name:sub(-6):lower() == "tar.bz2" then
return {
name = name,
type = "tbz"
}
end
if name:sub(-2):lower() == "7z" then
return {
name = name,
type = "7z"
}
end
return {
name = name,
type = "ukn"
}
end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function pkgmgr.find_by_gameid(gameid) function pkgmgr.find_by_gameid(gameid)
for i=1,#pkgmgr.games,1 do for i=1,#pkgmgr.games,1 do
@ -925,7 +888,7 @@ function pkgmgr.get_game_mods(gamespec, retval)
if gamespec ~= nil and if gamespec ~= nil and
gamespec.gamemods_path ~= nil and gamespec.gamemods_path ~= nil and
gamespec.gamemods_path ~= "" then gamespec.gamemods_path ~= "" then
get_mods(gamespec.gamemods_path, retval) get_mods(gamespec.gamemods_path, ("games/%s/mods"):format(gamespec.id), retval)
end end
end end

View File

@ -38,32 +38,35 @@ local core_developers = {
"Lars Hofhansl <larsh@apache.org>", "Lars Hofhansl <larsh@apache.org>",
"Pierre-Yves Rollo <dev@pyrollo.com>", "Pierre-Yves Rollo <dev@pyrollo.com>",
"v-rob <robinsonvincent89@gmail.com>", "v-rob <robinsonvincent89@gmail.com>",
"hecks",
"Hugues Ross <hugues.ross@gmail.com>",
"Dmitry Kostenko (x2048) <codeforsmile@gmail.com>",
} }
-- 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
local active_contributors = { local active_contributors = {
"Wuzzy [devtest game, visual corrections]", "Wuzzy [I18n for builtin, liquid features, fixes]",
"Zughy [Visual improvements, various fixes]", "Zughy [Various features and fixes]",
"Maksim (MoNTE48) [Android]",
"numzero [Graphics and rendering]", "numzero [Graphics and rendering]",
"appgurueu [Various internal fixes]", "Desour [Internal fixes, Clipboard on X11]",
"Desour [Formspec and vector API changes]", "Lars Müller [Various internal fixes]",
"HybridDog [Rendering fixes and documentation]", "JosiahWI [CMake, cleanups and fixes]",
"Hugues Ross [Graphics-related improvements]", "HybridDog [builtin, documentation]",
"ANAND (ClobberXD) [Mouse buttons rebinding]", "Jude Melton-Houghton [Database implementation]",
"luk3yx [Fixes]", "savilli [Fixes]",
"hecks [Audiovisuals, Lua API]", "Liso [Shadow Mapping]",
"LoneWolfHT [Object crosshair, documentation fixes]", "MoNTE48 [Build fix]",
"Lejo [Server-related improvements]", "Jean-Patrick Guerrero (kilbith) [Fixes]",
"EvidenceB [Compass HUD element]", "ROllerozxa [Code cleanups]",
"Paul Ouellette (pauloue) [Lua API, documentation]", "Lejo [bitop library integration]",
"TheTermos [Collision detection, physics]", "LoneWolfHT [Build fixes]",
"NeroBurner [Joystick]",
"Elias Fleckenstein [Internal fixes]",
"David CARLIER [Unix & Haiku build fixes]", "David CARLIER [Unix & Haiku build fixes]",
"dcbrwn [Object shading]", "pecksin [Clickable web links]",
"Elias Fleckenstein [API features/fixes]", "srfqi [Android & rendering fixes]",
"Jean-Patrick Guerrero (kilbith) [model element, visual fixes]", "EvidenceB [Formspec]",
"k.h.lai [Memory leak fixes, documentation]",
} }
local previous_core_developers = { local previous_core_developers = {
@ -80,6 +83,7 @@ local previous_core_developers = {
"Zeno", "Zeno",
"ShadowNinja <shadowninja@minetest.net>", "ShadowNinja <shadowninja@minetest.net>",
"Auke Kok (sofar) <sofar@foo-projects.org>", "Auke Kok (sofar) <sofar@foo-projects.org>",
"Aaron Suen <warr1024@gmail.com>",
} }
local previous_contributors = { local previous_contributors = {
@ -90,10 +94,10 @@ local previous_contributors = {
"MirceaKitsune <mirceakitsune@gmail.com>", "MirceaKitsune <mirceakitsune@gmail.com>",
"Constantin Wenger (SpeedProg)", "Constantin Wenger (SpeedProg)",
"Ciaran Gultnieks (CiaranG)", "Ciaran Gultnieks (CiaranG)",
"stujones11 [Android UX improvements]", "Paul Ouellette (pauloue)",
"Rogier <rogier777@gmail.com> [Fixes]", "stujones11",
"Gregory Currie (gregorycu) [optimisation]", "Rogier <rogier777@gmail.com>",
"srifqi [Fixes]", "Gregory Currie (gregorycu)",
"JacobF", "JacobF",
"Jeija <jeija@mesecons.net> [HTTP, particles]", "Jeija <jeija@mesecons.net> [HTTP, particles]",
} }

View File

@ -88,12 +88,14 @@ local function get_formspec(tabview, name, tabdata)
tabdata.selected_pkg = 1 tabdata.selected_pkg = 1
end end
local use_technical_names = core.settings:get_bool("show_technical_names")
local retval = local retval =
"label[0.05,-0.25;".. fgettext("Installed Packages:") .. "]" .. "label[0.05,-0.25;".. fgettext("Installed Packages:") .. "]" ..
"tablecolumns[color;tree;text]" .. "tablecolumns[color;tree;text]" ..
"table[0,0.25;5.1,4.3;pkglist;" .. "table[0,0.25;5.1,4.3;pkglist;" ..
pkgmgr.render_packagelist(packages) .. pkgmgr.render_packagelist(packages, use_technical_names) ..
";" .. tabdata.selected_pkg .. "]" .. ";" .. tabdata.selected_pkg .. "]" ..
"button[0,4.85;5.25,0.5;btn_contentdb;".. fgettext("Browse online content") .. "]" "button[0,4.85;5.25,0.5;btn_contentdb;".. fgettext("Browse online content") .. "]"
@ -124,9 +126,17 @@ local function get_formspec(tabview, name, tabdata)
desc = info.description desc = info.description
end end
local title_and_name
if selected_pkg.type == "game" then
title_and_name = selected_pkg.name
else
title_and_name = (selected_pkg.title or selected_pkg.name) .. "\n" ..
core.colorize("#BFBFBF", selected_pkg.name)
end
retval = retval .. retval = retval ..
"image[5.5,0;3,2;" .. core.formspec_escape(modscreenshot) .. "]" .. "image[5.5,0;3,2;" .. core.formspec_escape(modscreenshot) .. "]" ..
"label[8.25,0.6;" .. core.formspec_escape(selected_pkg.name) .. "]" .. "label[8.25,0.6;" .. core.formspec_escape(title_and_name) .. "]" ..
"box[5.5,2.2;6.15,2.35;#000]" "box[5.5,2.2;6.15,2.35;#000]"
if selected_pkg.type == "mod" then if selected_pkg.type == "mod" then
@ -214,6 +224,9 @@ local function handle_doubleclick(pkg, pkg_name)
core.settings:set("texture_path", pkg.path) core.settings:set("texture_path", pkg.path)
end end
packages = nil packages = nil
mm_game_theme.init()
mm_game_theme.reset()
elseif pkg.is_clientside then elseif pkg.is_clientside then
pkgmgr.enable_mod({data = {list = packages, selected_mod = pkg_name}}) pkgmgr.enable_mod({data = {list = packages, selected_mod = pkg_name}})
packages = nil packages = nil
@ -276,17 +289,17 @@ local function handle_buttons(tabview, fields, tabname, tabdata)
return true return true
end end
if fields.btn_mod_mgr_use_txp then if fields.btn_mod_mgr_use_txp or fields.btn_mod_mgr_disable_txp then
local txp = packages:get_list()[tabdata.selected_pkg] local txp_path = ""
core.settings:set("texture_path", txp.path) if fields.btn_mod_mgr_use_txp then
packages = nil txp_path = packages:get_list()[tabdata.selected_pkg].path
return true end
end
core.settings:set("texture_path", txp_path)
if fields.btn_mod_mgr_disable_txp then
core.settings:set("texture_path", "")
packages = nil packages = nil
mm_game_theme.init()
mm_game_theme.reset()
return true return true
end end

View File

@ -33,10 +33,30 @@ if enable_gamebar then
return game return game
end end
-- Apply menu changes from given game
function apply_game(game)
core.set_topleft_text(game.name)
core.settings:set("menu_last_game", game.id)
menudata.worldlist:set_filtercriteria(game.id)
mm_game_theme.update("singleplayer", game) -- this refreshes the formspec
local index = filterlist.get_current_index(menudata.worldlist,
tonumber(core.settings:get("mainmenu_last_selected_world")))
if not index or index < 1 then
local selected = core.get_textlist_index("sp_worlds")
if selected ~= nil and selected < #menudata.worldlist:get_list() then
index = selected
else
index = #menudata.worldlist:get_list()
end
end
menu_worldmt_legacy(index)
end
function singleplayer_refresh_gamebar() function singleplayer_refresh_gamebar()
local old_bar = ui.find_by_name("game_button_bar") local old_bar = ui.find_by_name("game_button_bar")
if old_bar ~= nil then if old_bar ~= nil then
old_bar:delete() old_bar:delete()
end end
@ -51,26 +71,10 @@ if enable_gamebar then
return true return true
end end
for key,value in pairs(fields) do for _, game in ipairs(pkgmgr.games) do
for j=1,#pkgmgr.games,1 do if fields["game_btnbar_" .. game.id] then
if ("game_btnbar_" .. pkgmgr.games[j].id == key) then apply_game(game)
mm_texture.update("singleplayer", pkgmgr.games[j]) return true
core.set_topleft_text(pkgmgr.games[j].name)
core.settings:set("menu_last_game",pkgmgr.games[j].id)
menudata.worldlist:set_filtercriteria(pkgmgr.games[j].id)
local index = filterlist.get_current_index(menudata.worldlist,
tonumber(core.settings:get("mainmenu_last_selected_world")))
if not index or index < 1 then
local selected = core.get_textlist_index("sp_worlds")
if selected ~= nil and selected < #menudata.worldlist:get_list() then
index = selected
else
index = #menudata.worldlist:get_list()
end
end
menu_worldmt_legacy(index)
return true
end
end end
end end
end end
@ -79,25 +83,22 @@ if enable_gamebar then
game_buttonbar_button_handler, game_buttonbar_button_handler,
{x=-0.3,y=5.9}, "horizontal", {x=12.4,y=1.15}) {x=-0.3,y=5.9}, "horizontal", {x=12.4,y=1.15})
for i=1,#pkgmgr.games,1 do for _, game in ipairs(pkgmgr.games) do
local btn_name = "game_btnbar_" .. pkgmgr.games[i].id local btn_name = "game_btnbar_" .. game.id
local image = nil local image = nil
local text = nil local text = nil
local tooltip = core.formspec_escape(pkgmgr.games[i].name) local tooltip = core.formspec_escape(game.name)
if pkgmgr.games[i].menuicon_path ~= nil and if (game.menuicon_path or "") ~= "" then
pkgmgr.games[i].menuicon_path ~= "" then image = core.formspec_escape(game.menuicon_path)
image = core.formspec_escape(pkgmgr.games[i].menuicon_path)
else else
local part1 = game.id:sub(1,5)
local part1 = pkgmgr.games[i].id:sub(1,5) local part2 = game.id:sub(6,10)
local part2 = pkgmgr.games[i].id:sub(6,10) local part3 = game.id:sub(11)
local part3 = pkgmgr.games[i].id:sub(11)
text = part1 .. "\n" .. part2 text = part1 .. "\n" .. part2
if part3 ~= nil and if part3 ~= "" then
part3 ~= "" then
text = text .. "\n" .. part3 text = text .. "\n" .. part3
end end
end end
@ -147,8 +148,12 @@ local function get_formspec(tabview, name, tabdata)
tonumber(core.settings:get("mainmenu_last_selected_world"))) tonumber(core.settings:get("mainmenu_last_selected_world")))
local list = menudata.worldlist:get_list() local list = menudata.worldlist:get_list()
local world = list and index and list[index] local world = list and index and list[index]
local gameid = world and world.gameid local game
local game = gameid and pkgmgr.find_by_gameid(gameid) if world then
game = pkgmgr.find_by_gameid(world.gameid)
else
game = current_game()
end
local disabled_settings = get_disabled_settings(game) local disabled_settings = get_disabled_settings(game)
local creative, damage, host = "", "", "" local creative, damage, host = "", "", ""
@ -182,7 +187,7 @@ local function get_formspec(tabview, name, tabdata)
damage .. damage ..
host .. host ..
"textlist[3.9,0.4;7.9,3.45;sp_worlds;" .. "textlist[3.9,0.4;7.9,3.45;sp_worlds;" ..
menu_render_worldlist() .. menu_render_worldlist(not enable_gamebar) ..
";" .. index .. "]" ";" .. index .. "]"
if core.settings:get_bool("enable_server") and disabled_settings["enable_server"] == nil then if core.settings:get_bool("enable_server") and disabled_settings["enable_server"] == nil then
@ -319,11 +324,11 @@ local function main_button_handler(this, fields, name, tabdata)
end end
if fields["world_create"] ~= nil then if fields["world_create"] ~= nil then
local create_world_dlg = create_create_world_dlg(true) local create_world_dlg = create_create_world_dlg(enable_gamebar)
create_world_dlg:set_parent(this) create_world_dlg:set_parent(this)
this:hide() this:hide()
create_world_dlg:show() create_world_dlg:show()
mm_texture.update("singleplayer", current_game()) mm_game_theme.update("singleplayer", current_game())
return true return true
end end
@ -340,7 +345,7 @@ local function main_button_handler(this, fields, name, tabdata)
delete_world_dlg:set_parent(this) delete_world_dlg:set_parent(this)
this:hide() this:hide()
delete_world_dlg:show() delete_world_dlg:show()
mm_texture.update("singleplayer",current_game()) mm_game_theme.update("singleplayer",current_game())
end end
end end
@ -358,7 +363,7 @@ local function main_button_handler(this, fields, name, tabdata)
configdialog:set_parent(this) configdialog:set_parent(this)
this:hide() this:hide()
configdialog:show() configdialog:show()
mm_texture.update("singleplayer",current_game()) mm_game_theme.update("singleplayer",current_game())
end end
end end
@ -371,11 +376,8 @@ if enable_gamebar then
function on_change(type, old_tab, new_tab) function on_change(type, old_tab, new_tab)
if (type == "ENTER") then if (type == "ENTER") then
local game = current_game() local game = current_game()
if game then if game then
menudata.worldlist:set_filtercriteria(game.id) apply_game(game)
core.set_topleft_text(game.name)
mm_texture.update("singleplayer",game)
end end
singleplayer_refresh_gamebar() singleplayer_refresh_gamebar()
@ -387,7 +389,7 @@ if enable_gamebar then
gamebar:hide() gamebar:hide()
end end
core.set_topleft_text("") core.set_topleft_text("")
mm_texture.update(new_tab,nil) mm_game_theme.update(new_tab,nil)
end end
end end
end end

View File

@ -247,7 +247,7 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
adv_settings_dlg:set_parent(this) adv_settings_dlg:set_parent(this)
this:hide() this:hide()
adv_settings_dlg:show() adv_settings_dlg:show()
--mm_texture.update("singleplayer", current_game()) --mm_game_theme.update("singleplayer", current_game())
return true return true
end end
if fields["cb_smooth_lighting"] then if fields["cb_smooth_lighting"] then
@ -275,13 +275,7 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
return true return true
end end
if fields["cb_shaders"] then if fields["cb_shaders"] then
if (core.settings:get("video_driver") == "direct3d8" or core.settings:set("enable_shaders", fields["cb_shaders"])
core.settings:get("video_driver") == "direct3d9") then
core.settings:set("enable_shaders", "false")
gamedata.errormessage = fgettext("To enable shaders the OpenGL driver needs to be used.")
else
core.settings:set("enable_shaders", fields["cb_shaders"])
end
return true return true
end end
if fields["cb_tonemapping"] then if fields["cb_tonemapping"] then
@ -370,11 +364,11 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
core.settings:set("enable_dynamic_shadows", "false") core.settings:set("enable_dynamic_shadows", "false")
else else
local shadow_presets = { local shadow_presets = {
[2] = { 80, 512, "true", 0, "false" }, [2] = { 55, 512, "true", 0, "false" },
[3] = { 120, 1024, "true", 1, "false" }, [3] = { 82, 1024, "true", 1, "false" },
[4] = { 350, 2048, "true", 1, "false" }, [4] = { 240, 2048, "true", 1, "false" },
[5] = { 350, 2048, "true", 2, "true" }, [5] = { 240, 2048, "true", 2, "true" },
[6] = { 450, 4096, "true", 2, "true" }, [6] = { 300, 4096, "true", 2, "true" },
} }
local s = shadow_presets[table.indexof(labels.shadow_levels, fields["dd_shadows"])] local s = shadow_presets[table.indexof(labels.shadow_levels, fields["dd_shadows"])]
if s then if s then

View File

@ -1,4 +1,5 @@
_G.core = {} _G.core = {}
_G.vector = {metatable = {}}
_G.unpack = table.unpack _G.unpack = table.unpack
_G.serverlistmgr = {} _G.serverlistmgr = {}

View File

@ -102,8 +102,9 @@ local function instrument(def)
-- also called https://en.wikipedia.org/wiki/Continuation_passing_style -- also called https://en.wikipedia.org/wiki/Continuation_passing_style
-- Compared to table creation and unpacking it won't lose `nil` returns -- Compared to table creation and unpacking it won't lose `nil` returns
-- and is expected to be faster -- and is expected to be faster
-- `measure` will be executed after time() and func(...) -- `measure` will be executed after func(...)
return measure(modname, instrument_name, time(), func(...)) local start = time()
return measure(modname, instrument_name, start, func(...))
end end
end end

View File

@ -146,17 +146,17 @@ enable_joysticks (Enable joysticks) bool false
joystick_id (Joystick ID) int 0 joystick_id (Joystick ID) int 0
# The type of joystick # The type of joystick
joystick_type (Joystick type) enum auto auto,generic,xbox joystick_type (Joystick type) enum auto auto,generic,xbox,dragonrise_gamecube
# The time in seconds it takes between repeated events # The time in seconds it takes between repeated events
# when holding down a joystick button combination. # when holding down a joystick button combination.
repeat_joystick_button_time (Joystick button repetition interval) float 0.17 0.001 repeat_joystick_button_time (Joystick button repetition interval) float 0.17 0.001
# The deadzone of the joystick # The dead zone of the joystick
joystick_deadzone (Joystick deadzone) int 2048 joystick_deadzone (Joystick dead zone) int 2048
# The sensitivity of the joystick axes for moving the # The sensitivity of the joystick axes for moving the
# ingame view frustum around. # in-game view frustum around.
joystick_frustum_sensitivity (Joystick frustum sensitivity) float 170 joystick_frustum_sensitivity (Joystick frustum sensitivity) float 170
# Key for moving the player forward. # Key for moving the player forward.
@ -463,9 +463,9 @@ keymap_decrease_viewing_range_min (View range decrease key) key -
[**Basic] [**Basic]
# Whether nametag backgrounds should be shown by default. # Whether name tag backgrounds should be shown by default.
# Mods may still set a background. # Mods may still set a background.
show_nametag_backgrounds (Show nametag backgrounds by default) bool true show_nametag_backgrounds (Show name tag backgrounds by default) bool true
# Enable vertex buffer objects. # Enable vertex buffer objects.
# This should greatly improve graphics performance. # This should greatly improve graphics performance.
@ -487,6 +487,10 @@ connected_glass (Connect glass) bool false
# Disable for speed or for different looks. # Disable for speed or for different looks.
smooth_lighting (Smooth lighting) bool true smooth_lighting (Smooth lighting) bool true
# Enables tradeoffs that reduce CPU load or increase rendering performance
# at the expense of minor visual glitches that do not impact game playability.
performance_tradeoffs (Tradeoffs for performance) bool false
# Clouds are a client side effect. # Clouds are a client side effect.
enable_clouds (Clouds) bool true enable_clouds (Clouds) bool true
@ -501,7 +505,7 @@ enable_particles (Digging particles) bool true
[**Filtering] [**Filtering]
# Use mip mapping to scale textures. May slightly increase performance, # Use mipmapping to scale textures. May slightly increase performance,
# especially when using a high resolution texture pack. # especially when using a high resolution texture pack.
# Gamma correct downscaling is not supported. # Gamma correct downscaling is not supported.
mip_map (Mipmapping) bool false mip_map (Mipmapping) bool false
@ -600,9 +604,10 @@ enable_waving_plants (Waving plants) bool false
# Requires shaders to be enabled. # Requires shaders to be enabled.
enable_dynamic_shadows (Dynamic shadows) bool false enable_dynamic_shadows (Dynamic shadows) bool false
# Set the shadow strength. # Set the shadow strength gamma.
# Adjusts the intensity of in-game dynamic shadows.
# Lower value means lighter shadows, higher value means darker shadows. # Lower value means lighter shadows, higher value means darker shadows.
shadow_strength (Shadow strength) float 0.2 0.05 1.0 shadow_strength_gamma (Shadow strength gamma) float 1.0 0.1 10.0
# Maximum distance to render shadows. # Maximum distance to render shadows.
shadow_map_max_distance (Shadow map max distance in nodes to render shadows) float 120.0 10.0 1000.0 shadow_map_max_distance (Shadow map max distance in nodes to render shadows) float 120.0 10.0 1000.0
@ -621,12 +626,12 @@ shadow_map_texture_32bit (Shadow map texture in 32 bits) bool true
# On true uses Poisson disk to make "soft shadows". Otherwise uses PCF filtering. # On true uses Poisson disk to make "soft shadows". Otherwise uses PCF filtering.
shadow_poisson_filter (Poisson filtering) bool true shadow_poisson_filter (Poisson filtering) bool true
# Define shadow filtering quality # Define shadow filtering quality.
# This simulates the soft shadows effect by applying a PCF or Poisson disk # This simulates the soft shadows effect by applying a PCF or Poisson disk
# but also uses more resources. # but also uses more resources.
shadow_filters (Shadow filter quality) enum 1 0,1,2 shadow_filters (Shadow filter quality) enum 1 0,1,2
# Enable colored shadows. # Enable colored shadows.
# On true translucent nodes cast colored shadows. This is expensive. # On true translucent nodes cast colored shadows. This is expensive.
shadow_map_color (Colored shadows) bool false shadow_map_color (Colored shadows) bool false
@ -638,10 +643,10 @@ shadow_update_frames (Map shadows update frames) int 8 1 16
# Set the soft shadow radius size. # Set the soft shadow radius size.
# Lower values mean sharper shadows, bigger values mean softer shadows. # Lower values mean sharper shadows, bigger values mean softer shadows.
# Minimum value: 1.0; maxiumum value: 10.0 # Minimum value: 1.0; maximum value: 10.0
shadow_soft_radius (Soft shadow radius) float 1.0 1.0 10.0 shadow_soft_radius (Soft shadow radius) float 1.0 1.0 10.0
# Set the tilt of Sun/Moon orbit in degrees # Set the tilt of Sun/Moon orbit in degrees.
# Value of 0 means no tilt / vertical orbit. # Value of 0 means no tilt / vertical orbit.
# Minimum value: 0.0; maximum value: 60.0 # Minimum value: 0.0; maximum value: 60.0
shadow_sky_body_orbit_tilt (Sky Body Orbit Tilt) float 0.0 0.0 60.0 shadow_sky_body_orbit_tilt (Sky Body Orbit Tilt) float 0.0 0.0 60.0
@ -801,7 +806,7 @@ desynchronize_mapblock_texture_animation (Desynchronize block animation) bool tr
# Useful if there's something to be displayed right or left of hotbar. # Useful if there's something to be displayed right or left of hotbar.
hud_hotbar_max_width (Maximum hotbar width) float 1.0 hud_hotbar_max_width (Maximum hotbar width) float 1.0
# Modifies the size of the hudbar elements. # Modifies the size of the HUD elements.
hud_scaling (HUD scale factor) float 1.0 hud_scaling (HUD scale factor) float 1.0
# Enables caching of facedir rotated meshes. # Enables caching of facedir rotated meshes.
@ -865,6 +870,10 @@ autoscale_mode (Autoscaling mode) enum disable disable,enable,force
# A restart is required after changing this. # A restart is required after changing this.
show_entity_selectionbox (Show entity selection boxes) bool false show_entity_selectionbox (Show entity selection boxes) bool false
# Distance in nodes at which transparency depth sorting is enabled
# Use this to limit the performance impact of transparency depth sorting
transparency_sorting_distance (Transparency Sorting Distance) int 16 0 128
[*Menus] [*Menus]
# Use a cloud animation for the main menu background. # Use a cloud animation for the main menu background.
@ -894,10 +903,6 @@ tooltip_show_delay (Tooltip delay) int 400
# Append item name to tooltip. # Append item name to tooltip.
tooltip_append_itemname (Append item name) bool false tooltip_append_itemname (Append item name) bool false
# Whether FreeType fonts are used, requires FreeType support to be compiled in.
# If disabled, bitmap and XML vectors fonts are used instead.
freetype (FreeType fonts) bool true
font_bold (Font bold by default) bool false font_bold (Font bold by default) bool false
font_italic (Font italic by default) bool false font_italic (Font italic by default) bool false
@ -908,12 +913,16 @@ font_shadow (Font shadow) int 1
# Opaqueness (alpha) of the shadow behind the default font, between 0 and 255. # Opaqueness (alpha) of the shadow behind the default font, between 0 and 255.
font_shadow_alpha (Font shadow alpha) int 127 0 255 font_shadow_alpha (Font shadow alpha) int 127 0 255
# Font size of the default font in point (pt). # Font size of the default font where 1 unit = 1 pixel at 96 DPI
font_size (Font size) int 16 1 font_size (Font size) int 16 1
# Path to the default font. # For pixel-style fonts that do not scale well, this ensures that font sizes used
# If “freetype” setting is enabled: Must be a TrueType font. # with this font will always be divisible by this value, in pixels. For instance,
# If “freetype” setting is disabled: Must be a bitmap or XML vectors font. # a pixel font 16 pixels tall should have this set to 16, so it will only ever be
# sized 16, 32, 48, etc., so a mod requesting a size of 25 will get 32.
font_size_divisible_by (Font size divisible by) int 1 1
# Path to the default font. Must be a TrueType font.
# The fallback font will be used if the font cannot be loaded. # The fallback font will be used if the font cannot be loaded.
font_path (Regular font path) filepath fonts/Arimo-Regular.ttf font_path (Regular font path) filepath fonts/Arimo-Regular.ttf
@ -921,12 +930,16 @@ font_path_bold (Bold font path) filepath fonts/Arimo-Bold.ttf
font_path_italic (Italic font path) filepath fonts/Arimo-Italic.ttf font_path_italic (Italic font path) filepath fonts/Arimo-Italic.ttf
font_path_bold_italic (Bold and italic font path) filepath fonts/Arimo-BoldItalic.ttf font_path_bold_italic (Bold and italic font path) filepath fonts/Arimo-BoldItalic.ttf
# Font size of the monospace font in point (pt). # Font size of the monospace font where 1 unit = 1 pixel at 96 DPI
mono_font_size (Monospace font size) int 15 1 mono_font_size (Monospace font size) int 16 1
# Path to the monospace font. # For pixel-style fonts that do not scale well, this ensures that font sizes used
# If “freetype” setting is enabled: Must be a TrueType font. # with this font will always be divisible by this value, in pixels. For instance,
# If “freetype” setting is disabled: Must be a bitmap or XML vectors font. # a pixel font 16 pixels tall should have this set to 16, so it will only ever be
# sized 16, 32, 48, etc., so a mod requesting a size of 25 will get 32.
mono_font_size_divisible_by (Monospace font size divisible by) int 1 1
# Path to the monospace font. Must be a TrueType font.
# This font is used for e.g. the console and profiler screen. # This font is used for e.g. the console and profiler screen.
mono_font_path (Monospace font path) filepath fonts/Cousine-Regular.ttf mono_font_path (Monospace font path) filepath fonts/Cousine-Regular.ttf
@ -934,9 +947,7 @@ mono_font_path_bold (Bold monospace font path) filepath fonts/Cousine-Bold.ttf
mono_font_path_italic (Italic monospace font path) filepath fonts/Cousine-Italic.ttf mono_font_path_italic (Italic monospace font path) filepath fonts/Cousine-Italic.ttf
mono_font_path_bold_italic (Bold and italic monospace font path) filepath fonts/Cousine-BoldItalic.ttf mono_font_path_bold_italic (Bold and italic monospace font path) filepath fonts/Cousine-BoldItalic.ttf
# Path of the fallback font. # Path of the fallback font. Must be a TrueType font.
# If “freetype” setting is enabled: Must be a TrueType font.
# If “freetype” setting is disabled: Must be a bitmap or XML vectors font.
# This font will be used for certain languages or if the default font is unavailable. # This font will be used for certain languages or if the default font is unavailable.
fallback_font_path (Fallback font path) filepath fonts/DroidSansFallbackFull.ttf fallback_font_path (Fallback font path) filepath fonts/DroidSansFallbackFull.ttf
@ -949,7 +960,7 @@ chat_font_size (Chat font size) int 0
screenshot_path (Screenshot folder) path screenshots screenshot_path (Screenshot folder) path screenshots
# Format of screenshots. # Format of screenshots.
screenshot_format (Screenshot format) enum png png,jpg,bmp,pcx,ppm,tga screenshot_format (Screenshot format) enum png png,jpg
# Screenshot quality. Only used for JPEG format. # Screenshot quality. Only used for JPEG format.
# 1 means worst quality; 100 means best quality. # 1 means worst quality; 100 means best quality.
@ -961,6 +972,9 @@ screenshot_quality (Screenshot quality) int 0 0 100
# Adjust dpi configuration to your screen (non X11/Android only) e.g. for 4k screens. # Adjust dpi configuration to your screen (non X11/Android only) e.g. for 4k screens.
screen_dpi (DPI) int 72 1 screen_dpi (DPI) int 72 1
# Adjust the detected display density, used for scaling UI elements.
display_density_factor (Display Density Scaling Factor) float 1
# Windows systems only: Start Minetest with the command line window in the background. # Windows systems only: Start Minetest with the command line window in the background.
# Contains the same information as the file debug.txt (default name). # Contains the same information as the file debug.txt (default name).
enable_console (Enable console window) bool false enable_console (Enable console window) bool false
@ -985,8 +999,8 @@ mute_sound (Mute sound) bool false
[Client] [Client]
# Clickable weblinks (middle-click or ctrl-left-click) enabled in chat console output. # Clickable weblinks (middle-click or Ctrl+left-click) enabled in chat console output.
clickable_chat_weblinks (Chat weblinks) bool false clickable_chat_weblinks (Chat weblinks) bool true
# Optional override for chat weblink color. # Optional override for chat weblink color.
chat_weblink_color (Weblink color) string chat_weblink_color (Weblink color) string
@ -1044,6 +1058,12 @@ client_unload_unused_data_timeout (Mapblock unload timeout) int 600
# Set to -1 for unlimited amount. # Set to -1 for unlimited amount.
client_mapblock_limit (Mapblock limit) int 7500 client_mapblock_limit (Mapblock limit) int 7500
# Whether to show technical names.
# Affects mods and texture packs in the Content and Select Mods menus, as well as
# setting names in All Settings.
# Controlled by the checkbox in the "All settings" menu.
show_technical_names (Show technical names) bool false
# Whether to show the client debug info (has the same effect as hitting F5). # Whether to show the client debug info (has the same effect as hitting F5).
show_debug (Show debug info) bool false show_debug (Show debug info) bool false
@ -1114,7 +1134,7 @@ max_packets_per_iteration (Max. packets per iteration) int 1024
# Compression level to use when sending mapblocks to the client. # Compression level to use when sending mapblocks to the client.
# -1 - use default compression level # -1 - use default compression level
# 0 - least compresson, fastest # 0 - least compression, fastest
# 9 - best compression, slowest # 9 - best compression, slowest
map_compression_level_net (Map Compression Level for Network Transfer) int -1 -1 9 map_compression_level_net (Map Compression Level for Network Transfer) int -1 -1 9
@ -1178,7 +1198,7 @@ enable_mod_channels (Mod channels) bool false
# If this is set, players will always (re)spawn at the given position. # If this is set, players will always (re)spawn at the given position.
static_spawnpoint (Static spawnpoint) string static_spawnpoint (Static spawnpoint) string
# If enabled, new players cannot join with an empty password. # If enabled, players cannot join without a password or change theirs to an empty password.
disallow_empty_password (Disallow empty passwords) bool false disallow_empty_password (Disallow empty passwords) bool false
# If enabled, disable cheat prevention in multiplayer. # If enabled, disable cheat prevention in multiplayer.
@ -1300,7 +1320,7 @@ movement_gravity (Gravity) float 9.81
deprecated_lua_api_handling (Deprecated Lua API handling) enum log none,log,error deprecated_lua_api_handling (Deprecated Lua API handling) enum log none,log,error
# Number of extra blocks that can be loaded by /clearobjects at once. # Number of extra blocks that can be loaded by /clearobjects at once.
# This is a trade-off between sqlite transaction overhead and # This is a trade-off between SQLite transaction overhead and
# memory consumption (4096=100MB, as a rule of thumb). # memory consumption (4096=100MB, as a rule of thumb).
max_clearobjects_extra_loaded_blocks (Max. clearobjects extra blocks) int 4096 max_clearobjects_extra_loaded_blocks (Max. clearobjects extra blocks) int 4096
@ -1309,14 +1329,14 @@ max_clearobjects_extra_loaded_blocks (Max. clearobjects extra blocks) int 4096
server_unload_unused_data_timeout (Unload unused server data) int 29 server_unload_unused_data_timeout (Unload unused server data) int 29
# Maximum number of statically stored objects in a block. # Maximum number of statically stored objects in a block.
max_objects_per_block (Maximum objects per block) int 64 max_objects_per_block (Maximum objects per block) int 256
# See https://www.sqlite.org/pragma.html#pragma_synchronous # See https://www.sqlite.org/pragma.html#pragma_synchronous
sqlite_synchronous (Synchronous SQLite) enum 2 0,1,2 sqlite_synchronous (Synchronous SQLite) enum 2 0,1,2
# Compression level to use when saving mapblocks to disk. # Compression level to use when saving mapblocks to disk.
# -1 - use default compression level # -1 - use default compression level
# 0 - least compresson, fastest # 0 - least compression, fastest
# 9 - best compression, slowest # 9 - best compression, slowest
map_compression_level_disk (Map Compression Level for Disk Storage) int -1 -1 9 map_compression_level_disk (Map Compression Level for Disk Storage) int -1 -1 9
@ -1423,8 +1443,8 @@ instrument.abm (Active Block Modifiers) bool true
# Instrument the action function of Loading Block Modifiers on registration. # Instrument the action function of Loading Block Modifiers on registration.
instrument.lbm (Loading Block Modifiers) bool true instrument.lbm (Loading Block Modifiers) bool true
# Instrument chatcommands on registration. # Instrument chat commands on registration.
instrument.chatcommand (Chatcommands) bool true instrument.chatcommand (Chat commands) bool true
# Instrument global callback functions on registration. # Instrument global callback functions on registration.
# (anything you pass to a minetest.register_*() function) # (anything you pass to a minetest.register_*() function)
@ -1460,7 +1480,8 @@ language (Language) enum ,be,bg,ca,cs,da,de,el,en,eo,es,et,eu,fi,fr,gd,gl,hu,i
# - action # - action
# - info # - info
# - verbose # - verbose
debug_log_level (Debug log level) enum action ,none,error,warning,action,info,verbose # - trace
debug_log_level (Debug log level) enum action ,none,error,warning,action,info,verbose,trace
# If the file size of debug.txt exceeds the number of megabytes specified in # If the file size of debug.txt exceeds the number of megabytes specified in
# this setting when it is opened, the file is moved to debug.txt.1, # this setting when it is opened, the file is moved to debug.txt.1,
@ -1469,7 +1490,7 @@ debug_log_level (Debug log level) enum action ,none,error,warning,action,info,ve
debug_log_size_max (Debug log file size threshold) int 50 debug_log_size_max (Debug log file size threshold) int 50
# Minimal level of logging to be written to chat. # Minimal level of logging to be written to chat.
chat_log_level (Chat log level) enum error ,none,error,warning,action,info,verbose chat_log_level (Chat log level) enum error ,none,error,warning,action,info,verbose,trace
# Enable IPv6 support (for both client and server). # Enable IPv6 support (for both client and server).
# Required for IPv6 connections to work at all. # Required for IPv6 connections to work at all.
@ -1514,11 +1535,11 @@ max_block_generate_distance (Max block generate distance) int 10
# Limit of map generation, in nodes, in all 6 directions from (0, 0, 0). # Limit of map generation, in nodes, in all 6 directions from (0, 0, 0).
# Only mapchunks completely within the mapgen limit are generated. # Only mapchunks completely within the mapgen limit are generated.
# Value is stored per-world. # Value is stored per-world.
mapgen_limit (Map generation limit) int 31000 0 31000 mapgen_limit (Map generation limit) int 31007 0 31007
# Global map generation attributes. # Global map generation attributes.
# In Mapgen v6 the 'decorations' flag controls all decorations except trees # In Mapgen v6 the 'decorations' flag controls all decorations except trees
# and junglegrass, in all other mapgens this flag controls all decorations. # and jungle grass, in all other mapgens this flag controls all decorations.
mg_flags (Mapgen flags) flags caves,dungeons,light,decorations,biomes,ores caves,dungeons,light,decorations,biomes,ores,nocaves,nodungeons,nolight,nodecorations,nobiomes,noores mg_flags (Mapgen flags) flags caves,dungeons,light,decorations,biomes,ores caves,dungeons,light,decorations,biomes,ores,nocaves,nodungeons,nolight,nodecorations,nobiomes,noores
[*Biome API temperature and humidity noise parameters] [*Biome API temperature and humidity noise parameters]
@ -2270,7 +2291,7 @@ contentdb_max_concurrent_downloads (ContentDB Max Concurrent Downloads) int 3
[Cheat Menu] [Cheat Menu]
# Font to use for cheat menu # Font to use for cheat menu
cheat_menu_font (MenuFont) enum FM_Mono FM_Standard,FM_Mono,FM_Fallback,FM_Simple,FM_SimpleMono,FM_MaxMode,FM_Unspecified cheat_menu_font (MenuFont) enum FM_Mono FM_Standard,FM_Mono,FM_Fallback,FM_MaxMode,FM_Unspecified
# (RGB value) # (RGB value)
cheat_menu_bg_color (Cell background color) v3f 255, 145, 88 cheat_menu_bg_color (Cell background color) v3f 255, 145, 88

View File

@ -1,5 +1,6 @@
uniform sampler2D baseTexture; uniform sampler2D baseTexture;
uniform vec3 dayLight;
uniform vec4 skyBgColor; uniform vec4 skyBgColor;
uniform float fogDistance; uniform float fogDistance;
uniform vec3 eyePosition; uniform vec3 eyePosition;
@ -15,10 +16,15 @@ uniform float animationTimer;
uniform float f_textureresolution; uniform float f_textureresolution;
uniform mat4 m_ShadowViewProj; uniform mat4 m_ShadowViewProj;
uniform float f_shadowfar; uniform float f_shadowfar;
varying float normalOffsetScale; uniform float f_shadow_strength;
uniform vec4 CameraPos;
uniform float xyPerspectiveBias0;
uniform float xyPerspectiveBias1;
varying float adj_shadow_strength; varying float adj_shadow_strength;
varying float cosLight; varying float cosLight;
varying float f_normal_length; varying float f_normal_length;
varying vec3 shadow_position;
#endif #endif
@ -42,23 +48,7 @@ varying float nightRatio;
const float fogStart = FOG_START; const float fogStart = FOG_START;
const float fogShadingParameter = 1.0 / ( 1.0 - fogStart); const float fogShadingParameter = 1.0 / ( 1.0 - fogStart);
#ifdef ENABLE_DYNAMIC_SHADOWS #ifdef ENABLE_DYNAMIC_SHADOWS
const float bias0 = 0.9;
const float zPersFactor = 0.5;
const float bias1 = 1.0 - bias0 + 1e-6;
vec4 getPerspectiveFactor(in vec4 shadowPosition)
{
float pDistance = length(shadowPosition.xy);
float pFactor = pDistance * bias0 + bias1;
shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor);
return shadowPosition;
}
// assuming near is always 1.0 // assuming near is always 1.0
float getLinearDepth() float getLinearDepth()
@ -68,16 +58,7 @@ float getLinearDepth()
vec3 getLightSpacePosition() vec3 getLightSpacePosition()
{ {
vec4 pLightSpace; return shadow_position * 0.5 + 0.5;
// some drawtypes have zero normals, so we need to handle it :(
#if DRAW_TYPE == NDT_PLANTLIKE
pLightSpace = m_ShadowViewProj * vec4(worldPosition, 1.0);
#else
float offsetScale = (0.0057 * getLinearDepth() + normalOffsetScale);
pLightSpace = m_ShadowViewProj * vec4(worldPosition + offsetScale * normalize(vNormal), 1.0);
#endif
pLightSpace = getPerspectiveFactor(pLightSpace);
return pLightSpace.xyz * 0.5 + 0.5;
} }
// custom smoothstep implementation because it's not defined in glsl1.2 // custom smoothstep implementation because it's not defined in glsl1.2
// https://docs.gl/sl4/smoothstep // https://docs.gl/sl4/smoothstep
@ -170,13 +151,13 @@ float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDis
float getBaseLength(vec2 smTexCoord) float getBaseLength(vec2 smTexCoord)
{ {
float l = length(2.0 * smTexCoord.xy - 1.0); // length in texture coords float l = length(2.0 * smTexCoord.xy - 1.0 - CameraPos.xy); // length in texture coords
return bias1 / (1.0 / l - bias0); // return to undistorted coords return xyPerspectiveBias1 / (1.0 / l - xyPerspectiveBias0); // return to undistorted coords
} }
float getDeltaPerspectiveFactor(float l) float getDeltaPerspectiveFactor(float l)
{ {
return 0.1 / (bias0 * l + bias1); // original distortion factor, divided by 10 return 0.04 * pow(512.0 / f_textureresolution, 0.4) / (xyPerspectiveBias0 * l + xyPerspectiveBias1); // original distortion factor, divided by 10
} }
float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDistance, float multiplier) float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDistance, float multiplier)
@ -185,6 +166,9 @@ float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDist
float perspectiveFactor; float perspectiveFactor;
// Return fast if sharp shadows are requested // Return fast if sharp shadows are requested
if (PCFBOUND == 0.0)
return 0.0;
if (SOFTSHADOWRADIUS <= 1.0) { if (SOFTSHADOWRADIUS <= 1.0) {
perspectiveFactor = getDeltaPerspectiveFactor(baseLength); perspectiveFactor = getDeltaPerspectiveFactor(baseLength);
return max(2 * length(smTexCoord.xy) * 2048 / f_textureresolution / pow(perspectiveFactor, 3), SOFTSHADOWRADIUS); return max(2 * length(smTexCoord.xy) * 2048 / f_textureresolution / pow(perspectiveFactor, 3), SOFTSHADOWRADIUS);
@ -479,36 +463,59 @@ void main(void)
vec4 col = vec4(color.rgb * varColor.rgb, 1.0); vec4 col = vec4(color.rgb * varColor.rgb, 1.0);
#ifdef ENABLE_DYNAMIC_SHADOWS #ifdef ENABLE_DYNAMIC_SHADOWS
float shadow_int = 0.0; if (f_shadow_strength > 0.0) {
vec3 shadow_color = vec3(0.0, 0.0, 0.0); float shadow_int = 0.0;
vec3 posLightSpace = getLightSpacePosition(); vec3 shadow_color = vec3(0.0, 0.0, 0.0);
vec3 posLightSpace = getLightSpacePosition();
float distance_rate = (1 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 20.0)); float distance_rate = (1.0 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 10.0));
float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1, posLightSpace.z ),0.0); if (max(abs(posLightSpace.x - 0.5), abs(posLightSpace.y - 0.5)) > 0.5)
distance_rate = 0.0;
float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1, posLightSpace.z),0.0);
if (distance_rate > 1e-7) {
if (distance_rate > 1e-7) {
#ifdef COLORED_SHADOWS #ifdef COLORED_SHADOWS
vec4 visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); vec4 visibility;
shadow_int = visibility.r; if (cosLight > 0.0 || f_normal_length < 1e-3)
shadow_color = visibility.gba; visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z);
else
visibility = vec4(1.0, 0.0, 0.0, 0.0);
shadow_int = visibility.r;
shadow_color = visibility.gba;
#else #else
shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); if (cosLight > 0.0 || f_normal_length < 1e-3)
shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z);
else
shadow_int = 1.0;
#endif #endif
shadow_int *= distance_rate; shadow_int *= distance_rate;
shadow_int *= 1.0 - nightRatio; shadow_int = clamp(shadow_int, 0.0, 1.0);
}
// turns out that nightRatio falls off much faster than
// actual brightness of artificial light in relation to natual light.
// Power ratio was measured on torches in MTG (brightness = 14).
float adjusted_night_ratio = pow(max(0.0, nightRatio), 0.6);
// Apply self-shadowing when light falls at a narrow angle to the surface
// Cosine of the cut-off angle.
const float self_shadow_cutoff_cosine = 0.035;
if (f_normal_length != 0 && cosLight < self_shadow_cutoff_cosine) {
shadow_int = max(shadow_int, 1 - clamp(cosLight, 0.0, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine);
shadow_color = mix(vec3(0.0), shadow_color, min(cosLight, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine);
}
shadow_int *= f_adj_shadow_strength;
// calculate fragment color from components:
col.rgb =
adjusted_night_ratio * col.rgb + // artificial light
(1.0 - adjusted_night_ratio) * ( // natural light
col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color
dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight
} }
if (f_normal_length != 0 && cosLight < 0.035) {
shadow_int = max(shadow_int, min(clamp(1.0-nightRatio, 0.0, 1.0), 1 - clamp(cosLight, 0.0, 0.035)/0.035));
}
shadow_int = 1.0 - (shadow_int * f_adj_shadow_strength);
col.rgb = mix(shadow_color,col.rgb,shadow_int)*shadow_int;
// col.r = 0.5 * clamp(getPenumbraRadius(ShadowMapSampler, posLightSpace.xy, posLightSpace.z, 1.0) / SOFTSHADOWRADIUS, 0.0, 1.0) + 0.5 * col.r;
#endif #endif
#if ENABLE_TONE_MAPPING #if ENABLE_TONE_MAPPING
@ -528,6 +535,6 @@ void main(void)
- fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0); - fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
col = mix(skyBgColor, col, clarity); col = mix(skyBgColor, col, clarity);
col = vec4(col.rgb, base.a); col = vec4(col.rgb, base.a);
gl_FragColor = col; gl_FragColor = col;
} }

View File

@ -32,10 +32,13 @@ centroid varying vec2 varTexCoord;
uniform float f_shadowfar; uniform float f_shadowfar;
uniform float f_shadow_strength; uniform float f_shadow_strength;
uniform float f_timeofday; uniform float f_timeofday;
uniform vec4 CameraPos;
varying float cosLight; varying float cosLight;
varying float normalOffsetScale; varying float normalOffsetScale;
varying float adj_shadow_strength; varying float adj_shadow_strength;
varying float f_normal_length; varying float f_normal_length;
varying vec3 shadow_position;
#endif #endif
@ -45,8 +48,38 @@ varying float nightRatio;
const vec3 artificialLight = vec3(1.04, 1.04, 1.04); const vec3 artificialLight = vec3(1.04, 1.04, 1.04);
const float e = 2.718281828459; const float e = 2.718281828459;
const float BS = 10.0; const float BS = 10.0;
uniform float xyPerspectiveBias0;
uniform float xyPerspectiveBias1;
uniform float zPerspectiveBias;
#ifdef ENABLE_DYNAMIC_SHADOWS #ifdef ENABLE_DYNAMIC_SHADOWS
vec4 getRelativePosition(in vec4 position)
{
vec2 l = position.xy - CameraPos.xy;
vec2 s = l / abs(l);
s = (1.0 - s * CameraPos.xy);
l /= s;
return vec4(l, s);
}
float getPerspectiveFactor(in vec4 relativePosition)
{
float pDistance = length(relativePosition.xy);
float pFactor = pDistance * xyPerspectiveBias0 + xyPerspectiveBias1;
return pFactor;
}
vec4 applyPerspectiveDistortion(in vec4 position)
{
vec4 l = getRelativePosition(position);
float pFactor = getPerspectiveFactor(l);
l.xy /= pFactor;
position.xy = l.xy * l.zw + CameraPos.xy;
position.z *= zPerspectiveBias;
return position;
}
// custom smoothstep implementation because it's not defined in glsl1.2 // custom smoothstep implementation because it's not defined in glsl1.2
// https://docs.gl/sl4/smoothstep // https://docs.gl/sl4/smoothstep
float mtsmoothstep(in float edge0, in float edge1, in float x) float mtsmoothstep(in float edge0, in float edge1, in float x)
@ -193,24 +226,45 @@ void main(void)
varColor = clamp(color, 0.0, 1.0); varColor = clamp(color, 0.0, 1.0);
#ifdef ENABLE_DYNAMIC_SHADOWS #ifdef ENABLE_DYNAMIC_SHADOWS
vec3 nNormal = normalize(vNormal); if (f_shadow_strength > 0.0) {
cosLight = dot(nNormal, -v_LightDirection); vec3 nNormal;
float texelSize = 767.0 / f_textureresolution; f_normal_length = length(vNormal);
float slopeScale = clamp(1.0 - abs(cosLight), 0.0, 1.0);
normalOffsetScale = texelSize * slopeScale;
if (f_timeofday < 0.2) {
adj_shadow_strength = f_shadow_strength * 0.5 *
(1.0 - mtsmoothstep(0.18, 0.2, f_timeofday));
} else if (f_timeofday >= 0.8) {
adj_shadow_strength = f_shadow_strength * 0.5 *
mtsmoothstep(0.8, 0.83, f_timeofday);
} else {
adj_shadow_strength = f_shadow_strength *
mtsmoothstep(0.20, 0.25, f_timeofday) *
(1.0 - mtsmoothstep(0.7, 0.8, f_timeofday));
}
f_normal_length = length(vNormal);
#endif
/* normalOffsetScale is in world coordinates (1/10th of a meter)
z_bias is in light space coordinates */
float normalOffsetScale, z_bias;
float pFactor = getPerspectiveFactor(getRelativePosition(m_ShadowViewProj * mWorld * inVertexPosition));
if (f_normal_length > 0.0) {
nNormal = normalize(vNormal);
cosLight = dot(nNormal, -v_LightDirection);
float sinLight = pow(1 - pow(cosLight, 2.0), 0.5);
normalOffsetScale = 2.0 * pFactor * pFactor * sinLight * min(f_shadowfar, 500.0) /
xyPerspectiveBias1 / f_textureresolution;
z_bias = 1.0 * sinLight / cosLight;
}
else {
nNormal = vec3(0.0);
cosLight = clamp(dot(v_LightDirection, normalize(vec3(v_LightDirection.x, 0.0, v_LightDirection.z))), 1e-2, 1.0);
float sinLight = pow(1 - pow(cosLight, 2.0), 0.5);
normalOffsetScale = 0.0;
z_bias = 3.6e3 * sinLight / cosLight;
}
z_bias *= pFactor * pFactor / f_textureresolution / f_shadowfar;
shadow_position = applyPerspectiveDistortion(m_ShadowViewProj * mWorld * (inVertexPosition + vec4(normalOffsetScale * nNormal, 0.0))).xyz;
shadow_position.z -= z_bias;
if (f_timeofday < 0.2) {
adj_shadow_strength = f_shadow_strength * 0.5 *
(1.0 - mtsmoothstep(0.18, 0.2, f_timeofday));
} else if (f_timeofday >= 0.8) {
adj_shadow_strength = f_shadow_strength * 0.5 *
mtsmoothstep(0.8, 0.83, f_timeofday);
} else {
adj_shadow_strength = f_shadow_strength *
mtsmoothstep(0.20, 0.25, f_timeofday) *
(1.0 - mtsmoothstep(0.7, 0.8, f_timeofday));
}
}
#endif
} }

View File

@ -1,28 +1,14 @@
uniform sampler2D baseTexture; uniform sampler2D baseTexture;
uniform vec4 emissiveColor; uniform vec4 emissiveColor;
uniform vec3 dayLight;
uniform vec4 skyBgColor; uniform vec4 skyBgColor;
uniform float fogDistance; uniform float fogDistance;
uniform vec3 eyePosition; uniform vec3 eyePosition;
varying vec3 vNormal; // The cameraOffset is the current center of the visible world.
varying vec3 vPosition; uniform vec3 cameraOffset;
varying vec3 worldPosition; uniform float animationTimer;
varying lowp vec4 varColor;
#ifdef GL_ES
varying mediump vec2 varTexCoord;
#else
centroid varying vec2 varTexCoord;
#endif
varying vec3 eyeVec;
varying float vIDiff;
const float e = 2.718281828459;
const float BS = 10.0;
const float fogStart = FOG_START;
const float fogShadingParameter = 1.0 / (1.0 - fogStart);
#ifdef ENABLE_DYNAMIC_SHADOWS #ifdef ENABLE_DYNAMIC_SHADOWS
// shadow texture // shadow texture
uniform sampler2D ShadowMapSampler; uniform sampler2D ShadowMapSampler;
@ -31,57 +17,41 @@ const float fogShadingParameter = 1.0 / (1.0 - fogStart);
uniform float f_textureresolution; uniform float f_textureresolution;
uniform mat4 m_ShadowViewProj; uniform mat4 m_ShadowViewProj;
uniform float f_shadowfar; uniform float f_shadowfar;
uniform float f_timeofday; uniform float f_shadow_strength;
varying float normalOffsetScale; uniform vec4 CameraPos;
uniform float xyPerspectiveBias0;
uniform float xyPerspectiveBias1;
varying float adj_shadow_strength; varying float adj_shadow_strength;
varying float cosLight; varying float cosLight;
varying float f_normal_length; varying float f_normal_length;
varying vec3 shadow_position;
#endif #endif
#if ENABLE_TONE_MAPPING
/* Hable's UC2 Tone mapping parameters
A = 0.22;
B = 0.30;
C = 0.10;
D = 0.20;
E = 0.01;
F = 0.30;
W = 11.2;
equation used: ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F
*/
vec3 uncharted2Tonemap(vec3 x) varying vec3 vNormal;
{ varying vec3 vPosition;
return ((x * (0.22 * x + 0.03) + 0.002) / (x * (0.22 * x + 0.3) + 0.06)) - 0.03333; // World position in the visible world (i.e. relative to the cameraOffset.)
} // This can be used for many shader effects without loss of precision.
// If the absolute position is required it can be calculated with
vec4 applyToneMapping(vec4 color) // cameraOffset + worldPosition (for large coordinates the limits of float
{ // precision must be considered).
color = vec4(pow(color.rgb, vec3(2.2)), color.a); varying vec3 worldPosition;
const float gamma = 1.6; varying lowp vec4 varColor;
const float exposureBias = 5.5; #ifdef GL_ES
color.rgb = uncharted2Tonemap(exposureBias * color.rgb); varying mediump vec2 varTexCoord;
// Precalculated white_scale from #else
//vec3 whiteScale = 1.0 / uncharted2Tonemap(vec3(W)); centroid varying vec2 varTexCoord;
vec3 whiteScale = vec3(1.036015346);
color.rgb *= whiteScale;
return vec4(pow(color.rgb, vec3(1.0 / gamma)), color.a);
}
#endif #endif
varying vec3 eyeVec;
varying float nightRatio;
varying float vIDiff;
const float fogStart = FOG_START;
const float fogShadingParameter = 1.0 / (1.0 - fogStart);
#ifdef ENABLE_DYNAMIC_SHADOWS #ifdef ENABLE_DYNAMIC_SHADOWS
const float bias0 = 0.9;
const float zPersFactor = 0.5;
const float bias1 = 1.0 - bias0;
vec4 getPerspectiveFactor(in vec4 shadowPosition)
{
float pDistance = length(shadowPosition.xy);
float pFactor = pDistance * bias0 + bias1;
shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor);
return shadowPosition;
}
// assuming near is always 1.0 // assuming near is always 1.0
float getLinearDepth() float getLinearDepth()
@ -91,11 +61,14 @@ float getLinearDepth()
vec3 getLightSpacePosition() vec3 getLightSpacePosition()
{ {
vec4 pLightSpace; return shadow_position * 0.5 + 0.5;
float normalBias = 0.0005 * getLinearDepth() * cosLight + normalOffsetScale; }
pLightSpace = m_ShadowViewProj * vec4(worldPosition + normalBias * normalize(vNormal), 1.0); // custom smoothstep implementation because it's not defined in glsl1.2
pLightSpace = getPerspectiveFactor(pLightSpace); // https://docs.gl/sl4/smoothstep
return pLightSpace.xyz * 0.5 + 0.5; float mtsmoothstep(in float edge0, in float edge1, in float x)
{
float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
return t * t * (3.0 - 2.0 * t);
} }
#ifdef COLORED_SHADOWS #ifdef COLORED_SHADOWS
@ -124,10 +97,10 @@ vec4 getHardShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDist
{ {
vec4 texDepth = texture2D(shadowsampler, smTexCoord.xy).rgba; vec4 texDepth = texture2D(shadowsampler, smTexCoord.xy).rgba;
float visibility = step(0.0, (realDistance-2e-5) - texDepth.r); float visibility = step(0.0, realDistance - texDepth.r);
vec4 result = vec4(visibility, vec3(0.0,0.0,0.0));//unpackColor(texDepth.g)); vec4 result = vec4(visibility, vec3(0.0,0.0,0.0));//unpackColor(texDepth.g));
if (visibility < 0.1) { if (visibility < 0.1) {
visibility = step(0.0, (realDistance-2e-5) - texDepth.r); visibility = step(0.0, realDistance - texDepth.b);
result = vec4(visibility, unpackColor(texDepth.a)); result = vec4(visibility, unpackColor(texDepth.a));
} }
return result; return result;
@ -138,13 +111,13 @@ vec4 getHardShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDist
float getHardShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance) float getHardShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{ {
float texDepth = texture2D(shadowsampler, smTexCoord.xy).r; float texDepth = texture2D(shadowsampler, smTexCoord.xy).r;
float visibility = step(0.0, (realDistance-2e-5) - texDepth); float visibility = step(0.0, realDistance - texDepth);
return visibility; return visibility;
} }
#endif #endif
#if SHADOW_FILTER == 2 #if SHADOW_FILTER == 2
#define PCFBOUND 3.5 #define PCFBOUND 3.5
#define PCFSAMPLES 64.0 #define PCFSAMPLES 64.0
@ -163,6 +136,76 @@ float getHardShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance
#define PCFSAMPLES 1.0 #define PCFSAMPLES 1.0
#endif #endif
#endif #endif
#ifdef COLORED_SHADOWS
float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{
vec4 texDepth = texture2D(shadowsampler, smTexCoord.xy);
float depth = max(realDistance - texDepth.r, realDistance - texDepth.b);
return depth;
}
#else
float getHardShadowDepth(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{
float texDepth = texture2D(shadowsampler, smTexCoord.xy).r;
float depth = realDistance - texDepth;
return depth;
}
#endif
float getBaseLength(vec2 smTexCoord)
{
float l = length(2.0 * smTexCoord.xy - 1.0 - CameraPos.xy); // length in texture coords
return xyPerspectiveBias1 / (1.0 / l - xyPerspectiveBias0); // return to undistorted coords
}
float getDeltaPerspectiveFactor(float l)
{
return 0.04 * pow(512.0 / f_textureresolution, 0.4) / (xyPerspectiveBias0 * l + xyPerspectiveBias1); // original distortion factor, divided by 10
}
float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDistance, float multiplier)
{
float baseLength = getBaseLength(smTexCoord);
float perspectiveFactor;
// Return fast if sharp shadows are requested
if (PCFBOUND == 0.0)
return 0.0;
if (SOFTSHADOWRADIUS <= 1.0) {
perspectiveFactor = getDeltaPerspectiveFactor(baseLength);
return max(2 * length(smTexCoord.xy) * 2048 / f_textureresolution / pow(perspectiveFactor, 3), SOFTSHADOWRADIUS);
}
vec2 clampedpos;
float texture_size = 1.0 / (2048 /*f_textureresolution*/ * 0.5);
float y, x;
float depth = 0.0;
float pointDepth;
float maxRadius = SOFTSHADOWRADIUS * 5.0 * multiplier;
float bound = clamp(PCFBOUND * (1 - baseLength), 0.0, PCFBOUND);
int n = 0;
for (y = -bound; y <= bound; y += 1.0)
for (x = -bound; x <= bound; x += 1.0) {
clampedpos = vec2(x,y);
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * maxRadius);
clampedpos = clampedpos * texture_size * perspectiveFactor * maxRadius * perspectiveFactor + smTexCoord.xy;
pointDepth = getHardShadowDepth(shadowsampler, clampedpos.xy, realDistance);
if (pointDepth > -0.01) {
depth += pointDepth;
n += 1;
}
}
depth = depth / n;
depth = pow(clamp(depth, 0.0, 1000.0), 1.6) / 0.001;
perspectiveFactor = getDeltaPerspectiveFactor(baseLength);
return max(length(smTexCoord.xy) * 2 * 2048 / f_textureresolution / pow(perspectiveFactor, 3), depth * maxRadius);
}
#ifdef POISSON_FILTER #ifdef POISSON_FILTER
const vec2[64] poissonDisk = vec2[64]( const vec2[64] poissonDisk = vec2[64](
@ -238,17 +281,28 @@ vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance
{ {
vec2 clampedpos; vec2 clampedpos;
vec4 visibility = vec4(0.0); vec4 visibility = vec4(0.0);
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.5); // scale to align with PCF
if (radius < 0.1) {
// we are in the middle of even brightness, no need for filtering
return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance);
}
float baseLength = getBaseLength(smTexCoord);
float perspectiveFactor;
float texture_size = 1.0 / (f_textureresolution * 0.5); float texture_size = 1.0 / (f_textureresolution * 0.5);
int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-PCFSAMPLES))); int samples = int(clamp(PCFSAMPLES * (1 - baseLength) * (1 - baseLength), PCFSAMPLES / 4, PCFSAMPLES));
int end_offset = int(PCFSAMPLES) + init_offset; int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples)));
int end_offset = int(samples) + init_offset;
for (int x = init_offset; x < end_offset; x++) { for (int x = init_offset; x < end_offset; x++) {
clampedpos = poissonDisk[x] * texture_size * SOFTSHADOWRADIUS + smTexCoord.xy; clampedpos = poissonDisk[x];
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius);
clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor + smTexCoord.xy;
visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance); visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance);
} }
return visibility / PCFSAMPLES; return visibility / samples;
} }
#else #else
@ -257,17 +311,28 @@ float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{ {
vec2 clampedpos; vec2 clampedpos;
float visibility = 0.0; float visibility = 0.0;
float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.5); // scale to align with PCF
if (radius < 0.1) {
// we are in the middle of even brightness, no need for filtering
return getHardShadow(shadowsampler, smTexCoord.xy, realDistance);
}
float baseLength = getBaseLength(smTexCoord);
float perspectiveFactor;
float texture_size = 1.0 / (f_textureresolution * 0.5); float texture_size = 1.0 / (f_textureresolution * 0.5);
int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-PCFSAMPLES))); int samples = int(clamp(PCFSAMPLES * (1 - baseLength) * (1 - baseLength), PCFSAMPLES / 4, PCFSAMPLES));
int end_offset = int(PCFSAMPLES) + init_offset; int init_offset = int(floor(mod(((smTexCoord.x * 34.0) + 1.0) * smTexCoord.y, 64.0-samples)));
int end_offset = int(samples) + init_offset;
for (int x = init_offset; x < end_offset; x++) { for (int x = init_offset; x < end_offset; x++) {
clampedpos = poissonDisk[x] * texture_size * SOFTSHADOWRADIUS + smTexCoord.xy; clampedpos = poissonDisk[x];
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius);
clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor + smTexCoord.xy;
visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance); visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance);
} }
return visibility / PCFSAMPLES; return visibility / samples;
} }
#endif #endif
@ -281,19 +346,31 @@ vec4 getShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDistance
{ {
vec2 clampedpos; vec2 clampedpos;
vec4 visibility = vec4(0.0); vec4 visibility = vec4(0.0);
float sradius=0.0; float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.0);
if( PCFBOUND>0) if (radius < 0.1) {
sradius = SOFTSHADOWRADIUS / PCFBOUND; // we are in the middle of even brightness, no need for filtering
float texture_size = 1.0 / (f_textureresolution * 0.5); return getHardShadowColor(shadowsampler, smTexCoord.xy, realDistance);
float y, x;
// basic PCF filter
for (y = -PCFBOUND; y <= PCFBOUND; y += 1.0)
for (x = -PCFBOUND; x <= PCFBOUND; x += 1.0) {
clampedpos = vec2(x,y) * texture_size* sradius + smTexCoord.xy;
visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance);
} }
return visibility / PCFSAMPLES; float baseLength = getBaseLength(smTexCoord);
float perspectiveFactor;
float texture_size = 1.0 / (f_textureresolution * 0.5);
float y, x;
float bound = clamp(PCFBOUND * (1 - baseLength), PCFBOUND / 2, PCFBOUND);
int n = 0;
// basic PCF filter
for (y = -bound; y <= bound; y += 1.0)
for (x = -bound; x <= bound; x += 1.0) {
clampedpos = vec2(x,y); // screen offset
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius / bound);
clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor / bound + smTexCoord.xy; // both dx,dy and radius are adjusted
visibility += getHardShadowColor(shadowsampler, clampedpos.xy, realDistance);
n += 1;
}
return visibility / n;
} }
#else #else
@ -301,20 +378,31 @@ float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{ {
vec2 clampedpos; vec2 clampedpos;
float visibility = 0.0; float visibility = 0.0;
float sradius=0.0; float radius = getPenumbraRadius(shadowsampler, smTexCoord, realDistance, 1.0);
if( PCFBOUND>0) if (radius < 0.1) {
sradius = SOFTSHADOWRADIUS / PCFBOUND; // we are in the middle of even brightness, no need for filtering
return getHardShadow(shadowsampler, smTexCoord.xy, realDistance);
float texture_size = 1.0 / (f_textureresolution * 0.5);
float y, x;
// basic PCF filter
for (y = -PCFBOUND; y <= PCFBOUND; y += 1.0)
for (x = -PCFBOUND; x <= PCFBOUND; x += 1.0) {
clampedpos = vec2(x,y) * texture_size * sradius + smTexCoord.xy;
visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance);
} }
return visibility / PCFSAMPLES; float baseLength = getBaseLength(smTexCoord);
float perspectiveFactor;
float texture_size = 1.0 / (f_textureresolution * 0.5);
float y, x;
float bound = clamp(PCFBOUND * (1 - baseLength), PCFBOUND / 2, PCFBOUND);
int n = 0;
// basic PCF filter
for (y = -bound; y <= bound; y += 1.0)
for (x = -bound; x <= bound; x += 1.0) {
clampedpos = vec2(x,y); // screen offset
perspectiveFactor = getDeltaPerspectiveFactor(baseLength + length(clampedpos) * texture_size * radius / bound);
clampedpos = clampedpos * texture_size * perspectiveFactor * radius * perspectiveFactor / bound + smTexCoord.xy; // both dx,dy and radius are adjusted
visibility += getHardShadow(shadowsampler, clampedpos.xy, realDistance);
n += 1;
}
return visibility / n;
} }
#endif #endif
@ -322,12 +410,46 @@ float getShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
#endif #endif
#endif #endif
#if ENABLE_TONE_MAPPING
/* Hable's UC2 Tone mapping parameters
A = 0.22;
B = 0.30;
C = 0.10;
D = 0.20;
E = 0.01;
F = 0.30;
W = 11.2;
equation used: ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F
*/
vec3 uncharted2Tonemap(vec3 x)
{
return ((x * (0.22 * x + 0.03) + 0.002) / (x * (0.22 * x + 0.3) + 0.06)) - 0.03333;
}
vec4 applyToneMapping(vec4 color)
{
color = vec4(pow(color.rgb, vec3(2.2)), color.a);
const float gamma = 1.6;
const float exposureBias = 5.5;
color.rgb = uncharted2Tonemap(exposureBias * color.rgb);
// Precalculated white_scale from
//vec3 whiteScale = 1.0 / uncharted2Tonemap(vec3(W));
vec3 whiteScale = vec3(1.036015346);
color.rgb *= whiteScale;
return vec4(pow(color.rgb, vec3(1.0 / gamma)), color.a);
}
#endif
void main(void) void main(void)
{ {
vec3 color; vec3 color;
vec2 uv = varTexCoord.st; vec2 uv = varTexCoord.st;
vec4 base = texture2D(baseTexture, uv).rgba;
vec4 base = texture2D(baseTexture, uv).rgba;
// If alpha is zero, we can just discard the pixel. This fixes transparency // If alpha is zero, we can just discard the pixel. This fixes transparency
// on GPUs like GC7000L, where GL_ALPHA_TEST is not implemented in mesa, // on GPUs like GC7000L, where GL_ALPHA_TEST is not implemented in mesa,
// and also on GLES 2, where GL_ALPHA_TEST is missing entirely. // and also on GLES 2, where GL_ALPHA_TEST is missing entirely.
@ -341,34 +463,66 @@ void main(void)
#endif #endif
color = base.rgb; color = base.rgb;
vec4 col = vec4(color.rgb, base.a); vec4 col = vec4(color.rgb * varColor.rgb, 1.0);
col.rgb *= varColor.rgb; col.rgb *= vIDiff;
col.rgb *= emissiveColor.rgb * vIDiff;
#ifdef ENABLE_DYNAMIC_SHADOWS #ifdef ENABLE_DYNAMIC_SHADOWS
float shadow_int = 0.0; if (f_shadow_strength > 0.0) {
vec3 shadow_color = vec3(0.0, 0.0, 0.0); float shadow_int = 0.0;
vec3 posLightSpace = getLightSpacePosition(); vec3 shadow_color = vec3(0.0, 0.0, 0.0);
vec3 posLightSpace = getLightSpacePosition();
float distance_rate = (1.0 - pow(clamp(2.0 * length(posLightSpace.xy - 0.5),0.0,1.0), 10.0));
if (max(abs(posLightSpace.x - 0.5), abs(posLightSpace.y - 0.5)) > 0.5)
distance_rate = 0.0;
float f_adj_shadow_strength = max(adj_shadow_strength-mtsmoothstep(0.9,1.1, posLightSpace.z),0.0);
if (distance_rate > 1e-7) {
#ifdef COLORED_SHADOWS #ifdef COLORED_SHADOWS
vec4 visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); vec4 visibility;
shadow_int = visibility.r; if (cosLight > 0.0 || f_normal_length < 1e-3)
shadow_color = visibility.gba; visibility = getShadowColor(ShadowMapSampler, posLightSpace.xy, posLightSpace.z);
else
visibility = vec4(1.0, 0.0, 0.0, 0.0);
shadow_int = visibility.r;
shadow_color = visibility.gba;
#else #else
shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z); if (cosLight > 0.0 || f_normal_length < 1e-3)
if (cosLight > 0.0)
shadow_int = getShadow(ShadowMapSampler, posLightSpace.xy, posLightSpace.z);
else
shadow_int = 1.0;
#endif #endif
shadow_int *= distance_rate;
shadow_int = clamp(shadow_int, 0.0, 1.0);
if (f_normal_length != 0 && cosLight <= 0.001) { }
shadow_int = clamp(shadow_int + 0.5 * abs(cosLight), 0.0, 1.0);
// turns out that nightRatio falls off much faster than
// actual brightness of artificial light in relation to natual light.
// Power ratio was measured on torches in MTG (brightness = 14).
float adjusted_night_ratio = pow(max(0.0, nightRatio), 0.6);
// Apply self-shadowing when light falls at a narrow angle to the surface
// Cosine of the cut-off angle.
const float self_shadow_cutoff_cosine = 0.14;
if (f_normal_length != 0 && cosLight < self_shadow_cutoff_cosine) {
shadow_int = max(shadow_int, 1 - clamp(cosLight, 0.0, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine);
shadow_color = mix(vec3(0.0), shadow_color, min(cosLight, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine);
}
shadow_int *= f_adj_shadow_strength;
// calculate fragment color from components:
col.rgb =
adjusted_night_ratio * col.rgb + // artificial light
(1.0 - adjusted_night_ratio) * ( // natural light
col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color
dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight
} }
shadow_int = 1.0 - (shadow_int * adj_shadow_strength);
col.rgb = mix(shadow_color, col.rgb, shadow_int) * shadow_int;
#endif #endif
#if ENABLE_TONE_MAPPING #if ENABLE_TONE_MAPPING
col = applyToneMapping(col); col = applyToneMapping(col);
#endif #endif
@ -385,6 +539,7 @@ void main(void)
float clarity = clamp(fogShadingParameter float clarity = clamp(fogShadingParameter
- fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0); - fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
col = mix(skyBgColor, col, clarity); col = mix(skyBgColor, col, clarity);
col = vec4(col.rgb, base.a);
gl_FragColor = vec4(col.rgb, base.a);
gl_FragColor = col;
} }

View File

@ -1,7 +1,10 @@
uniform mat4 mWorld; uniform mat4 mWorld;
uniform vec3 dayLight;
uniform vec3 eyePosition; uniform vec3 eyePosition;
uniform float animationTimer; uniform float animationTimer;
uniform vec4 emissiveColor;
uniform vec3 cameraOffset;
varying vec3 vNormal; varying vec3 vNormal;
varying vec3 vPosition; varying vec3 vPosition;
@ -21,19 +24,53 @@ centroid varying vec2 varTexCoord;
uniform float f_shadowfar; uniform float f_shadowfar;
uniform float f_shadow_strength; uniform float f_shadow_strength;
uniform float f_timeofday; uniform float f_timeofday;
uniform vec4 CameraPos;
varying float cosLight; varying float cosLight;
varying float normalOffsetScale;
varying float adj_shadow_strength; varying float adj_shadow_strength;
varying float f_normal_length; varying float f_normal_length;
varying vec3 shadow_position;
#endif #endif
varying vec3 eyeVec; varying vec3 eyeVec;
varying float nightRatio;
// Color of the light emitted by the light sources.
const vec3 artificialLight = vec3(1.04, 1.04, 1.04);
varying float vIDiff; varying float vIDiff;
const float e = 2.718281828459; const float e = 2.718281828459;
const float BS = 10.0; const float BS = 10.0;
uniform float xyPerspectiveBias0;
uniform float xyPerspectiveBias1;
uniform float zPerspectiveBias;
#ifdef ENABLE_DYNAMIC_SHADOWS #ifdef ENABLE_DYNAMIC_SHADOWS
vec4 getRelativePosition(in vec4 position)
{
vec2 l = position.xy - CameraPos.xy;
vec2 s = l / abs(l);
s = (1.0 - s * CameraPos.xy);
l /= s;
return vec4(l, s);
}
float getPerspectiveFactor(in vec4 relativePosition)
{
float pDistance = length(relativePosition.xy);
float pFactor = pDistance * xyPerspectiveBias0 + xyPerspectiveBias1;
return pFactor;
}
vec4 applyPerspectiveDistortion(in vec4 position)
{
vec4 l = getRelativePosition(position);
float pFactor = getPerspectiveFactor(l);
l.xy /= pFactor;
position.xy = l.xy * l.zw + CameraPos.xy;
position.z *= zPerspectiveBias;
return position;
}
// custom smoothstep implementation because it's not defined in glsl1.2 // custom smoothstep implementation because it's not defined in glsl1.2
// https://docs.gl/sl4/smoothstep // https://docs.gl/sl4/smoothstep
float mtsmoothstep(in float edge0, in float edge1, in float x) float mtsmoothstep(in float edge0, in float edge1, in float x)
@ -60,7 +97,7 @@ void main(void)
gl_Position = mWorldViewProj * inVertexPosition; gl_Position = mWorldViewProj * inVertexPosition;
vPosition = gl_Position.xyz; vPosition = gl_Position.xyz;
vNormal = inVertexNormal; vNormal = (mWorld * vec4(inVertexNormal, 0.0)).xyz;
worldPosition = (mWorld * inVertexPosition).xyz; worldPosition = (mWorld * inVertexPosition).xyz;
eyeVec = -(mWorldView * inVertexPosition).xyz; eyeVec = -(mWorldView * inVertexPosition).xyz;
@ -75,29 +112,68 @@ void main(void)
#endif #endif
#ifdef GL_ES #ifdef GL_ES
varColor = inVertexColor.bgra; vec4 color = inVertexColor.bgra;
#else #else
varColor = inVertexColor; vec4 color = inVertexColor;
#endif #endif
color *= emissiveColor;
// The alpha gives the ratio of sunlight in the incoming light.
nightRatio = 1.0 - color.a;
color.rgb = color.rgb * (color.a * dayLight.rgb +
nightRatio * artificialLight.rgb) * 2.0;
color.a = 1.0;
// Emphase blue a bit in darker places
// See C++ implementation in mapblock_mesh.cpp final_color_blend()
float brightness = (color.r + color.g + color.b) / 3.0;
color.b += max(0.0, 0.021 - abs(0.2 * brightness - 0.021) +
0.07 * brightness);
varColor = clamp(color, 0.0, 1.0);
#ifdef ENABLE_DYNAMIC_SHADOWS #ifdef ENABLE_DYNAMIC_SHADOWS
if (f_shadow_strength > 0.0) {
vec3 nNormal = normalize(vNormal);
f_normal_length = length(vNormal);
cosLight = max(0.0, dot(vNormal, -v_LightDirection)); /* normalOffsetScale is in world coordinates (1/10th of a meter)
float texelSize = 0.51; z_bias is in light space coordinates */
float slopeScale = clamp(1.0 - cosLight, 0.0, 1.0); float normalOffsetScale, z_bias;
normalOffsetScale = texelSize * slopeScale; float pFactor = getPerspectiveFactor(getRelativePosition(m_ShadowViewProj * mWorld * inVertexPosition));
if (f_timeofday < 0.2) { if (f_normal_length > 0.0) {
adj_shadow_strength = f_shadow_strength * 0.5 * nNormal = normalize(vNormal);
(1.0 - mtsmoothstep(0.18, 0.2, f_timeofday)); cosLight = dot(nNormal, -v_LightDirection);
} else if (f_timeofday >= 0.8) { float sinLight = pow(1 - pow(cosLight, 2.0), 0.5);
adj_shadow_strength = f_shadow_strength * 0.5 * normalOffsetScale = 0.1 * pFactor * pFactor * sinLight * min(f_shadowfar, 500.0) /
mtsmoothstep(0.8, 0.83, f_timeofday); xyPerspectiveBias1 / f_textureresolution;
} else { z_bias = 1e3 * sinLight / cosLight * (0.5 + f_textureresolution / 1024.0);
adj_shadow_strength = f_shadow_strength * }
mtsmoothstep(0.20, 0.25, f_timeofday) * else {
(1.0 - mtsmoothstep(0.7, 0.8, f_timeofday)); nNormal = vec3(0.0);
cosLight = clamp(dot(v_LightDirection, normalize(vec3(v_LightDirection.x, 0.0, v_LightDirection.z))), 1e-2, 1.0);
float sinLight = pow(1 - pow(cosLight, 2.0), 0.5);
normalOffsetScale = 0.0;
z_bias = 3.6e3 * sinLight / cosLight;
}
z_bias *= pFactor * pFactor / f_textureresolution / f_shadowfar;
shadow_position = applyPerspectiveDistortion(m_ShadowViewProj * mWorld * (inVertexPosition + vec4(normalOffsetScale * nNormal, 0.0))).xyz;
shadow_position.z -= z_bias;
if (f_timeofday < 0.2) {
adj_shadow_strength = f_shadow_strength * 0.5 *
(1.0 - mtsmoothstep(0.18, 0.2, f_timeofday));
} else if (f_timeofday >= 0.8) {
adj_shadow_strength = f_shadow_strength * 0.5 *
mtsmoothstep(0.8, 0.83, f_timeofday);
} else {
adj_shadow_strength = f_shadow_strength *
mtsmoothstep(0.20, 0.25, f_timeofday) *
(1.0 - mtsmoothstep(0.7, 0.8, f_timeofday));
}
} }
f_normal_length = length(vNormal);
#endif #endif
} }

View File

@ -2,6 +2,8 @@ uniform sampler2D ColorMapSampler;
varying vec4 tPos; varying vec4 tPos;
#ifdef COLORED_SHADOWS #ifdef COLORED_SHADOWS
varying vec3 varColor;
// c_precision of 128 fits within 7 base-10 digits // c_precision of 128 fits within 7 base-10 digits
const float c_precision = 128.0; const float c_precision = 128.0;
const float c_precisionp1 = c_precision + 1.0; const float c_precisionp1 = c_precision + 1.0;
@ -30,7 +32,9 @@ void main()
//col.rgb = col.a == 1.0 ? vec3(1.0) : col.rgb; //col.rgb = col.a == 1.0 ? vec3(1.0) : col.rgb;
#ifdef COLORED_SHADOWS #ifdef COLORED_SHADOWS
float packedColor = packColor(mix(col.rgb, black, col.a)); col.rgb *= varColor.rgb;
// premultiply color alpha (see-through side)
float packedColor = packColor(col.rgb * (1.0 - col.a));
gl_FragColor = vec4(depth, packedColor, 0.0,1.0); gl_FragColor = vec4(depth, packedColor, 0.0,1.0);
#else #else
gl_FragColor = vec4(depth, 0.0, 0.0, 1.0); gl_FragColor = vec4(depth, 0.0, 0.0, 1.0);

View File

@ -1,26 +1,50 @@
uniform mat4 LightMVP; // world matrix uniform mat4 LightMVP; // world matrix
uniform vec4 CameraPos;
varying vec4 tPos; varying vec4 tPos;
#ifdef COLORED_SHADOWS
varying vec3 varColor;
#endif
const float bias0 = 0.9; uniform float xyPerspectiveBias0;
const float zPersFactor = 0.5; uniform float xyPerspectiveBias1;
const float bias1 = 1.0 - bias0 + 1e-6; uniform float zPerspectiveBias;
vec4 getPerspectiveFactor(in vec4 shadowPosition) vec4 getRelativePosition(in vec4 position)
{ {
float pDistance = length(shadowPosition.xy); vec2 l = position.xy - CameraPos.xy;
float pFactor = pDistance * bias0 + bias1; vec2 s = l / abs(l);
shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor); s = (1.0 - s * CameraPos.xy);
l /= s;
return shadowPosition; return vec4(l, s);
} }
float getPerspectiveFactor(in vec4 relativePosition)
{
float pDistance = length(relativePosition.xy);
float pFactor = pDistance * xyPerspectiveBias0 + xyPerspectiveBias1;
return pFactor;
}
vec4 applyPerspectiveDistortion(in vec4 position)
{
vec4 l = getRelativePosition(position);
float pFactor = getPerspectiveFactor(l);
l.xy /= pFactor;
position.xy = l.xy * l.zw + CameraPos.xy;
position.z *= zPerspectiveBias;
return position;
}
void main() void main()
{ {
vec4 pos = LightMVP * gl_Vertex; vec4 pos = LightMVP * gl_Vertex;
tPos = getPerspectiveFactor(LightMVP * gl_Vertex); tPos = applyPerspectiveDistortion(LightMVP * gl_Vertex);
gl_Position = vec4(tPos.xyz, 1.0); gl_Position = vec4(tPos.xyz, 1.0);
gl_TexCoord[0].st = gl_MultiTexCoord0.st; gl_TexCoord[0].st = gl_MultiTexCoord0.st;
#ifdef COLORED_SHADOWS
varColor = gl_Color.rgb;
#endif
} }

View File

@ -1,26 +1,43 @@
uniform mat4 LightMVP; // world matrix uniform mat4 LightMVP; // world matrix
uniform vec4 CameraPos; // camera position
varying vec4 tPos; varying vec4 tPos;
const float bias0 = 0.9; uniform float xyPerspectiveBias0;
const float zPersFactor = 0.5; uniform float xyPerspectiveBias1;
const float bias1 = 1.0 - bias0 + 1e-6; uniform float zPerspectiveBias;
vec4 getPerspectiveFactor(in vec4 shadowPosition) vec4 getRelativePosition(in vec4 position)
{ {
float pDistance = length(shadowPosition.xy); vec2 l = position.xy - CameraPos.xy;
float pFactor = pDistance * bias0 + bias1; vec2 s = l / abs(l);
shadowPosition.xyz *= vec3(vec2(1.0 / pFactor), zPersFactor); s = (1.0 - s * CameraPos.xy);
l /= s;
return shadowPosition; return vec4(l, s);
} }
float getPerspectiveFactor(in vec4 relativePosition)
{
float pDistance = length(relativePosition.xy);
float pFactor = pDistance * xyPerspectiveBias0 + xyPerspectiveBias1;
return pFactor;
}
vec4 applyPerspectiveDistortion(in vec4 position)
{
vec4 l = getRelativePosition(position);
float pFactor = getPerspectiveFactor(l);
l.xy /= pFactor;
position.xy = l.xy * l.zw + CameraPos.xy;
position.z *= zPerspectiveBias;
return position;
}
void main() void main()
{ {
vec4 pos = LightMVP * gl_Vertex; vec4 pos = LightMVP * gl_Vertex;
tPos = getPerspectiveFactor(pos); tPos = applyPerspectiveDistortion(pos);
gl_Position = vec4(tPos.xyz, 1.0); gl_Position = vec4(tPos.xyz, 1.0);
gl_TexCoord[0].st = gl_MultiTexCoord0.st; gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
} }

View File

@ -1 +1 @@
name = preview name = preview

View File

@ -1,73 +0,0 @@
#-------------------------------------------------------------------
# The contents of this file are placed in the public domain. Feel
# free to make use of it in any way you like.
#-------------------------------------------------------------------
# - Try to find OpenGLES and EGL
# Once done this will define
#
# OPENGLES2_FOUND - system has OpenGLES
# OPENGLES2_INCLUDE_DIR - the GL include directory
# OPENGLES2_LIBRARIES - Link these to use OpenGLES
#
# EGL_FOUND - system has EGL
# EGL_INCLUDE_DIR - the EGL include directory
# EGL_LIBRARIES - Link these to use EGL
# Win32 and Apple are not tested!
# Linux tested and works
if(WIN32)
find_path(OPENGLES2_INCLUDE_DIR GLES2/gl2.h)
find_library(OPENGLES2_LIBRARY libGLESv2)
elseif(APPLE)
create_search_paths(/Developer/Platforms)
findpkg_framework(OpenGLES2)
set(OPENGLES2_LIBRARY "-framework OpenGLES")
else()
# Unix
find_path(OPENGLES2_INCLUDE_DIR GLES2/gl2.h
PATHS /usr/openwin/share/include
/opt/graphics/OpenGL/include
/usr/X11R6/include
/usr/include
)
find_library(OPENGLES2_LIBRARY
NAMES GLESv2
PATHS /opt/graphics/OpenGL/lib
/usr/openwin/lib
/usr/X11R6/lib
/usr/lib
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(OpenGLES2 DEFAULT_MSG OPENGLES2_LIBRARY OPENGLES2_INCLUDE_DIR)
find_path(EGL_INCLUDE_DIR EGL/egl.h
PATHS /usr/openwin/share/include
/opt/graphics/OpenGL/include
/usr/X11R6/include
/usr/include
)
find_library(EGL_LIBRARY
NAMES EGL
PATHS /opt/graphics/OpenGL/lib
/usr/openwin/lib
/usr/X11R6/lib
/usr/lib
)
find_package_handle_standard_args(EGL DEFAULT_MSG EGL_LIBRARY EGL_INCLUDE_DIR)
endif()
set(OPENGLES2_LIBRARIES ${OPENGLES2_LIBRARY})
set(EGL_LIBRARIES ${EGL_LIBRARY})
mark_as_advanced(
OPENGLES2_INCLUDE_DIR
OPENGLES2_LIBRARY
EGL_INCLUDE_DIR
EGL_LIBRARY
)

View File

@ -1,4 +1,4 @@
mark_as_advanced(SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR) mark_as_advanced(SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR)
find_path(SQLITE3_INCLUDE_DIR sqlite3.h) find_path(SQLITE3_INCLUDE_DIR sqlite3.h)

View File

@ -29,18 +29,17 @@ else(NOT GP2XWIZ)
find_package_handle_standard_args(Vorbis DEFAULT_MSG find_package_handle_standard_args(Vorbis DEFAULT_MSG
VORBIS_INCLUDE_DIR VORBIS_LIBRARY) VORBIS_INCLUDE_DIR VORBIS_LIBRARY)
endif(NOT GP2XWIZ) endif(NOT GP2XWIZ)
if(VORBIS_FOUND) if(VORBIS_FOUND)
if(NOT GP2XWIZ) if(NOT GP2XWIZ)
set(VORBIS_LIBRARIES ${VORBISFILE_LIBRARY} ${VORBIS_LIBRARY} set(VORBIS_LIBRARIES ${VORBISFILE_LIBRARY} ${VORBIS_LIBRARY}
${OGG_LIBRARY}) ${OGG_LIBRARY})
else(NOT GP2XWIZ) else(NOT GP2XWIZ)
set(VORBIS_LIBRARIES ${VORBIS_LIBRARY}) set(VORBIS_LIBRARIES ${VORBIS_LIBRARY})
endif(NOT GP2XWIZ) endif(NOT GP2XWIZ)
else(VORBIS_FOUND) else(VORBIS_FOUND)
set(VORBIS_LIBRARIES) set(VORBIS_LIBRARIES)
endif(VORBIS_FOUND) endif(VORBIS_FOUND)
mark_as_advanced(OGG_INCLUDE_DIR VORBIS_INCLUDE_DIR) mark_as_advanced(OGG_INCLUDE_DIR VORBIS_INCLUDE_DIR)
mark_as_advanced(OGG_LIBRARY VORBIS_LIBRARY VORBISFILE_LIBRARY) mark_as_advanced(OGG_LIBRARY VORBIS_LIBRARY VORBISFILE_LIBRARY)

View File

@ -16,7 +16,6 @@ PREDEFINED = "USE_SPATIAL=1" \
"USE_REDIS=1" \ "USE_REDIS=1" \
"USE_SOUND=1" \ "USE_SOUND=1" \
"USE_CURL=1" \ "USE_CURL=1" \
"USE_FREETYPE=1" \
"USE_GETTEXT=1" "USE_GETTEXT=1"
# Input # Input

8
doc/breakages.md Normal file
View File

@ -0,0 +1,8 @@
# Minetest Major Breakages List
This document contains a list of breaking changes to be made in the next major version.
* Remove attachment space multiplier (*10)
* `get_sky()` returns a table (without arg)
* `game.conf` name/id mess
* remove `depends.txt` / `description.txt` (would simplify ContentDB and Minetest code a little)

View File

@ -1,4 +1,4 @@
Minetest Lua Client Modding API Reference 5.5.0 Minetest Lua Client Modding API Reference 5.6.0
================================================ ================================================
* More information at <http://www.minetest.net/> * More information at <http://www.minetest.net/>
* Developer Wiki: <http://dev.minetest.net/> * Developer Wiki: <http://dev.minetest.net/>
@ -587,6 +587,7 @@ Spatial Vectors
* `vector.floor(v)`: returns a vector, each dimension rounded down * `vector.floor(v)`: returns a vector, each dimension rounded down
* `vector.round(v)`: returns a vector, each dimension rounded to nearest int * `vector.round(v)`: returns a vector, each dimension rounded to nearest int
* `vector.apply(v, func)`: returns a vector * `vector.apply(v, func)`: returns a vector
* `vector.combine(v, w, func)`: returns a vector
* `vector.equals(v1, v2)`: returns a boolean * `vector.equals(v1, v2)`: returns a boolean
For the following functions `x` can be either a vector or a number: For the following functions `x` can be either a vector or a number:
@ -1276,8 +1277,8 @@ Methods:
* returns true if player is in a liquid (This oscillates so that the player jumps a bit above the surface) * returns true if player is in a liquid (This oscillates so that the player jumps a bit above the surface)
* `is_in_liquid_stable()` * `is_in_liquid_stable()`
* returns true if player is in a stable liquid (This is more stable and defines the maximum speed of the player) * returns true if player is in a stable liquid (This is more stable and defines the maximum speed of the player)
* `get_liquid_viscosity()` * `get_move_resistance()`
* returns liquid viscosity (Gets the viscosity of liquid to calculate friction) * returns move resistance of current node, the higher the slower the player moves
* `is_climbing()` * `is_climbing()`
* returns true if player is climbing * returns true if player is climbing
* `swimming_vertical()` * `swimming_vertical()`
@ -1581,7 +1582,7 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or
liquid_type = <string>, -- A string containing "none", "flowing", or "source" *May not exist* liquid_type = <string>, -- A string containing "none", "flowing", or "source" *May not exist*
liquid_alternative_flowing = <string>, -- Alternative node for liquid *May not exist* liquid_alternative_flowing = <string>, -- Alternative node for liquid *May not exist*
liquid_alternative_source = <string>, -- Alternative node for liquid *May not exist* liquid_alternative_source = <string>, -- Alternative node for liquid *May not exist*
liquid_viscosity = <number>, -- How fast the liquid flows *May not exist* liquid_viscosity = <number>, -- How slow the liquid flows *May not exist*
liquid_renewable = <boolean>, -- Whether the liquid makes an infinite source *May not exist* liquid_renewable = <boolean>, -- Whether the liquid makes an infinite source *May not exist*
liquid_range = <number>, -- How far the liquid flows *May not exist* liquid_range = <number>, -- How far the liquid flows *May not exist*
drowning = bool, -- Whether the player will drown in the node drowning = bool, -- Whether the player will drown in the node
@ -1596,6 +1597,7 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or
}, },
legacy_facedir_simple = bool, -- Whether to use old facedir legacy_facedir_simple = bool, -- Whether to use old facedir
legacy_wallmounted = bool -- Whether to use old wallmounted legacy_wallmounted = bool -- Whether to use old wallmounted
move_resistance = <number>, -- How slow players can move through the node *May not exist*
} }
``` ```

View File

@ -55,7 +55,7 @@ modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be author's reputation will not be affected by problems that might be
introduced by others. introduced by others.
Finally, software patents pose a constant threat to the existence of Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a effectively restrict the users of a free program by obtaining a
@ -111,7 +111,7 @@ modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The "work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must former contains code derived from the library, whereas the latter must
be combined with the library in order to run. be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
@ -158,7 +158,7 @@ Library.
You may charge a fee for the physical act of transferring a copy, You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a and you may at your option offer warranty protection in exchange for a
fee. fee.
2. You may modify your copy or copies of the Library or any portion 2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1 distribute such modifications or work under the terms of Section 1
@ -216,7 +216,7 @@ instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in that version instead if you wish.) Do not make any other change in
these notices. these notices.
Once this change is made in a given copy, it is irreversible for Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy. subsequent copies and derivative works made from that copy.
@ -267,7 +267,7 @@ Library will still fall under Section 6.)
distribute the object code for the work under the terms of Section 6. distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6, Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself. whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or 6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work work containing portions of the Library, and distribute that work
@ -329,7 +329,7 @@ restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you use both them and the Library together in an executable that you
distribute. distribute.
7. You may place library facilities that are a work based on the 7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined facilities not covered by this License, and distribute such a combined
@ -370,7 +370,7 @@ subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein. restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with You are not responsible for enforcing compliance by third parties with
this License. this License.
11. If, as a consequence of a court judgment or allegation of patent 11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues), infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or conditions are imposed on you (whether by court order, agreement or
@ -422,7 +422,7 @@ conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by license version number, you may choose any version ever published by
the Free Software Foundation. the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free 14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these, programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is write to the author to ask for permission. For software which is
@ -456,7 +456,7 @@ SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES. DAMAGES.
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest If you develop a new library, and you want it to be of the greatest

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
Minetest Lua Mainmenu API Reference 5.5.0 Minetest Lua Mainmenu API Reference 5.6.0
========================================= =========================================
Introduction Introduction
@ -85,7 +85,9 @@ core.get_video_drivers()
core.get_mapgen_names([include_hidden=false]) -> table of map generator algorithms core.get_mapgen_names([include_hidden=false]) -> table of map generator algorithms
registered in the core (possible in async calls) registered in the core (possible in async calls)
core.get_cache_path() -> path of cache core.get_cache_path() -> path of cache
core.get_temp_path() -> path of temp folder core.get_temp_path([param]) (possible in async calls)
^ param=true: returns path to a temporary file
^ otherwise: returns path to the temporary folder
HTTP Requests HTTP Requests
@ -219,7 +221,24 @@ Package - content which is downloadable from the content db, may or may not be i
* returns path to global user data, * returns path to global user data,
the directory that contains user-provided mods, worlds, games, and texture packs. the directory that contains user-provided mods, worlds, games, and texture packs.
* core.get_modpath() (possible in async calls) * core.get_modpath() (possible in async calls)
* returns path to global modpath * returns path to global modpath in the user path, where mods can be installed
* core.get_modpaths() (possible in async calls)
* returns table of virtual path to global modpaths, where mods have been installed
The difference with "core.get_modpath" is that no mods should be installed in these
directories by Minetest -- they might be read-only.
Ex:
```
{
mods = "/home/user/.minetest/mods",
share = "/usr/share/minetest/mods",
-- Custom dirs can be specified by the MINETEST_MOD_DIR env variable
["/path/to/custom/dir"] = "/path/to/custom/dir",
}
```
* core.get_clientmodpath() (possible in async calls) * core.get_clientmodpath() (possible in async calls)
* returns path to global client-side modpath * returns path to global client-side modpath
* core.get_gamepath() (possible in async calls) * core.get_gamepath() (possible in async calls)

View File

@ -112,6 +112,10 @@ leveldb, and files.
Migrate from current players backend to another. Possible values are sqlite3, Migrate from current players backend to another. Possible values are sqlite3,
leveldb, postgresql, dummy, and files. leveldb, postgresql, dummy, and files.
.TP .TP
.B \-\-migrate-mod-storage <value>
Migrate from current mod storage backend to another. Possible values are
sqlite3, dummy, and files.
.TP
.B \-\-terminal .B \-\-terminal
Display an interactive terminal over ncurses during execution. Display an interactive terminal over ncurses during execution.
@ -119,6 +123,9 @@ Display an interactive terminal over ncurses during execution.
.TP .TP
.B MINETEST_SUBGAME_PATH .B MINETEST_SUBGAME_PATH
Colon delimited list of directories to search for games. Colon delimited list of directories to search for games.
.TP
.B MINETEST_MOD_PATH
Colon delimited list of directories to search for mods.
.SH BUGS .SH BUGS
Please report all bugs at https://github.com/minetest/minetest/issues. Please report all bugs at https://github.com/minetest/minetest/issues.

View File

@ -90,9 +90,10 @@ by texture packs. All existing fallback textures can be found in the directory
* `minimap_mask_square.png`: mask used for the square minimap * `minimap_mask_square.png`: mask used for the square minimap
* `minimap_overlay_round.png`: overlay texture for the round minimap * `minimap_overlay_round.png`: overlay texture for the round minimap
* `minimap_overlay_square.png`: overlay texture for the square minimap * `minimap_overlay_square.png`: overlay texture for the square minimap
* `no_texture_airlike.png`: fallback inventory image for airlike nodes
* `object_marker_red.png`: texture for players on the minimap * `object_marker_red.png`: texture for players on the minimap
* `player_marker.png`: texture for the own player on the square minimap * `player_marker.png`: texture for the own player on the square minimap
* `no_texture_airlike.png`: fallback inventory image for airlike nodes
* `no_texture.png`: fallback image for unspecified textures
* `player.png`: front texture of the 2D upright sprite player * `player.png`: front texture of the 2D upright sprite player
* `player_back.png`: back texture of the 2D upright sprite player * `player_back.png`: back texture of the 2D upright sprite player

View File

@ -129,9 +129,34 @@ Example content (added indentation and - explanations):
backend = sqlite3 - which DB backend to use for blocks (sqlite3, dummy, leveldb, redis, postgresql) backend = sqlite3 - which DB backend to use for blocks (sqlite3, dummy, leveldb, redis, postgresql)
player_backend = sqlite3 - which DB backend to use for player data player_backend = sqlite3 - which DB backend to use for player data
readonly_backend = sqlite3 - optionally readonly seed DB (DB file _must_ be located in "readonly" subfolder) readonly_backend = sqlite3 - optionally readonly seed DB (DB file _must_ be located in "readonly" subfolder)
auth_backend = files - which DB backend to use for authentication data
server_announce = false - whether the server is publicly announced or not server_announce = false - whether the server is publicly announced or not
load_mod_<mod> = false - whether <mod> is to be loaded in this world load_mod_<mod> = false - whether <mod> is to be loaded in this world
auth_backend = files - which DB backend to use for authentication data
For load_mod_<mod>, the possible values are:
* `false` - Do not load the mod.
* `true` - Load the mod from wherever it is found (may cause conflicts if the same mod appears also in some other place).
* `mods/modpack/moddir` - Relative path to the mod
* Must be one of the following:
* `mods/`: mods in the user path's mods folder (ex `/home/user/.minetest/mods`)
* `share/`: mods in the share's mods folder (ex: `/usr/share/minetest/mods`)
* `/path/to/env`: you can use absolute paths to mods inside folders specified with the `MINETEST_MOD_PATH` env variable.
* Other locations and absolute paths are not supported
* Note that `moddir` is the directory name, not the mod name specified in mod.conf.
PostgreSQL backend specific settings:
pgsql_connection = host=127.0.0.1 port=5432 user=mt_user password=mt_password dbname=minetest
pgsql_player_connection = (same parameters as above)
pgsql_readonly_connection = (same parameters as above)
pgsql_auth_connection = (same parameters as above)
Redis backend specific settings:
redis_address = 127.0.0.1 - Redis server address
redis_hash = foo - Database hash
redis_port = 6379 - (optional) connection port
redis_password = hunter2 - (optional) server password
Player File Format Player File Format
=================== ===================
@ -333,6 +358,9 @@ if map format version >= 29:
- 0xffffffff = invalid/unknown timestamp, nothing should be done with the time - 0xffffffff = invalid/unknown timestamp, nothing should be done with the time
difference when loaded difference when loaded
u8 name_id_mapping_version
- Should be zero for map format version 29.
u16 num_name_id_mappings u16 num_name_id_mappings
foreach num_name_id_mappings foreach num_name_id_mappings
u16 id u16 id
@ -434,7 +462,7 @@ if map format version < 29:
u8[name_len] name u8[name_len] name
- Node timers - Node timers
if map format version == 25: if map format version >= 25:
u8 length of the data of a single timer (always 2+4+4=10) u8 length of the data of a single timer (always 2+4+4=10)
u16 num_of_timers u16 num_of_timers
foreach num_of_timers: foreach num_of_timers:

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

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