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

View File

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

View File

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

View File

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

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

7
.gitignore vendored
View File

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

View File

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

View File

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

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/
RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \
mkdir prometheus-cpp/build && \
cd prometheus-cpp/build && \
cmake .. \
cd prometheus-cpp && \
cmake -B build \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DCMAKE_BUILD_TYPE=Release \
-DENABLE_TESTING=0 \
-GNinja && \
ninja && \
ninja install
cmake --build build && \
cmake --install build
RUN git clone --depth=1 https://github.com/minetest/irrlicht/ -b ${IRRLICHT_VERSION} && \
cp -r irrlicht/include /usr/include/irrlichtmt
WORKDIR /usr/src/minetest
RUN mkdir build && \
cd build && \
cmake .. \
RUN cmake -B build \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SERVER=TRUE \
@ -51,8 +48,8 @@ RUN mkdir build && \
-DBUILD_UNITTESTS=FALSE \
-DBUILD_CLIENT=FALSE \
-GNinja && \
ninja && \
ninja install
cmake --build build && \
cmake --install build
ARG DOCKER_IMAGE=alpine:3.14
FROM $DOCKER_IMAGE AS runtime

View File

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

View File

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

View File

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

View File

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

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

View File

@ -3,9 +3,11 @@
<string name="label">Minetest</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="notification_title">Loading Minetest</string>
<string name="notification_description">Less than 1 minute&#8230;</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>

View File

@ -1,21 +1,22 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
project.ext.set("versionMajor", 5) // Version Major
project.ext.set("versionMinor", 5) // Version Minor
project.ext.set("versionMinor", 6) // Version Minor
project.ext.set("versionPatch", 0) // Version Patch
project.ext.set("versionExtra", "-dev") // Version Extra
project.ext.set("versionCode", 32) // Android Version Code
project.ext.set("versionCode", 38) // Android Version Code
// NOTE: +2 after each release!
// +1 for ARM and +1 for ARM64 APK's, because
// each APK must have a larger `versionCode` than the previous
buildscript {
ext.ndk_version = '23.0.7599858'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.1'
classpath 'com.android.tools.build:gradle:7.0.3'
classpath 'de.undercouch:gradle-download-task:4.1.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

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

2
android/gradlew vendored
View File

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

View File

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

View File

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

View File

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

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)
local param = core.deserialize(serialized_param)
@ -8,4 +7,3 @@ function core.job_processor(func, serialized_param)
return retval or core.serialize(nil)
end

View File

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

View File

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

View File

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

View File

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

View File

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

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
function core.global_exists(name)
@ -33,11 +28,6 @@ function meta:__newindex(name, value)
end
declared[name] = true
end
-- Ignore mod namespaces
if WARN_INIT and name ~= core.get_current_modname() then
core.log("warning", ("Global variable %q created at %s.")
:format(name, desc))
end
rawset(self, name, value)
end
@ -54,4 +44,3 @@ function meta:__index(name)
end
setmetatable(_G, meta)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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"),
give_to_singleplayer = false,
})
core.register_privilege("basic_debug", {
description = S("Can view more debug info that might give a gameplay advantage"),
give_to_singleplayer = false,
})
core.register_privilege("debug", {
description = S("Can enable wireframe"),
give_to_singleplayer = false,

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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)

View File

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

View File

@ -16,7 +16,6 @@ PREDEFINED = "USE_SPATIAL=1" \
"USE_REDIS=1" \
"USE_SOUND=1" \
"USE_CURL=1" \
"USE_FREETYPE=1" \
"USE_GETTEXT=1"
# 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/>
* Developer Wiki: <http://dev.minetest.net/>
@ -587,6 +587,7 @@ Spatial Vectors
* `vector.floor(v)`: returns a vector, each dimension rounded down
* `vector.round(v)`: returns a vector, each dimension rounded to nearest int
* `vector.apply(v, func)`: returns a vector
* `vector.combine(v, w, func)`: returns a vector
* `vector.equals(v1, v2)`: returns a boolean
For the following functions `x` can be either a vector or a number:
@ -1276,8 +1277,8 @@ Methods:
* returns true if player is in a liquid (This oscillates so that the player jumps a bit above the surface)
* `is_in_liquid_stable()`
* returns true if player is in a stable liquid (This is more stable and defines the maximum speed of the player)
* `get_liquid_viscosity()`
* returns liquid viscosity (Gets the viscosity of liquid to calculate friction)
* `get_move_resistance()`
* returns move resistance of current node, the higher the slower the player moves
* `is_climbing()`
* returns true if player is climbing
* `swimming_vertical()`
@ -1581,7 +1582,7 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or
liquid_type = <string>, -- A string containing "none", "flowing", or "source" *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_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_range = <number>, -- How far the liquid flows *May not exist*
drowning = bool, -- Whether the player will drown in the node
@ -1596,6 +1597,7 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or
},
legacy_facedir_simple = bool, -- Whether to use old facedir
legacy_wallmounted = bool -- Whether to use old wallmounted
move_resistance = <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
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
@ -111,7 +111,7 @@ modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
@ -158,7 +158,7 @@ Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
@ -216,7 +216,7 @@ instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
@ -267,7 +267,7 @@ Library will still fall under Section 6.)
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
@ -329,7 +329,7 @@ restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
@ -370,7 +370,7 @@ subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
@ -422,7 +422,7 @@ conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
@ -456,7 +456,7 @@ SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest

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

View File

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

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_overlay_round.png`: overlay texture for the round minimap
* `minimap_overlay_square.png`: overlay texture for the square minimap
* `no_texture_airlike.png`: fallback inventory image for airlike nodes
* `object_marker_red.png`: texture for players on the minimap
* `player_marker.png`: texture for the own player on the square minimap
* `no_texture_airlike.png`: fallback inventory image for airlike nodes
* `no_texture.png`: fallback image for unspecified textures
* `player.png`: front texture of the 2D upright sprite player
* `player_back.png`: back texture of the 2D upright sprite player

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)
player_backend = sqlite3 - which DB backend to use for player data
readonly_backend = sqlite3 - optionally readonly seed DB (DB file _must_ be located in "readonly" subfolder)
auth_backend = files - which DB backend to use for authentication data
server_announce = false - whether the server is publicly announced or not
load_mod_<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
===================
@ -333,6 +358,9 @@ if map format version >= 29:
- 0xffffffff = invalid/unknown timestamp, nothing should be done with the time
difference when loaded
u8 name_id_mapping_version
- Should be zero for map format version 29.
u16 num_name_id_mappings
foreach num_name_id_mappings
u16 id
@ -434,7 +462,7 @@ if map format version < 29:
u8[name_len] name
- Node timers
if map format version == 25:
if map format version >= 25:
u8 length of the data of a single timer (always 2+4+4=10)
u16 num_of_timers
foreach num_of_timers:

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