merge upstream

master
Milan 2020-09-24 22:39:19 +02:00
commit ad6043b3c5
1101 changed files with 192793 additions and 61962 deletions

View File

@ -13,6 +13,7 @@ BraceWrapping:
AfterUnion: true
BeforeCatch: false
BeforeElse: false
FixNamespaceComments: false
AllowShortIfStatementsOnASingleLine: false
IndentCaseLabels: false
AccessModifierOffset: -8

View File

@ -1,4 +1,5 @@
Checks: '-*,modernize-use-emplace,modernize-use-default-member-init,modernize-use-equals-delete,modernize-use-equals-default,modernize-return-braced-init-list,modernize-loop-convert,modernize-avoid-bind,misc-throw-by-value-catch-by-reference,misc-string-compare,misc-inefficient-algorithm,misc-inaccurate-erase,misc-incorrect-roundings,misc-unconventional-assign-operator,bugprone-suspicious-memset-usage,performance-*'
Checks: '-*,modernize-use-emplace,modernize-avoid-bind,misc-throw-by-value-catch-by-reference,misc-unconventional-assign-operator,performance-*'
WarningsAsErrors: '-*,modernize-use-emplace,performance-type-promotion-in-math-fn,performance-faster-string-find,performance-implicit-cast-in-loop'
CheckOptions:
- key: modernize-use-default-member-init.UseAssignment
value: True
- key: performance-unnecessary-value-param.AllowedTypes
value: v[23]f;v[23][su](16|32)

View File

@ -1,10 +1,11 @@
##### Issue type
<!-- Pick one below and delete others -->
- Bug report
- Feature request
- Documentation issue
- Build issue
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: Unconfirmed bug
assignees: ''
---
##### Minetest version
<!--
Paste Minetest version between quotes below

View File

@ -0,0 +1,25 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: Feature request
assignees: ''
---
## Problem
A clear and concise description of what the problem is.
ie: Why is this needed?
Ex. I'm always frustrated when [...]
## Solutions
A clear and concise description of what you want to happen.
## Alternatives
A clear and concise description of any alternative solutions or features you've considered.
## Additional context
Add any other context or screenshots about the feature request here.

19
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,19 @@
Add compact, short information about your PR for easier understanding:
- Goal of the PR
- How does the PR work?
- Does it resolve any reported issue?
- If not a bug fix, why is this PR needed? What usecases does it solve?
## To do
This PR is a Work in Progress / Ready for Review.
<!-- ^ delete one -->
- [ ] List
- [ ] Things
- [ ] To do
## How to test
<!-- Example code or instructions -->

20
.github/SECURITY.md vendored Normal file
View File

@ -0,0 +1,20 @@
# Security Policy
## Supported Versions
We only support the latest stable version for security issues.
See the [releases page](https://github.com/minetest/minetest/releases).
## Reporting a Vulnerability
We ask that you report vulnerabilities privately, by contacting a core developer,
to give us time to fix them. You can do that by emailing one of the following addresses:
* celeron55@gmail.com
* rubenwardy@minetest.net
Depending on severity, we will either create a private issue for the vulnerability
and release a patch version of Minetest, or give you permission to file the issue publicly.
For more information on the justification of this policy, see
[Responsible Disclosure](https://en.wikipedia.org/wiki/Responsible_disclosure).

289
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,289 @@
name: build
# build on c/cpp changes or workflow changes
on:
push:
paths:
- 'lib/**.[ch]'
- 'lib/**.cpp'
- 'src/**.[ch]'
- 'src/**.cpp'
- '**/CMakeLists.txt'
- 'cmake/Modules/**'
- 'util/buildbot/**'
- 'util/ci/**'
- '.github/workflows/**.yml'
pull_request:
paths:
- 'lib/**.[ch]'
- 'lib/**.cpp'
- 'src/**.[ch]'
- 'src/**.cpp'
- '**/CMakeLists.txt'
- 'cmake/Modules/**'
- 'util/buildbot/**'
- 'util/ci/**'
- '.github/workflows/**.yml'
jobs:
# This is our minor gcc compiler
gcc_6:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
sudo apt-get install g++-6 gcc-6 -qyy
source ./util/ci/common.sh
install_linux_deps
- name: Build
run: |
./util/ci/build.sh
env:
CC: gcc-6
CXX: g++-6
- name: Test
run: |
./bin/minetest --run-unittests
# This is the current gcc compiler (available in bionic)
gcc_8:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
sudo apt-get install g++-8 gcc-8 -qyy
source ./util/ci/common.sh
install_linux_deps
- name: Build
run: |
./util/ci/build.sh
env:
CC: gcc-8
CXX: g++-8
- name: Test
run: |
./bin/minetest --run-unittests
# This is our minor clang compiler
clang_3_9:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
sudo apt-get install clang-3.9 -qyy
source ./util/ci/common.sh
install_linux_deps
- name: Build
run: |
./util/ci/build.sh
env:
CC: clang-3.9
CXX: clang++-3.9
- name: Test
run: |
./bin/minetest --run-unittests
# This is the current clang version
clang_9:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
sudo apt-get install clang-9 valgrind -qyy
source ./util/ci/common.sh
install_linux_deps
env:
WITH_LUAJIT: 1
- name: Build
run: |
./util/ci/build.sh
env:
CC: clang-9
CXX: clang++-9
- name: Test
run: |
./bin/minetest --run-unittests
- name: Valgrind
run: |
valgrind --leak-check=full --leak-check-heuristics=all --undef-value-errors=no --error-exitcode=9 ./bin/minetest --run-unittests
# Build with prometheus-cpp (server-only)
clang_9_prometheus:
name: "clang_9 (PROMETHEUS=1)"
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
sudo apt-get install clang-9 -qyy
source ./util/ci/common.sh
install_linux_deps
- name: Build prometheus-cpp
run: |
./util/ci/build_prometheus_cpp.sh
- name: Build
run: |
./util/ci/build.sh
env:
CC: clang-9
CXX: clang++-9
CMAKE_FLAGS: "-DENABLE_PROMETHEUS=1 -DBUILD_CLIENT=0"
- name: Test
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: |
sudo apt-get install clang-9 -qyy
source ./util/ci/common.sh
install_linux_deps
- 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
steps:
- uses: actions/checkout@v2
- name: Build docker image
run: |
docker build .
win32:
name: "MinGW cross-compiler (32-bit)"
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Install compiler
run: |
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 tar -xaf mingw.tar.xz -C /usr
- name: Build
run: |
EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh winbuild
env:
NO_MINETEST_GAME: 1
NO_PACKAGE: 1
win64:
name: "MinGW cross-compiler (64-bit)"
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Install compiler
run: |
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 tar -xaf mingw.tar.xz -C /usr
- name: Build
run: |
EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh winbuild
env:
NO_MINETEST_GAME: 1
NO_PACKAGE: 1
msvc:
name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }}
runs-on: windows-2019
env:
VCPKG_VERSION: c7ab9d3110813979a873b2dbac630a9ab79850dc
# 2020.04
vcpkg_packages: irrlicht zlib curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit
strategy:
fail-fast: false
matrix:
config:
- {
arch: x86,
generator: "-G'Visual Studio 16 2019' -A Win32",
vcpkg_triplet: x86-windows
}
- {
arch: x64,
generator: "-G'Visual Studio 16 2019' -A x64",
vcpkg_triplet: x64-windows
}
type: [portable]
# type: [portable, installer]
# The installer type is working, but disabled, to save runner jobs.
# Enable it, when working on the installer.
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Restore from cache and run vcpkg
uses: lukka/run-vcpkg@v2
with:
vcpkgArguments: ${{env.vcpkg_packages}}
vcpkgDirectory: '${{ github.workspace }}\vcpkg'
appendedCacheKey: ${{ matrix.config.vcpkg_triplet }}
vcpkgGitCommitId: ${{ env.VCPKG_VERSION }}
vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }}
- name: CMake
run: |
cmake ${{matrix.config.generator}} `
-DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}\vcpkg\scripts\buildsystems\vcpkg.cmake" `
-DCMAKE_BUILD_TYPE=Release `
-DENABLE_POSTGRESQL=OFF `
-DRUN_IN_PLACE=${{ contains(matrix.type, 'portable') }} .
- name: Build
run: cmake --build . --config Release
- name: CPack
run: |
If ($env:TYPE -eq "installer")
{
cpack -G WIX -B "$env:GITHUB_WORKSPACE\Package"
}
ElseIf($env:TYPE -eq "portable")
{
cpack -G ZIP -B "$env:GITHUB_WORKSPACE\Package"
}
env:
TYPE: ${{matrix.type}}
- name: Package Clean
run: rm -r $env:GITHUB_WORKSPACE\Package\_CPack_Packages
- uses: actions/upload-artifact@v1
with:
name: msvc-${{ matrix.config.arch }}-${{ matrix.type }}
path: .\Package\

54
.github/workflows/cpp_lint.yml vendored Normal file
View File

@ -0,0 +1,54 @@
name: cpp_lint
# lint on c/cpp changes or workflow changes
on:
push:
paths:
- 'lib/**.[ch]'
- 'lib/**.cpp'
- 'src/**.[ch]'
- 'src/**.cpp'
- '**/CMakeLists.txt'
- 'cmake/Modules/**'
- 'util/ci/**'
- '.github/workflows/**.yml'
pull_request:
paths:
- 'lib/**.[ch]'
- 'lib/**.cpp'
- 'src/**.[ch]'
- 'src/**.cpp'
- '**/CMakeLists.txt'
- 'cmake/Modules/**'
- 'util/ci/**'
- '.github/workflows/**.yml'
jobs:
clang_format:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Install clang-format
run: |
sudo apt-get install clang-format-9 -qyy
- name: Run clang-format
run: |
source ./util/ci/lint.sh
perform_lint
env:
CLANG_FORMAT: clang-format-9
clang_tidy:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
sudo apt-get install clang-tidy-9 -qyy
source ./util/ci/common.sh
install_linux_deps
- name: Run clang-tidy
run: |
./util/ci/clang-tidy.sh

32
.github/workflows/lua_lint.yml vendored Normal file
View File

@ -0,0 +1,32 @@
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

43
.gitignore vendored
View File

@ -1,8 +1,9 @@
## Editors and Development environments
## Editors and development environments
*~
*.swp
*.bak*
*.orig
.DS_Store
# Vim
*.vim
# Kate
@ -21,11 +22,16 @@
tags
!tags/
gtags.files
.idea/*
.idea
# Codelite
*.project
# Visual Studio Code & plugins
.vscode/
build/.cmake/
# Gradle
.gradle
## Files related to minetest development cycle
## Files related to Minetest development cycle
/*.patch
*.diff
# GNU Patch reject file
@ -34,7 +40,7 @@ gtags.files
## Non-static Minetest directories or symlinks to these
/bin/
/games/*
!/games/minimal/
!/games/devtest/
/cache
/textures/*
!/textures/base/
@ -53,8 +59,9 @@ gtags.files
## Configuration/log files
minetest.conf
debug.txt
debug.txt.1
## Other files generated by minetest
## Other files generated by Minetest
screenshot_*.png
testbm.txt
@ -63,20 +70,19 @@ doc/Doxyfile
doc/html/
doc/doxygen_*
## MkDocs files
public/
doc/mkdocs/docs/*.md
doc/mkdocs/mkdocs.yml
## Build files
CMakeFiles
Makefile
!build/android/Makefile
build/android/path.cfg
build/android/*.apk
build/android/.externalNativeBuild
cmake_install.cmake
CMakeCache.txt
CPackConfig.cmake
CPackSourceConfig.cmake
src/test_config.h
src/android_version.h
src/android_version_githash.h
src/cmake_config.h
src/cmake_config_githash.h
src/unittest/test_world/world.mt
@ -98,16 +104,5 @@ cmake_config.h
cmake_config_githash.h
CMakeDoxy*
compile_commands.json
## Android build files
build/android/src/main/assets
build/android/build
build/android/deps
build/android/libs
build/android/jni/lib
build/android/jni/src
build/android/src/main/jniLibs
build/android/obj
build/android/local.properties
build/android/.gradle
timestamp
*.apk
*.zip

View File

@ -12,7 +12,7 @@ variables:
MINETEST_GAME_REPO: "https://github.com/minetest/minetest_game.git"
CONTAINER_IMAGE: registry.gitlab.com/$CI_PROJECT_PATH
.build_template: &build_definition
.build_template:
stage: build
script:
- mkdir cmakebuild
@ -27,7 +27,7 @@ variables:
paths:
- artifact/*
.debpkg_template: &debpkg_template
.debpkg_template:
stage: package
before_script:
- apt-get update -y
@ -47,7 +47,7 @@ variables:
paths:
- ./*.deb
.debpkg_install: &debpkg_install
.debpkg_install:
stage: deploy
before_script:
- apt-get update -y
@ -62,7 +62,7 @@ variables:
# Jessie
build:debian-8:
<<: *build_definition
extends: .build_template
image: debian:8
before_script:
- echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu trusty main" > /etc/apt/sources.list.d/uptodate-toolchain.list
@ -74,46 +74,70 @@ build:debian-8:
CXX: g++-6
package:debian-8:
extends: .debpkg_template
image: debian:8
dependencies:
- build:debian-8
variables:
LEVELDB_PKG: libleveldb1
<<: *debpkg_template
deploy:debian-8:
extends: .debpkg_install
image: debian:8
dependencies:
- package:debian-8
variables:
LEVELDB_PKG: libleveldb1
<<: *debpkg_install
# Stretch
build:debian-9:
<<: *build_definition
extends: .build_template
image: debian:9
before_script:
- apt-get update -y
- apt-get -y install build-essential libirrlicht-dev cmake libbz2-dev 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
package:debian-9:
extends: .debpkg_template
image: debian:9
dependencies:
- build:debian-9
variables:
LEVELDB_PKG: libleveldb1v5
<<: *debpkg_template
deploy:debian-9:
extends: .debpkg_install
image: debian:9
dependencies:
- package:debian-9
variables:
LEVELDB_PKG: libleveldb1v5
<<: *debpkg_install
# Stretch
build:debian-10:
extends: .build_template
image: debian:10
before_script:
- apt-get update -y
- apt-get -y install build-essential libirrlicht-dev cmake libbz2-dev 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
package:debian-10:
extends: .debpkg_template
image: debian:10
dependencies:
- build:debian-10
variables:
LEVELDB_PKG: libleveldb1d
deploy:debian-10:
extends: .debpkg_install
image: debian:10
dependencies:
- package:debian-10
variables:
LEVELDB_PKG: libleveldb1d
##
## Ubuntu
##
@ -121,7 +145,7 @@ deploy:debian-9:
# Trusty
build:ubuntu-14.04:
<<: *build_definition
extends: .build_template
image: ubuntu:trusty
before_script:
- echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu trusty main" > /etc/apt/sources.list.d/uptodate-toolchain.list
@ -133,102 +157,53 @@ build:ubuntu-14.04:
CXX: g++-6
package:ubuntu-14.04:
extends: .debpkg_template
image: ubuntu:trusty
dependencies:
- build:ubuntu-14.04
variables:
LEVELDB_PKG: libleveldb1
<<: *debpkg_template
deploy:ubuntu-14.04:
extends: .debpkg_install
image: ubuntu:trusty
dependencies:
- package:ubuntu-14.04
variables:
LEVELDB_PKG: libleveldb1
<<: *debpkg_install
# Xenial
build:ubuntu-16.04:
<<: *build_definition
extends: .build_template
image: ubuntu:xenial
before_script:
- apt-get update -y
- apt-get -y install build-essential libirrlicht-dev cmake libbz2-dev 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
package:ubuntu-16.04:
extends: .debpkg_template
image: ubuntu:xenial
dependencies:
- build:ubuntu-16.04
variables:
LEVELDB_PKG: libleveldb1v5
<<: *debpkg_template
deploy:ubuntu-16.04:
extends: .debpkg_install
image: ubuntu:xenial
dependencies:
- package:ubuntu-16.04
variables:
LEVELDB_PKG: libleveldb1v5
<<: *debpkg_install
# Yakkety
#build:ubuntu-16.10:
# <<: *build_definition
# image: ubuntu:yakkety
# before_script:
# - apt-get update -y
# - apt-get -y install build-essential libirrlicht-dev cmake libbz2-dev 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
#package:ubuntu-16.10:
# image: ubuntu:yakkety
# dependencies:
# - build:ubuntu-16.10
# variables:
# LEVELDB_PKG: libleveldb1v5
# <<: *debpkg_template
#deploy:ubuntu-16.10:
# image: ubuntu:yakkety
# dependencies:
# - package:ubuntu-16.10
# variables:
# LEVELDB_PKG: libleveldb1v5
# <<: *debpkg_install
# Zesty
#build:ubuntu-17.04:
# <<: *build_definition
# image: ubuntu:zesty
# before_script:
# - apt-get update -y
# - apt-get -y install build-essential libirrlicht-dev cmake libbz2-dev 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
#package:ubuntu-17.04:
# image: ubuntu:zesty
# dependencies:
# - build:ubuntu-17.04
# variables:
# LEVELDB_PKG: libleveldb1v5
# <<: *debpkg_template
#deploy:ubuntu-17.04:
# image: ubuntu:zesty
# dependencies:
# - package:ubuntu-17.04
# variables:
# LEVELDB_PKG: libleveldb1v5
# <<: *debpkg_install
##
## Fedora
##
# Do we need to support this old version ?
build:fedora-24:
<<: *build_definition
extends: .build_template
image: fedora:24
before_script:
- dnf -y install make automake gcc gcc-c++ kernel-devel cmake libcurl* openal* libvorbis* libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel irrlicht-devel bzip2-libs gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel doxygen spatialindex-devel bzip2-devel
@ -238,17 +213,16 @@ build:fedora-24:
## Mingw for Windows
##
.generic_win_template: &generic_win_template
image: ubuntu:xenial
.generic_win_template:
image: ubuntu:bionic
before_script:
- apt-get update -y
- apt-get install -y p7zip-full wget unzip git cmake gettext
- wget http://minetest.kitsunemimi.pw/mingw-w64-${WIN_ARCH}_7.1.1_ubuntu14.04.7z -O mingw.7z > /dev/null
- sed -e "s|%PREFIX%|${WIN_ARCH}-w64-mingw32|" -e "s|%ROOTPATH%|/usr/${WIN_ARCH}-w64-mingw32|" < util/travis/toolchain_mingw.cmake.in > ${TOOLCHAIN_OUTPUT}
- 7z x -y -o/usr mingw.7z > /dev/null
- apt-get install -y wget xz-utils unzip git cmake gettext
- wget -q http://minetest.kitsunemimi.pw/mingw-w64-${WIN_ARCH}_9.2.0_ubuntu18.04.tar.xz -O mingw.tar.xz
- tar -xaf mingw.tar.xz -C /usr
.build_win_template: &build_win_template
<<: *generic_win_template
.build_win_template:
extends: .generic_win_template
stage: build
artifacts:
when: on_success
@ -256,8 +230,8 @@ build:fedora-24:
paths:
- build/*
.package_win_template: &package_win_template
<<: *generic_win_template
.package_win_template:
extends: .generic_win_template
stage: package
script:
- cd build/minetest/_build
@ -275,40 +249,36 @@ build:fedora-24:
- minetest-win-*/*
build:win32:
<<: *build_win_template
extends: .build_win_template
script:
- ./util/buildbot/buildwin32.sh build
variables:
NO_PACKAGE: "1"
WIN_ARCH: "i686"
TOOLCHAIN_OUTPUT: "util/buildbot/toolchain_mingw.cmake"
package:win32:
<<: *package_win_template
extends: .package_win_template
dependencies:
- build:win32
variables:
NO_PACKAGE: "1"
WIN_ARCH: "i686"
TOOLCHAIN_OUTPUT: "util/buildbot/toolchain_mingw.cmake"
build:win64:
<<: *build_win_template
extends: .build_win_template
script:
- ./util/buildbot/buildwin64.sh build
variables:
NO_PACKAGE: "1"
WIN_ARCH: "x86_64"
TOOLCHAIN_OUTPUT: "util/buildbot/toolchain_mingw64.cmake"
package:win64:
<<: *package_win_template
extends: .package_win_template
dependencies:
- build:win64
variables:
NO_PACKAGE: "1"
WIN_ARCH: "x86_64"
TOOLCHAIN_OUTPUT: "util/buildbot/toolchain_mingw64.cmake"
package:docker:
stage: package
@ -322,3 +292,19 @@ package:docker:
- docker push ${CONTAINER_IMAGE}/server:$CI_COMMIT_SHA
- docker push ${CONTAINER_IMAGE}/server:$CI_COMMIT_REF_NAME
- docker push ${CONTAINER_IMAGE}/server:latest
pages:
stage: deploy
image: python:3.8
before_script:
- pip install git+https://github.com/Python-Markdown/markdown.git
- pip install git+https://github.com/mkdocs/mkdocs.git
- pip install pygments
script:
- cd doc/mkdocs && ./build.sh
artifacts:
paths:
- public
only:
- master

82
.luacheckrc Normal file
View File

@ -0,0 +1,82 @@
unused_args = false
allow_defined_top = true
ignore = {
"131", -- Unused global variable
"431", -- Shadowing an upvalue
"432", -- Shadowing an upvalue argument
}
read_globals = {
"ItemStack",
"INIT",
"DIR_DELIM",
"dump", "dump2",
"fgettext", "fgettext_ne",
"vector",
"VoxelArea",
"profiler",
"Settings",
string = {fields = {"split", "trim"}},
table = {fields = {"copy", "getn", "indexof", "insert_all"}},
math = {fields = {"hypot"}},
}
globals = {
"core",
"gamedata",
os = { fields = { "tempfolder" } },
"_",
}
files["builtin/client/register.lua"] = {
globals = {
debug = {fields={"getinfo"}},
}
}
files["builtin/common/misc_helpers.lua"] = {
globals = {
"dump", "dump2", "table", "math", "string",
"fgettext", "fgettext_ne", "basic_dump", "game", -- ???
"file_exists", "get_last_folder", "cleanup_path", -- ???
},
}
files["builtin/common/vector.lua"] = {
globals = { "vector" },
}
files["builtin/game/voxelarea.lua"] = {
globals = { "VoxelArea" },
}
files["builtin/game/init.lua"] = {
globals = { "profiler" },
}
files["builtin/common/filterlist.lua"] = {
globals = {
"filterlist",
"compare_worlds", "sort_worlds_alphabetic", "sort_mod_list", -- ???
},
}
files["builtin/mainmenu"] = {
globals = {
"gamedata",
},
read_globals = {
"PLATFORM",
},
}
files["builtin/common/tests"] = {
read_globals = {
"describe",
"it",
"assert",
},
}

View File

@ -1,109 +0,0 @@
language: cpp
before_install: ./util/travis/before_install.sh
script: ./util/travis/script.sh
sudo: required
dist: trusty
group: edge
notifications:
email: false
matrix:
fast_finish: true
include:
- env: LINT=1
compiler: clang
os: linux
addons:
apt:
packages: ['clang-format-5.0']
sources: &sources
- llvm-toolchain-trusty-5.0
- env: CLANG_TIDY=1
compiler: clang
os: linux
script: ./util/travis/clangtidy.sh
addons:
apt:
packages: ['clang-tidy-5.0']
sources: &sources
- llvm-toolchain-trusty-5.0
- env: PLATFORM=Win32
compiler: gcc
os: linux
addons:
apt:
packages: ['gcc-mingw-w64-i686', 'g++-mingw-w64-i686', 'binutils-mingw-w64-i686']
sources: &sources
- ubuntu-toolchain-r-test
- sourceline: 'deb http://mirrors.kernel.org/ubuntu xenial main universe'
- env: PLATFORM=Win64
compiler: gcc
os: linux
addons:
apt:
packages: ['gcc-mingw-w64-x86-64', 'g++-mingw-w64-x86-64', 'binutils-mingw-w64-x86-64']
sources: &sources
- ubuntu-toolchain-r-test
- sourceline: 'deb http://mirrors.kernel.org/ubuntu xenial main universe'
- env: PLATFORM=Unix
compiler: clang
os: osx
osx_image: xcode8
- env: PLATFORM=Unix COMPILER=gcc-6
compiler: gcc
os: linux
addons:
apt:
packages: ['gcc-6', 'g++-6']
sources: &sources
- ubuntu-toolchain-r-test
- env: PLATFORM=Unix COMPILER=gcc-7
compiler: gcc
os: linux
addons:
apt:
packages: ['gcc-7', 'g++-7']
sources: &sources
- ubuntu-toolchain-r-test
- env: PLATFORM=Unix COMPILER=clang-3.6
compiler: clang
os: linux
addons:
apt:
packages: ['clang-3.6', 'clang++-3.6']
sources: &sources
- llvm-toolchain-trusty-3.6
- env: PLATFORM=Unix COMPILER=clang-5.0
compiler: clang
os: linux
addons:
apt:
packages: ['clang-5.0', 'clang++-5.0']
sources: &sources
- llvm-toolchain-trusty-5.0
- env: PLATFORM=Unix COMPILER=clang-5.0 FREETYPE=0
compiler: clang
os: linux
addons:
apt:
packages: ['clang-5.0', 'clang++-5.0']
sources: &sources
- llvm-toolchain-trusty-5.0
- env: PLATFORM=Unix COMPILER=clang-5.0 VALGRIND=1
compiler: clang
os: linux
addons:
apt:
packages: ['valgrind', 'clang-5.0', 'clang++-5.0']
sources: &sources
- llvm-toolchain-trusty-5.0

View File

@ -16,7 +16,7 @@ set(CLANG_MINIMUM_VERSION "3.4")
# Also remember to set PROTOCOL_VERSION in network/networkprotocol.h when releasing
set(VERSION_MAJOR 5)
set(VERSION_MINOR 1)
set(VERSION_MINOR 4)
set(VERSION_PATCH 0)
set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")
@ -49,6 +49,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(WARN_ALL TRUE CACHE BOOL "Enable -Wall for Release build")
@ -102,7 +103,7 @@ elseif(UNIX) # Linux, BSD etc
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/${PROJECT_NAME}/locale")
set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/share/locale")
endif()
endif()
@ -166,22 +167,22 @@ endif()
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/games/minetest_game" DESTINATION "${SHAREDIR}/games/"
COMPONENT "SUBGAME_MINETEST_GAME" OPTIONAL PATTERN ".git*" EXCLUDE )
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/games/minimal" DESTINATION "${SHAREDIR}/games/"
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/games/devtest" DESTINATION "${SHAREDIR}/games/"
COMPONENT "SUBGAME_MINIMAL" OPTIONAL PATTERN ".git*" EXCLUDE )
if(BUILD_CLIENT)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/client/shaders" DESTINATION "${SHAREDIR}/client")
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/textures/base/pack" DESTINATION "${SHAREDIR}/textures/base")
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/fonts" DESTINATION "${SHAREDIR}")
if(RUN_IN_PLACE)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/clientmods" DESTINATION "${SHAREDIR}")
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/client/serverlist" DESTINATION "${SHAREDIR}/client")
endif()
endif()
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/fonts" DESTINATION "${SHAREDIR}")
install(FILES "README.md" DESTINATION "${DOCDIR}" COMPONENT "Docs")
install(FILES "doc/lua_api.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs")
install(FILES "doc/client_lua_api.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs")
install(FILES "doc/menu_lua_api.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs")
install(FILES "doc/texture_packs.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs")
install(FILES "doc/world_format.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs")
@ -232,7 +233,7 @@ add_subdirectory(src)
# CPack
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "An InfiniMiner/Minecraft inspired game")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A free open-source voxel game engine with easy modding and game creation.")
set(CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${VERSION_PATCH})
@ -253,8 +254,8 @@ cpack_add_component(SUBGAME_MINETEST_GAME
)
cpack_add_component(SUBGAME_MINIMAL
DISPLAY_NAME "Minimal development test"
DESCRIPTION "A minimal subgame helping to develop the engine."
DISPLAY_NAME "Development Test"
DESCRIPTION "A minimal test game helping to develop the engine."
DISABLED #DISABLED does not mean it is disabled, and is just not selected by default.
GROUP "Subgames"
)
@ -325,4 +326,3 @@ if(DOXYGEN_FOUND)
COMMENT "Generating API documentation with Doxygen" VERBATIM
)
endif()

View File

@ -1,31 +1,59 @@
FROM debian:stretch
FROM alpine:3.11
USER root
RUN apt-get update -y && \
apt-get -y install build-essential libirrlicht-dev cmake libbz2-dev libpng-dev libjpeg-dev \
libsqlite3-dev libcurl4-gnutls-dev zlib1g-dev libgmp-dev libjsoncpp-dev git && \
apt-get clean && rm -rf /var/cache/apt/archives/* && \
rm -rf /var/lib/apt/lists/*
ENV MINETEST_GAME_VERSION master
COPY . /usr/src/minetest
COPY .git /usr/src/minetest/.git
COPY CMakeLists.txt /usr/src/minetest/CMakeLists.txt
COPY README.md /usr/src/minetest/README.md
COPY minetest.conf.example /usr/src/minetest/minetest.conf.example
COPY builtin /usr/src/minetest/builtin
COPY cmake /usr/src/minetest/cmake
COPY doc /usr/src/minetest/doc
COPY fonts /usr/src/minetest/fonts
COPY lib /usr/src/minetest/lib
COPY misc /usr/src/minetest/misc
COPY po /usr/src/minetest/po
COPY src /usr/src/minetest/src
COPY textures /usr/src/minetest/textures
RUN mkdir -p /usr/src/minetest/cmakebuild && cd /usr/src/minetest/cmakebuild && \
cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Release -DRUN_IN_PLACE=FALSE \
WORKDIR /usr/src/minetest
RUN apk add --no-cache git build-base irrlicht-dev cmake bzip2-dev libpng-dev \
jpeg-dev libxxf86vm-dev mesa-dev sqlite-dev libogg-dev \
libvorbis-dev openal-soft-dev curl-dev freetype-dev zlib-dev \
gmp-dev jsoncpp-dev postgresql-dev ca-certificates && \
git clone --depth=1 -b ${MINETEST_GAME_VERSION} https://github.com/minetest/minetest_game.git ./games/minetest_game && \
rm -fr ./games/minetest_game/.git
WORKDIR /usr/src/
RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \
mkdir prometheus-cpp/build && \
cd prometheus-cpp/build && \
cmake .. \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DCMAKE_BUILD_TYPE=Release \
-DENABLE_TESTING=0 && \
make -j2 && \
make install
WORKDIR /usr/src/minetest
RUN mkdir build && \
cd build && \
cmake .. \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SERVER=TRUE \
-DBUILD_CLIENT=FALSE \
-DENABLE_SYSTEM_JSONCPP=1 \
.. && \
make -j2 && \
rm -Rf ../games/minetest_game && git clone https://github.com/minetest/minetest_game ../games/minetest_game && \
make install
-DENABLE_PROMETHEUS=TRUE \
-DBUILD_UNITTESTS=FALSE \
-DBUILD_CLIENT=FALSE && \
make -j2 && \
make install
FROM debian:stretch
FROM alpine:3.11
USER root
RUN groupadd minetest && useradd -m -g minetest -d /var/lib/minetest minetest && \
apt-get update -y && \
apt-get -y install libcurl3-gnutls libjsoncpp1 liblua5.1-0 libluajit-5.1-2 libpq5 libsqlite3-0 \
libstdc++6 zlib1g libc6
RUN apk add --no-cache sqlite-libs curl gmp libstdc++ libgcc libpq && \
adduser -D minetest --uid 30000 -h /var/lib/minetest && \
chown -R minetest:minetest /var/lib/minetest
WORKDIR /var/lib/minetest
@ -33,8 +61,8 @@ COPY --from=0 /usr/local/share/minetest /usr/local/share/minetest
COPY --from=0 /usr/local/bin/minetestserver /usr/local/bin/minetestserver
COPY --from=0 /usr/local/share/doc/minetest/minetest.conf.example /etc/minetest/minetest.conf
USER minetest
USER minetest:minetest
EXPOSE 30000/udp
EXPOSE 30000/udp 30000/tcp
CMD ["/usr/local/bin/minetestserver", "--config", "/etc/minetest/minetest.conf"]

View File

@ -21,6 +21,12 @@ ShadowNinja:
paramat:
textures/base/pack/menu_header.png
textures/base/pack/next_icon.png
textures/base/pack/prev_icon.png
rubenwardy, paramat:
textures/base/pack/start_icon.png
textures/base/pack/end_icon.png
erlehmann:
misc/minetest-icon-24x24.png

11
build/android/.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
*.iml
.externalNativeBuild
.gradle
app/build
app/release
app/src/main/assets
build
local.properties
native/.*
native/build
native/deps

View File

@ -1,766 +0,0 @@
# build options
OS := $(shell uname)
# compile with GPROF
# GPROF = 1
# build for build platform
API = 14
APP_PLATFORM = android-$(API)
ANDR_ROOT = $(shell pwd)
PROJ_ROOT = $(shell realpath $(ANDR_ROOT)/../..)
APP_ROOT = $(ANDR_ROOT)/src/main
VERSION_MAJOR := $(shell cat $(PROJ_ROOT)/CMakeLists.txt | \
grep ^set\(VERSION_MAJOR\ | sed 's/)/ /' | cut -f2 -d' ')
VERSION_MINOR := $(shell cat $(PROJ_ROOT)/CMakeLists.txt | \
grep ^set\(VERSION_MINOR\ | sed 's/)/ /' | cut -f2 -d' ')
VERSION_PATCH := $(shell cat $(PROJ_ROOT)/CMakeLists.txt | \
grep ^set\(VERSION_PATCH\ | sed 's/)/ /' | cut -f2 -d' ')
################################################################################
# toolchain config for arm new processors
################################################################################
TARGET_HOST = arm-linux
TARGET_ABI = armeabi-v7a
TARGET_LIBDIR = armeabi-v7a
TARGET_TOOLCHAIN = arm-linux-androideabi-
TARGET_CFLAGS_ADDON = -mfloat-abi=softfp -mfpu=vfpv3 -O3
TARGET_CXXFLAGS_ADDON = $(TARGET_CFLAGS_ADDON)
TARGET_ARCH = armv7
CROSS_CC = clang
CROSS_CXX = clang++
COMPILER_VERSION = clang
HAVE_LEVELDB = 0
################################################################################
# toolchain config for little endian mips
################################################################################
#TARGET_HOST = mipsel-linux
#TARGET_ABI = mips
#TARGET_LIBDIR = mips
#TARGET_TOOLCHAIN = mipsel-linux-android-
#TARGET_ARCH = mips32
#CROSS_CC = mipsel-linux-android-gcc
#CROSS_CXX = mipsel-linux-android-g++
#COMPILER_VERSION = 4.9
#HAVE_LEVELDB = 0
################################################################################
# toolchain config for x86
################################################################################
#TARGET_HOST = x86-linux
#TARGET_ABI = x86
#TARGET_LIBDIR = x86
#TARGET_TOOLCHAIN = x86-
#TARGET_ARCH = x86
#CROSS_CC = clang
#CROSS_CXX = clang++
#COMPILER_VERSION = clang
#HAVE_LEVELDB = 0
################################################################################
ASSETS_TIMESTAMP = deps/assets_timestamp
LEVELDB_DIR = $(ANDR_ROOT)/deps/leveldb/
LEVELDB_LIB = $(LEVELDB_DIR)libleveldb.a
LEVELDB_TIMESTAMP = $(LEVELDB_DIR)/timestamp
LEVELDB_TIMESTAMP_INT = $(ANDR_ROOT)/deps/leveldb_timestamp
LEVELDB_URL_GIT = https://github.com/google/leveldb
LEVELDB_COMMIT = 2d0320a458d0e6a20fff46d5f80b18bfdcce7018
OPENAL_DIR = $(ANDR_ROOT)/deps/openal-soft/
OPENAL_LIB = $(OPENAL_DIR)libs/$(TARGET_ABI)/libopenal.so
OPENAL_TIMESTAMP = $(OPENAL_DIR)/timestamp
OPENAL_TIMESTAMP_INT = $(ANDR_ROOT)/deps/openal_timestamp
OPENAL_URL_GIT = https://github.com/apportable/openal-soft
OGG_DIR = $(ANDR_ROOT)/deps/libvorbis-libogg-android/
OGG_LIB = $(OGG_DIR)libs/$(TARGET_ABI)/libogg.so
VORBIS_LIB = $(OGG_DIR)libs/$(TARGET_ABI)/libogg.so
OGG_TIMESTAMP = $(OGG_DIR)timestamp
OGG_TIMESTAMP_INT = $(ANDR_ROOT)/deps/ogg_timestamp
OGG_URL_GIT = https://gitlab.com/minetest/libvorbis-libogg-android
IRRLICHT_REVISION = 5150
IRRLICHT_DIR = $(ANDR_ROOT)/deps/irrlicht/
IRRLICHT_LIB = $(IRRLICHT_DIR)lib/Android/libIrrlicht.a
IRRLICHT_TIMESTAMP = $(IRRLICHT_DIR)timestamp
IRRLICHT_TIMESTAMP_INT = $(ANDR_ROOT)/deps/irrlicht_timestamp
IRRLICHT_URL_SVN = https://svn.code.sf.net/p/irrlicht/code/branches/ogl-es@$(IRRLICHT_REVISION)
OPENSSL_VERSION = 1.0.2n
OPENSSL_BASEDIR = openssl-$(OPENSSL_VERSION)
OPENSSL_DIR = $(ANDR_ROOT)/deps/$(OPENSSL_BASEDIR)/
OPENSSL_LIB = $(OPENSSL_DIR)/libssl.a
OPENSSL_TIMESTAMP = $(OPENSSL_DIR)timestamp
OPENSSL_TIMESTAMP_INT = $(ANDR_ROOT)/deps/openssl_timestamp
OPENSSL_URL = https://www.openssl.org/source/openssl-$(OPENSSL_VERSION).tar.gz
CURL_VERSION = 7.60.0
CURL_DIR = $(ANDR_ROOT)/deps/curl-$(CURL_VERSION)
CURL_LIB = $(CURL_DIR)/lib/.libs/libcurl.a
CURL_TIMESTAMP = $(CURL_DIR)/timestamp
CURL_TIMESTAMP_INT = $(ANDR_ROOT)/deps/curl_timestamp
CURL_URL_HTTP = https://curl.haxx.se/download/curl-${CURL_VERSION}.tar.bz2
FREETYPE_DIR = $(ANDR_ROOT)/deps/freetype2-android/
FREETYPE_LIB = $(FREETYPE_DIR)/Android/obj/local/$(TARGET_ABI)/libfreetype2-static.a
FREETYPE_TIMESTAMP = $(FREETYPE_DIR)timestamp
FREETYPE_TIMESTAMP_INT = $(ANDR_ROOT)/deps/freetype_timestamp
FREETYPE_URL_GIT = https://github.com/cdave1/freetype2-android
ICONV_VERSION = 1.14
ICONV_DIR = $(ANDR_ROOT)/deps/libiconv/
ICONV_LIB = $(ICONV_DIR)/lib/.libs/libiconv.so
ICONV_TIMESTAMP = $(ICONV_DIR)timestamp
ICONV_TIMESTAMP_INT = $(ANDR_ROOT)/deps/iconv_timestamp
ICONV_URL_HTTP = https://ftp.gnu.org/pub/gnu/libiconv/libiconv-$(ICONV_VERSION).tar.gz
SQLITE3_FOLDER = sqlite-amalgamation-3240000
SQLITE3_URL = https://www.sqlite.org/2018/$(SQLITE3_FOLDER).zip
ANDROID_SDK = $(shell grep '^sdk\.dir' local.properties | sed 's/^.*=[[:space:]]*//')
ANDROID_NDK = $(shell grep '^ndk\.dir' local.properties | sed 's/^.*=[[:space:]]*//')
#use interim target variable to switch leveldb on or off
ifeq ($(HAVE_LEVELDB),1)
LEVELDB_TARGET = $(LEVELDB_LIB)
endif
.PHONY : debug release reconfig delconfig \
leveldb_download clean_leveldb leveldb\
irrlicht_download clean_irrlicht irrlicht \
clean_assets assets sqlite3_download \
freetype_download clean_freetype freetype \
apk clean_apk \
clean_all clean prep_srcdir \
install_debug install_release envpaths all \
$(ASSETS_TIMESTAMP) $(LEVELDB_TIMESTAMP) \
$(OPENAL_TIMESTAMP) $(OGG_TIMESTAMP) \
$(IRRLICHT_TIMESTAMP) $(CURL_TIMESTAMP) \
$(OPENSSL_TIMESTAMP) \
$(ANDR_ROOT)/jni/src/android_version.h \
$(ANDR_ROOT)/jni/src/android_version_githash.h
debug : local.properties
export NDEBUG=; \
export BUILD_TYPE=debug; \
$(MAKE) apk
all : debug release
release : local.properties
@export NDEBUG=1; \
export BUILD_TYPE=release; \
$(MAKE) apk
reconfig: delconfig
@$(MAKE) local.properties
delconfig:
$(RM) local.properties
local.properties:
@echo "Please specify path of ANDROID NDK"; \
echo "e.g. $$HOME/Android/Sdk/ndk-bundle/"; \
read ANDROID_NDK ; \
if [ ! -d $$ANDROID_NDK ] ; then \
echo "$$ANDROID_NDK is not a valid folder"; \
exit 1; \
fi; \
echo "ndk.dir = $$ANDROID_NDK" > local.properties; \
echo "Please specify path of ANDROID SDK"; \
echo "e.g. $$HOME/Android/Sdk/"; \
read SDKFLDR ; \
if [ ! -d $$SDKFLDR ] ; then \
echo "$$SDKFLDR is not a valid folder"; \
exit 1; \
fi; \
echo "sdk.dir = $$SDKFLDR" >> local.properties;
$(OPENAL_TIMESTAMP) : openal_download
@LAST_MODIF=$$(find ${OPENAL_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
touch ${OPENAL_TIMESTAMP}; \
fi
openal_download :
@if [ ! -d ${OPENAL_DIR} ] ; then \
echo "openal sources missing, downloading..."; \
mkdir -p ${ANDR_ROOT}/deps; \
cd ${ANDR_ROOT}/deps ; \
git clone ${OPENAL_URL_GIT} || exit 1; \
fi
openal : $(OPENAL_LIB)
$(OPENAL_LIB): $(OPENAL_TIMESTAMP)
+ @REFRESH=0; \
if [ ! -e ${OPENAL_TIMESTAMP_INT} ] ; then \
REFRESH=1; \
fi; \
if [ ${OPENAL_TIMESTAMP} -nt ${OPENAL_TIMESTAMP_INT} ] ; then \
REFRESH=1; \
fi; \
if [ $$REFRESH -ne 0 ] ; then \
echo "changed timestamp for openal detected building..."; \
cd ${OPENAL_DIR}; \
export APP_PLATFORM=${APP_PLATFORM}; \
export TARGET_ABI=${TARGET_ABI}; \
export TARGET_CFLAGS_ADDON="${TARGET_CFLAGS_ADDON}"; \
export TARGET_CXXFLAGS_ADDON="${TARGET_CXXFLAGS_ADDON}"; \
export COMPILER_VERSION=${COMPILER_VERSION}; \
${ANDROID_NDK}/ndk-build \
NDK_APPLICATION_MK=${ANDR_ROOT}/jni/Deps.mk || exit 1; \
touch ${OPENAL_TIMESTAMP}; \
touch ${OPENAL_TIMESTAMP_INT}; \
else \
echo "nothing to be done for openal"; \
fi
clean_openal :
$(RM) -rf ${OPENAL_DIR}
$(OGG_TIMESTAMP) : ogg_download
@LAST_MODIF=$$(find ${OGG_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
touch ${OGG_TIMESTAMP}; \
fi
ogg_download :
@if [ ! -d ${OGG_DIR} ] ; then \
echo "ogg sources missing, downloading..."; \
mkdir -p ${ANDR_ROOT}/deps; \
cd ${ANDR_ROOT}/deps ; \
git clone ${OGG_URL_GIT}|| exit 1; \
cd libvorbis-libogg-android ; \
patch -p1 < ${ANDR_ROOT}/patches/libvorbis-libogg-fpu.patch || exit 1; \
fi
ogg : $(OGG_LIB)
$(OGG_LIB): $(OGG_TIMESTAMP)
+ @REFRESH=0; \
if [ ! -e ${OGG_TIMESTAMP_INT} ] ; then \
echo "${OGG_TIMESTAMP_INT} doesn't exist"; \
REFRESH=1; \
fi; \
if [ ${OGG_TIMESTAMP} -nt ${OGG_TIMESTAMP_INT} ] ; then \
REFRESH=1; \
fi; \
if [ $$REFRESH -ne 0 ] ; then \
echo "changed timestamp for ogg detected building..."; \
cd ${OGG_DIR}; \
export APP_PLATFORM=${APP_PLATFORM}; \
export TARGET_ABI=${TARGET_ABI}; \
${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh \
--toolchain=${TARGET_TOOLCHAIN}${COMPILER_VERSION} \
--platform=${APP_PLATFORM} \
--install-dir=$${TOOLCHAIN}; \
touch ${OGG_TIMESTAMP}; \
touch ${OGG_TIMESTAMP_INT}; \
else \
echo "nothing to be done for libogg/libvorbis"; \
fi
clean_ogg :
$(RM) -rf ${OGG_DIR}
$(OPENSSL_TIMESTAMP) : openssl_download
@LAST_MODIF=$$(find ${OPENSSL_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
touch ${OPENSSL_TIMESTAMP}; \
fi
openssl_download :
@if [ ! -d ${OPENSSL_DIR} ] ; then \
echo "openssl sources missing, downloading..."; \
mkdir -p ${ANDR_ROOT}/deps; \
cd ${ANDR_ROOT}/deps ; \
wget ${OPENSSL_URL} || exit 1; \
tar -xzf ${OPENSSL_BASEDIR}.tar.gz; \
cd ${OPENSSL_BASEDIR}; \
patch -p1 < ${ANDR_ROOT}/patches/openssl_arch.patch; \
sed -i 's/-mandroid //g' Configure; \
fi
openssl : $(OPENSSL_LIB)
$(OPENSSL_LIB): $(OPENSSL_TIMESTAMP)
@REFRESH=0; \
if [ ! -e ${OPENSSL_TIMESTAMP_INT} ] ; then \
echo "${OPENSSL_TIMESTAMP_INT} doesn't exist"; \
REFRESH=1; \
fi; \
if [ ${OPENSSL_TIMESTAMP} -nt ${OPENSSL_TIMESTAMP_INT} ] ; then \
REFRESH=1; \
fi; \
if [ $$REFRESH -ne 0 ] ; then \
echo "changed timestamp for openssl detected building..."; \
cd ${OPENSSL_DIR}; \
ln -s ${OPENSSL_DIR} ../openssl; \
export TOOLCHAIN=/tmp/ndk-${TARGET_HOST}-openssl; \
${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh \
--toolchain=${TARGET_TOOLCHAIN}${COMPILER_VERSION} \
--platform=${APP_PLATFORM} \
--stl=libc++ \
--install-dir=$${TOOLCHAIN}; \
export PATH="$${TOOLCHAIN}/bin:$${PATH}"; \
export CFLAGS="$${CFLAGS} ${TARGET_CFLAGS_ADDON}"; \
export LDFLAGS="$${LDFLAGS} ${TARGET_LDFLAGS_ADDON}"; \
CC=${CROSS_CC} ./Configure -DL_ENDIAN no-asm android-${TARGET_ARCH} \
-D__ANDROID_API__=$(API); \
CC=${CROSS_CC} ANDROID_DEV=/tmp/ndk-${TARGET_HOST} make depend; \
CC=${CROSS_CC} ANDROID_DEV=/tmp/ndk-${TARGET_HOST} make build_libs; \
touch ${OPENSSL_TIMESTAMP}; \
touch ${OPENSSL_TIMESTAMP_INT}; \
$(RM) -rf $${TOOLCHAIN}; \
else \
echo "nothing to be done for openssl"; \
fi
clean_openssl :
$(RM) -rf ${OPENSSL_DIR}; \
$(RM) -rf $(ANDR_ROOT)/deps/${OPENSSL_BASEDIR}.tar.gz; \
$(RM) -rf $(ANDR_ROOT)/deps/openssl
$(LEVELDB_TIMESTAMP) : leveldb_download
@LAST_MODIF=$$(find ${LEVELDB_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
touch ${LEVELDB_TIMESTAMP}; \
fi
leveldb_download :
@if [ ! -d ${LEVELDB_DIR} ] ; then \
echo "leveldb sources missing, downloading..."; \
mkdir -p ${ANDR_ROOT}/deps; \
cd ${ANDR_ROOT}/deps ; \
git clone ${LEVELDB_URL_GIT} || exit 1; \
cd ${LEVELDB_DIR} || exit 1; \
git checkout ${LEVELDB_COMMIT} || exit 1; \
fi
leveldb : $(LEVELDB_LIB)
ifeq ($(HAVE_LEVELDB),1)
$(LEVELDB_LIB): $(LEVELDB_TIMESTAMP)
@REFRESH=0; \
if [ ! -e ${LEVELDB_TIMESTAMP_INT} ] ; then \
REFRESH=1; \
fi; \
if [ ${LEVELDB_TIMESTAMP} -nt ${LEVELDB_TIMESTAMP_INT} ] ; then \
REFRESH=1; \
fi; \
if [ $$REFRESH -ne 0 ] ; then \
echo "changed timestamp for leveldb detected building..."; \
cd deps/leveldb; \
export CROSS_PREFIX=${TARGET_TOOLCHAIN}; \
export TOOLCHAIN=/tmp/ndk-${TARGET_HOST}-leveldb; \
${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh \
--toolchain=${TARGET_TOOLCHAIN}${COMPILER_VERSION} \
--platform=${APP_PLATFORM} \
--stl=libc++ \
--install-dir=$${TOOLCHAIN}; \
export PATH="$${TOOLCHAIN}/bin:$${PATH}"; \
export CC=${CROSS_CC}; \
export CXX=${CROSS_CXX}; \
export CFLAGS="$${CFLAGS} ${TARGET_CFLAGS_ADDON}"; \
export CPPFLAGS="$${CPPFLAGS} ${TARGET_CXXFLAGS_ADDON}"; \
export LDFLAGS="$${LDFLAGS} ${TARGET_LDFLAGS_ADDON}"; \
export TARGET_OS=OS_ANDROID_CROSSCOMPILE; \
$(MAKE) || exit 1; \
touch ${LEVELDB_TIMESTAMP}; \
touch ${LEVELDB_TIMESTAMP_INT}; \
$(RM) -rf $${TOOLCHAIN}; \
else \
echo "nothing to be done for leveldb"; \
fi
endif
clean_leveldb :
./gradlew cleanLevelDB
$(FREETYPE_TIMESTAMP) : freetype_download
@LAST_MODIF=$$(find ${FREETYPE_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
touch ${FREETYPE_TIMESTAMP}; \
fi
freetype_download :
@if [ ! -d ${FREETYPE_DIR} ] ; then \
echo "freetype sources missing, downloading..."; \
mkdir -p ${ANDR_ROOT}/deps; \
cd deps; \
git clone ${FREETYPE_URL_GIT} || exit 1; \
fi
freetype : $(FREETYPE_LIB)
$(FREETYPE_LIB) : $(FREETYPE_TIMESTAMP)
+ @REFRESH=0; \
if [ ! -e ${FREETYPE_TIMESTAMP_INT} ] ; then \
REFRESH=1; \
fi; \
if [ ! -e ${FREETYPE_LIB} ] ; then \
REFRESH=1; \
fi; \
if [ ${FREETYPE_TIMESTAMP} -nt ${FREETYPE_TIMESTAMP_INT} ] ; then \
REFRESH=1; \
fi; \
if [ $$REFRESH -ne 0 ] ; then \
mkdir -p ${FREETYPE_DIR}; \
echo "changed timestamp for freetype detected building..."; \
cd ${FREETYPE_DIR}/Android/jni; \
export APP_PLATFORM=${APP_PLATFORM}; \
export TARGET_ABI=${TARGET_ABI}; \
export TARGET_CFLAGS_ADDON="${TARGET_CFLAGS_ADDON}"; \
export TARGET_CXXFLAGS_ADDON="${TARGET_CXXFLAGS_ADDON}"; \
export COMPILER_VERSION=${COMPILER_VERSION}; \
${ANDROID_NDK}/ndk-build \
NDK_APPLICATION_MK=${ANDR_ROOT}/jni/Deps.mk || exit 1; \
touch ${FREETYPE_TIMESTAMP}; \
touch ${FREETYPE_TIMESTAMP_INT}; \
else \
echo "nothing to be done for freetype"; \
fi
clean_freetype :
./gradlew cleanFreetype
$(ICONV_TIMESTAMP) : iconv_download
@LAST_MODIF=$$(find ${ICONV_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
touch ${ICONV_TIMESTAMP}; \
fi
iconv_download :
@if [ ! -d ${ICONV_DIR} ] ; then \
echo "iconv sources missing, downloading..."; \
mkdir -p ${ANDR_ROOT}/deps; \
cd ${ANDR_ROOT}/deps; \
wget ${ICONV_URL_HTTP} || exit 1; \
tar -xzf libiconv-${ICONV_VERSION}.tar.gz || exit 1; \
rm libiconv-${ICONV_VERSION}.tar.gz; \
ln -s libiconv-${ICONV_VERSION} libiconv; \
cd ${ICONV_DIR}; \
patch -p1 < ${ANDR_ROOT}/patches/libiconv_android.patch; \
patch -p1 < ${ANDR_ROOT}/patches/libiconv_stdio.patch; \
fi
iconv : $(ICONV_LIB)
$(ICONV_LIB) : $(ICONV_TIMESTAMP)
@REFRESH=0; \
if [ ! -e ${ICONV_TIMESTAMP_INT} ] ; then \
REFRESH=1; \
fi; \
if [ ! -e ${ICONV_LIB} ] ; then \
REFRESH=1; \
fi; \
if [ ${ICONV_TIMESTAMP} -nt ${ICONV_TIMESTAMP_INT} ] ; then \
REFRESH=1; \
fi; \
if [ $$REFRESH -ne 0 ] ; then \
mkdir -p ${ICONV_DIR}; \
echo "changed timestamp for iconv detected building..."; \
cd ${ICONV_DIR}; \
export TOOLCHAIN=/tmp/ndk-${TARGET_HOST}-iconv; \
${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh \
--toolchain=${TARGET_TOOLCHAIN}${COMPILER_VERSION} \
--platform=${APP_PLATFORM} \
--stl=libc++ \
--install-dir=$${TOOLCHAIN}; \
export PATH="$${TOOLCHAIN}/bin:$${PATH}"; \
export CFLAGS="$${CFLAGS} ${TARGET_CFLAGS_ADDON}"; \
export LDFLAGS="$${LDFLAGS} ${TARGET_LDFLAGS_ADDON} -lstdc++"; \
export CC=${CROSS_CC}; \
export CXX=${CROSS_CXX}; \
export TARGET_OS=OS_ANDROID_CROSSCOMPILE; \
./configure --host=${TARGET_HOST} || exit 1; \
sed -i 's/LIBICONV_VERSION_INFO) /LIBICONV_VERSION_INFO) -avoid-version /g' lib/Makefile; \
grep "iconv_LDFLAGS" src/Makefile; \
$(MAKE) -s || exit 1; \
touch ${ICONV_TIMESTAMP}; \
touch ${ICONV_TIMESTAMP_INT}; \
rm -rf ${TOOLCHAIN}; \
else \
echo "nothing to be done for iconv"; \
fi
clean_iconv :
./gradlew cleanIconv
#Note: Texturehack patch is required for gpu's not supporting color format
# correctly. Known bad GPU:
# -geforce on emulator
# -Vivante Corporation GC1000 core (e.g. Galaxy Tab 3)
irrlicht_download :
@if [ ! -d "deps/irrlicht" ] ; then \
echo "irrlicht sources missing, downloading..."; \
mkdir -p ${ANDR_ROOT}/deps; \
cd deps; \
svn co ${IRRLICHT_URL_SVN} irrlicht || exit 1; \
cd irrlicht; \
patch -p1 < ${ANDR_ROOT}/patches/irrlicht-touchcount.patch || exit 1; \
patch -p1 < ${ANDR_ROOT}/patches/irrlicht-back_button.patch || exit 1; \
patch -p1 < ${ANDR_ROOT}/patches/irrlicht-texturehack.patch || exit 1; \
patch -p1 < ${ANDR_ROOT}/patches/irrlicht-native_activity.patch || exit 1; \
fi
$(IRRLICHT_TIMESTAMP) : irrlicht_download
@LAST_MODIF=$$(find ${IRRLICHT_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
touch ${IRRLICHT_TIMESTAMP}; \
fi
irrlicht : $(IRRLICHT_LIB)
$(IRRLICHT_LIB): $(IRRLICHT_TIMESTAMP) $(FREETYPE_LIB)
+ @REFRESH=0; \
if [ ! -e ${IRRLICHT_TIMESTAMP_INT} ] ; then \
REFRESH=1; \
fi; \
if [ ! -e ${IRRLICHT_LIB} ] ; then \
REFRESH=1; \
fi; \
if [ ${IRRLICHT_TIMESTAMP} -nt ${IRRLICHT_TIMESTAMP_INT} ] ; then \
REFRESH=1; \
fi; \
if [ $$REFRESH -ne 0 ] ; then \
mkdir -p ${IRRLICHT_DIR}; \
echo "changed timestamp for irrlicht detected building..."; \
cd deps/irrlicht/source/Irrlicht/Android; \
export APP_PLATFORM=${APP_PLATFORM}; \
export TARGET_ABI=${TARGET_ABI}; \
export TARGET_CFLAGS_ADDON="${TARGET_CFLAGS_ADDON}"; \
export TARGET_CXXFLAGS_ADDON="${TARGET_CXXFLAGS_ADDON}"; \
export COMPILER_VERSION=${COMPILER_VERSION}; \
${ANDROID_NDK}/ndk-build \
NDK_APPLICATION_MK=${ANDR_ROOT}/jni/Deps.mk || exit 1; \
touch ${IRRLICHT_TIMESTAMP}; \
touch ${IRRLICHT_TIMESTAMP_INT}; \
else \
echo "nothing to be done for irrlicht"; \
fi
clean_irrlicht :
./gradlew cleanIrrlicht
$(CURL_TIMESTAMP) : curl_download
@LAST_MODIF=$$(find ${CURL_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
touch ${CURL_TIMESTAMP}; \
fi
curl_download :
@if [ ! -d "deps/curl-${CURL_VERSION}" ] ; then \
echo "curl sources missing, downloading..."; \
mkdir -p ${ANDR_ROOT}/deps; \
cd deps; \
wget ${CURL_URL_HTTP} || exit 1; \
tar -xjf curl-${CURL_VERSION}.tar.bz2 || exit 1; \
rm curl-${CURL_VERSION}.tar.bz2; \
ln -s curl-${CURL_VERSION} curl; \
fi
curl : $(CURL_LIB)
$(CURL_LIB): $(CURL_TIMESTAMP) $(OPENSSL_LIB)
@REFRESH=0; \
if [ ! -e ${CURL_TIMESTAMP_INT} ] ; then \
REFRESH=1; \
fi; \
if [ ! -e ${CURL_LIB} ] ; then \
REFRESH=1; \
fi; \
if [ ${CURL_TIMESTAMP} -nt ${CURL_TIMESTAMP_INT} ] ; then \
REFRESH=1; \
fi; \
if [ $$REFRESH -ne 0 ] ; then \
mkdir -p ${CURL_DIR}; \
echo "changed timestamp for curl detected building..."; \
cd deps/curl-${CURL_VERSION}; \
export CROSS_PREFIX=${TARGET_TOOLCHAIN}; \
export TOOLCHAIN=/tmp/ndk-${TARGET_HOST}-curl; \
${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh \
--toolchain=${TARGET_TOOLCHAIN}${COMPILER_VERSION} \
--platform=${APP_PLATFORM} \
--stl=libc++ \
--install-dir=$${TOOLCHAIN}; \
export PATH="$${TOOLCHAIN}/bin:$${PATH}"; \
export CC=${CROSS_CC}; \
export CXX=${CROSS_CXX}; \
export TARGET_OS=OS_ANDROID_CROSSCOMPILE; \
export CPPFLAGS="$${CPPFLAGS} -I${OPENSSL_DIR}/include ${TARGET_CFLAGS_ADDON}"; \
export CFLAGS="$${CFLAGS} ${TARGET_CFLAGS_ADDON}"; \
export LDFLAGS="$${LDFLAGS} -L${OPENSSL_DIR} ${TARGET_LDFLAGS_ADDON}"; \
./configure --host=${TARGET_HOST} --disable-shared --enable-static --with-ssl; \
$(MAKE) -s || exit 1; \
touch ${CURL_TIMESTAMP}; \
touch ${CURL_TIMESTAMP_INT}; \
$(RM) -rf $${TOOLCHAIN}; \
else \
echo "nothing to be done for curl"; \
fi
clean_curl :
./gradlew cleanCURL
sqlite3_download: deps/${SQLITE3_FOLDER}/sqlite3.c
deps/${SQLITE3_FOLDER}/sqlite3.c :
cd deps; \
wget ${SQLITE3_URL}; \
unzip ${SQLITE3_FOLDER}.zip; \
ln -s ${SQLITE3_FOLDER} sqlite; \
cd ${SQLITE3_FOLDER};
clean_sqlite3:
./gradlew cleanSQLite3
$(ASSETS_TIMESTAMP) : $(IRRLICHT_LIB)
@mkdir -p ${ANDR_ROOT}/deps; \
for DIRNAME in {builtin,client,doc,fonts,games,mods,po,textures}; do \
LAST_MODIF=$$(find ${PROJ_ROOT}/${DIRNAME} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
if [ $$(basename $$LAST_MODIF) != "timestamp" ]; then \
touch ${PROJ_ROOT}/${DIRNAME}/timestamp; \
touch ${ASSETS_TIMESTAMP}; \
echo ${DIRNAME} changed $$LAST_MODIF; \
fi; \
done; \
LAST_MODIF=$$(find ${IRRLICHT_DIR}/media -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
touch ${IRRLICHT_DIR}/media/timestamp; \
touch ${ASSETS_TIMESTAMP}; \
fi; \
if [ ${PROJ_ROOT}/minetest.conf.example -nt ${ASSETS_TIMESTAMP} ] ; then \
echo "conf changed"; \
touch ${ASSETS_TIMESTAMP}; \
fi; \
if [ ${PROJ_ROOT}/README.txt -nt ${ASSETS_TIMESTAMP} ] ; then \
touch ${ASSETS_TIMESTAMP}; \
fi; \
if [ ! -e $(ASSETS_TIMESTAMP) ] ; then \
touch $(ASSETS_TIMESTAMP); \
fi
assets : $(ASSETS_TIMESTAMP)
@REFRESH=0; \
if [ ! -e ${ASSETS_TIMESTAMP}.old ] ; then \
REFRESH=1; \
fi; \
if [ ${ASSETS_TIMESTAMP} -nt ${ASSETS_TIMESTAMP}.old ] ; then \
REFRESH=1; \
fi; \
if [ ! -d ${APP_ROOT}/assets ] ; then \
REFRESH=1; \
fi; \
if [ $$REFRESH -ne 0 ] ; then \
echo "assets changed, refreshing..."; \
$(MAKE) clean_assets; \
./gradlew copyAssets; \
cp -r ${IRRLICHT_DIR}/media/Shaders ${APP_ROOT}/assets/Minetest/media; \
cd ${APP_ROOT}/assets || exit 1; \
find . -name "timestamp" -exec rm {} \; ; \
find . -name "*.blend" -exec rm {} \; ; \
find . -name "*~" -exec rm {} \; ; \
find . -type d -path "*.git" -exec rm -rf {} \; ; \
find . -type d -path "*.svn" -exec rm -rf {} \; ; \
find . -type f -path "*.gitignore" -exec rm -rf {} \; ; \
ls -R | grep ":$$" | sed -e 's/:$$//' -e 's/\.//' -e 's/^\///' > "index.txt"; \
find -L Minetest > filelist.txt; \
cp ${ANDR_ROOT}/${ASSETS_TIMESTAMP} ${ANDR_ROOT}/${ASSETS_TIMESTAMP}.old; \
else \
echo "nothing to be done for assets"; \
fi
clean_assets :
./gradlew cleanAssets
apk: local.properties assets $(ICONV_LIB) $(IRRLICHT_LIB) $(CURL_LIB) $(LEVELDB_TARGET) \
$(OPENAL_LIB) $(OGG_LIB) prep_srcdir $(ANDR_ROOT)/jni/src/android_version.h \
$(ANDR_ROOT)/jni/src/android_version_githash.h sqlite3_download
+ @export TARGET_LIBDIR=${TARGET_LIBDIR}; \
export HAVE_LEVELDB=${HAVE_LEVELDB}; \
export APP_PLATFORM=${APP_PLATFORM}; \
export TARGET_ABI=${TARGET_ABI}; \
export TARGET_CFLAGS_ADDON="${TARGET_CFLAGS_ADDON}"; \
export TARGET_CXXFLAGS_ADDON="${TARGET_CXXFLAGS_ADDON}"; \
export COMPILER_VERSION=${COMPILER_VERSION}; \
export GPROF=${GPROF}; \
${ANDROID_NDK}/ndk-build || exit 1; \
if [ ! -e ${APP_ROOT}/jniLibs ]; then \
ln -s ${ANDR_ROOT}/libs ${APP_ROOT}/jniLibs || exit 1; \
fi; \
export VERSION_STR="${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}" && \
export BUILD_TYPE_C=$$(echo "$${BUILD_TYPE}" | sed 's/./\U&/') && \
./gradlew assemble$$BUILD_TYPE_C && \
echo "APK stored at: build/outputs/apk/$$BUILD_TYPE/Minetest-$$BUILD_TYPE.apk" && \
echo "You can install it with \`make install_$$BUILD_TYPE\`"
# These Intentionally doesn't depend on their respective build steps,
# because it takes a while to verify that everything's up-to-date.
install_debug:
${ANDROID_SDK}/platform-tools/adb install -r build/outputs/apk/debug/Minetest-debug.apk
install_release:
${ANDROID_SDK}/platform-tools/adb install -r build/outputs/apk/release/Minetest-release.apk
prep_srcdir :
@if [ ! -e ${ANDR_ROOT}/jni/src ]; then \
ln -s ${PROJ_ROOT}/src ${ANDR_ROOT}/jni/src; \
fi; \
if [ ! -e ${ANDR_ROOT}/jni/lib ]; then \
ln -s ${PROJ_ROOT}/lib ${ANDR_ROOT}/jni/lib; \
fi
clean_apk :
./gradlew clean
clean_all :
./gradlew cleanAll
$(ANDR_ROOT)/jni/src/android_version_githash.h : prep_srcdir
@export VERSION_FILE=${ANDR_ROOT}/jni/src/android_version_githash.h; \
export VERSION_FILE_NEW=$${VERSION_FILE}.new; \
{ \
echo "#ifndef ANDROID_MT_VERSION_GITHASH_H"; \
echo "#define ANDROID_MT_VERSION_GITHASH_H"; \
export GITHASH=$$(git rev-parse --short=8 HEAD); \
export VERSION_STR="${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}"; \
echo "#define VERSION_GITHASH \"$$VERSION_STR-$$GITHASH-Android\""; \
echo "#endif"; \
} > "$${VERSION_FILE_NEW}"; \
if ! cmp -s $${VERSION_FILE} $${VERSION_FILE_NEW}; then \
echo "android_version_githash.h changed, updating..."; \
mv "$${VERSION_FILE_NEW}" "$${VERSION_FILE}"; \
else \
rm "$${VERSION_FILE_NEW}"; \
fi
$(ANDR_ROOT)/jni/src/android_version.h : prep_srcdir
@export VERSION_FILE=${ANDR_ROOT}/jni/src/android_version.h; \
export VERSION_FILE_NEW=$${VERSION_FILE}.new; \
{ \
echo "#ifndef ANDROID_MT_VERSION_H"; \
echo "#define ANDROID_MT_VERSION_H"; \
echo "#define VERSION_MAJOR ${VERSION_MAJOR}"; \
echo "#define VERSION_MINOR ${VERSION_MINOR}"; \
echo "#define VERSION_PATCH ${VERSION_PATCH}"; \
echo "#define VERSION_STRING STR(VERSION_MAJOR) \".\" STR(VERSION_MINOR) \
\".\" STR(VERSION_PATCH)"; \
echo "#endif"; \
} > $${VERSION_FILE_NEW}; \
if ! cmp -s $${VERSION_FILE} $${VERSION_FILE_NEW}; then \
echo "android_version.h changed, updating..."; \
mv "$${VERSION_FILE_NEW}" "$${VERSION_FILE}"; \
else \
rm "$${VERSION_FILE_NEW}"; \
fi
clean : clean_apk clean_assets

View File

@ -0,0 +1,111 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion '29.0.3'
ndkVersion '21.1.6352462'
defaultConfig {
applicationId 'net.minetest.minetest'
minSdkVersion 16
targetSdkVersion 29
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
versionCode project.versionCode
}
Properties props = new Properties()
props.load(new FileInputStream(file('../local.properties')))
if (props.getProperty('keystore') != null) {
signingConfigs {
release {
storeFile file(props['keystore'])
storePassword props['keystore.password']
keyAlias props['key']
keyPassword props['key.password']
}
}
buildTypes {
release {
minifyEnabled true
signingConfig signingConfigs.release
}
}
}
// for multiple APKs
splits {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
task prepareAssets() {
def assetsFolder = "build/assets"
def projRoot = "../../.."
def gameToCopy = "minetest_game"
copy {
from "${projRoot}/minetest.conf.example", "${projRoot}/README.md" into assetsFolder
}
copy {
from "${projRoot}/doc/lgpl-2.1.txt" into "${assetsFolder}"
}
copy {
from "${projRoot}/builtin" into "${assetsFolder}/builtin"
}
/*copy {
// ToDo: fix Minetest shaders that currently don't work with OpenGL ES
from "${projRoot}/client/shaders" into "${assetsFolder}/client/shaders"
}*/
copy {
from "../native/deps/Android/Irrlicht/shaders" into "${assetsFolder}/client/shaders/Irrlicht"
}
copy {
from "${projRoot}/fonts" include "*.ttf" into "${assetsFolder}/fonts"
}
copy {
from "${projRoot}/games/${gameToCopy}" into "${assetsFolder}/games/${gameToCopy}"
}
/*copy {
// ToDo: fix broken locales
from "${projRoot}/po" into "${assetsFolder}/po"
}*/
copy {
from "${projRoot}/textures" into "${assetsFolder}/textures"
}
file("${assetsFolder}/.nomedia").text = "";
task zipAssets(type: Zip) {
archiveName "Minetest.zip"
from "${assetsFolder}"
destinationDir file("src/main/assets")
}
}
preBuild.dependsOn zipAssets
// Map for the version code that gives each ABI a value.
import com.android.build.OutputFile
def abiCodes = ['armeabi-v7a': 0, 'arm64-v8a': 1]
android.applicationVariants.all { variant ->
variant.outputs.each {
output ->
def abiName = output.getFilter(OutputFile.ABI)
output.versionCodeOverride = abiCodes.get(abiName, 0) + variant.versionCode
}
}
dependencies {
implementation project(':native')
implementation 'androidx.appcompat:appcompat:1.1.0'
}

View File

@ -1,30 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="net.minetest.minetest"
android:installLocation="auto">
<uses-feature
android:glEsVersion="0x00010000"
android:required="true" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--
`android:requestLegacyExternalStorage="true"` is workaround for using `/sdcard`
instead of the `getFilesDir()` patch for assets. Check link below for more information:
https://developer.android.com/training/data-storage/compatibility
-->
<application
android:allowBackup="true"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="${project}"
android:resizeableActivity="false">
android:label="@string/label"
android:resizeableActivity="false"
android:requestLegacyExternalStorage="true"
tools:ignore="UnusedAttribute">
<meta-data
android:name="android.max_aspect"
android:value="2.1" />
android:value="3.0" />
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|navigation|screenSize"
android:label="${project}"
android:launchMode="singleTask"
android:maxAspectRatio="3.0"
android:screenOrientation="sensorLandscape"
android:theme="@style/AppTheme">
<intent-filter>
@ -32,11 +36,13 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MtNativeActivity"
android:name=".GameActivity"
android:configChanges="orientation|keyboard|keyboardHidden|navigation|screenSize|smallestScreenSize"
android:hardwareAccelerated="true"
android:launchMode="singleTask"
android:maxAspectRatio="3.0"
android:screenOrientation="sensorLandscape"
android:theme="@style/AppTheme">
<intent-filter>
@ -44,16 +50,18 @@
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="minetest" />
android:value="Minetest" />
</activity>
<activity
android:name=".MinetestTextEntry"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/Theme.Dialog"
android:windowSoftInputMode="stateAlwaysHidden"/>
<activity
android:name=".MinetestAssetCopy"
android:screenOrientation="sensorLandscape"
android:theme="@style/AppTheme"/>
android:name=".InputDialogActivity"
android:maxAspectRatio="3.0"
android:theme="@style/InputTheme" />
<service
android:name=".UnzipService"
android:enabled="true"
android:exported="false" />
</application>
</manifest>

View File

@ -0,0 +1,82 @@
/*
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

@ -0,0 +1,126 @@
/*
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.app.NativeActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
public class GameActivity extends NativeActivity {
static {
System.loadLibrary("c++_shared");
System.loadLibrary("Minetest");
}
private int messageReturnCode;
private String messageReturnValue;
public static native void putMessageBoxResult(String text);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
messageReturnCode = -1;
messageReturnValue = "";
}
private void makeFullScreen() {
if (Build.VERSION.SDK_INT >= 19)
this.getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus)
makeFullScreen();
}
@Override
protected void onResume() {
super.onResume();
makeFullScreen();
}
@Override
public void onBackPressed() {
// Ignore the back press so Minetest can handle it
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 101) {
if (resultCode == RESULT_OK) {
String text = data.getStringExtra("text");
messageReturnCode = 0;
messageReturnValue = text;
} else
messageReturnCode = 1;
}
}
public void showDialog(String acceptButton, String hint, String current, int editType) {
Intent intent = new Intent(this, InputDialogActivity.class);
Bundle params = new Bundle();
params.putString("acceptButton", acceptButton);
params.putString("hint", hint);
params.putString("current", current);
params.putInt("editType", editType);
intent.putExtras(params);
startActivityForResult(intent, 101);
messageReturnValue = "";
messageReturnCode = -1;
}
public int getDialogState() {
return messageReturnCode;
}
public String getDialogValue() {
messageReturnCode = -1;
return messageReturnValue;
}
public float getDensity() {
return getResources().getDisplayMetrics().density;
}
public int getDisplayHeight() {
return getResources().getDisplayMetrics().heightPixels;
}
public int getDisplayWidth() {
return getResources().getDisplayMetrics().widthPixels;
}
public void openURL(String url) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(browserIntent);
}
}

View File

@ -0,0 +1,98 @@
/*
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.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.text.InputType;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Objects;
public class InputDialogActivity extends AppCompatActivity {
private AlertDialog alertDialog;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle b = getIntent().getExtras();
int editType = Objects.requireNonNull(b).getInt("editType");
String hint = b.getString("hint");
String current = b.getString("current");
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
EditText editText = new EditText(this);
builder.setView(editText);
editText.requestFocus();
editText.setHint(hint);
editText.setText(current);
final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
Objects.requireNonNull(imm).toggleSoftInput(InputMethodManager.SHOW_FORCED,
InputMethodManager.HIDE_IMPLICIT_ONLY);
if (editType == 3)
editText.setInputType(InputType.TYPE_CLASS_TEXT |
InputType.TYPE_TEXT_VARIATION_PASSWORD);
else
editText.setInputType(InputType.TYPE_CLASS_TEXT);
editText.setOnKeyListener((view, KeyCode, event) -> {
if (KeyCode == KeyEvent.KEYCODE_ENTER) {
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
pushResult(editText.getText().toString());
return true;
}
return false;
});
alertDialog = builder.create();
if (!this.isFinishing())
alertDialog.show();
alertDialog.setOnCancelListener(dialog -> {
pushResult(editText.getText().toString());
setResult(Activity.RESULT_CANCELED);
alertDialog.dismiss();
makeFullScreen();
finish();
});
}
private void pushResult(String text) {
Intent resultData = new Intent();
resultData.putExtra("text", text);
setResult(AppCompatActivity.RESULT_OK, resultData);
alertDialog.dismiss();
makeFullScreen();
finish();
}
private void makeFullScreen() {
if (Build.VERSION.SDK_INT >= 19)
this.getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
}

View File

@ -0,0 +1,153 @@
/*
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.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
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;
public class MainActivity extends AppCompatActivity {
private final static int versionCode = BuildConfig.VERSION_CODE;
private final static int PERMISSIONS = 1;
private static final String[] REQUIRED_SDK_PERMISSIONS =
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)
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) {
Toast.makeText(MainActivity.this, intent.getStringExtra(ACTION_FAILURE), Toast.LENGTH_LONG).show();
finish();
} else if (progress == SUCCESS)
startNative();
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
IntentFilter filter = new IntentFilter(ACTION_UPDATE);
registerReceiver(myReceiver, filter);
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)
checkPermission();
else
checkAppVersion();
}
private void checkPermission() {
final List<String> missingPermissions = new ArrayList<>();
for (final String permission : REQUIRED_SDK_PERMISSIONS) {
final int result = ContextCompat.checkSelfPermission(this, permission);
if (result != PackageManager.PERMISSION_GRANTED)
missingPermissions.add(permission);
}
if (!missingPermissions.isEmpty()) {
final String[] permissions = missingPermissions
.toArray(new String[0]);
ActivityCompat.requestPermissions(this, permissions, PERMISSIONS);
} else {
final int[] grantResults = new int[REQUIRED_SDK_PERMISSIONS.length];
Arrays.fill(grantResults, PackageManager.PERMISSION_GRANTED);
onRequestPermissionsResult(PERMISSIONS, REQUIRED_SDK_PERMISSIONS, grantResults);
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PERMISSIONS) {
for (int grantResult : grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, R.string.not_granted, Toast.LENGTH_LONG).show();
finish();
}
}
checkAppVersion();
}
}
private void checkAppVersion() {
if (sharedPreferences.getInt(TAG_VERSION_CODE, 0) == versionCode)
startNative();
else
new CopyZipTask(this).execute(getCacheDir() + "/Minetest.zip");
}
private void startNative() {
sharedPreferences.edit().putInt(TAG_VERSION_CODE, versionCode).apply();
Intent intent = new Intent(this, GameActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
}
@Override
public void onBackPressed() {
// Prevent abrupt interruption when copy game files from assets
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(myReceiver);
}
}

View File

@ -0,0 +1,157 @@
/*
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.app.IntentService;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Environment;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
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_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;
private final int id = 1;
private NotificationManager mNotifyManager;
private boolean isSuccess = true;
private String failureMessage;
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);
}
private void createNotification() {
String name = "net.minetest.minetest";
String channelId = "Minetest channel";
String description = "notifications from Minetest";
Notification.Builder builder;
if (mNotifyManager == null)
mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel mChannel = null;
if (mNotifyManager != null)
mChannel = mNotifyManager.getNotificationChannel(channelId);
if (mChannel == null) {
mChannel = new NotificationChannel(channelId, name, importance);
mChannel.setDescription(description);
// Configure the notification channel, NO SOUND
mChannel.setSound(null, null);
mChannel.enableLights(false);
mChannel.enableVibration(false);
mNotifyManager.createNotificationChannel(mChannel);
}
builder = new Notification.Builder(this, channelId);
} else {
builder = new Notification.Builder(this);
}
builder.setContentTitle(getString(R.string.notification_title))
.setSmallIcon(R.mipmap.ic_launcher)
.setContentText(getString(R.string.notification_description));
mNotifyManager.notify(id, builder.build());
}
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;
int per = 0;
int size = getSummarySize(zip);
File zipFile = new File(zip);
int readLen;
byte[] readBuffer = new byte[8192];
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);
}
}
}
zipFile.delete();
}
} catch (IOException e) {
isSuccess = false;
failureMessage = e.getLocalizedMessage();
}
}
private void publishProgress(int progress) {
Intent intentUpdate = new Intent(ACTION_UPDATE);
intentUpdate.putExtra(ACTION_PROGRESS, progress);
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();
}
return size;
}
@Override
public void onDestroy() {
super.onDestroy();
mNotifyManager.cancel(id);
publishProgress(isSuccess ? SUCCESS : FAILURE);
}
}

View File

Before

Width:  |  Height:  |  Size: 83 B

After

Width:  |  Height:  |  Size: 83 B

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/background"
android:tileMode="repeat" />
android:tileMode="repeat" />

View File

@ -0,0 +1,30 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg">
<ProgressBar
android:id="@+id/progressBar"
style="@style/CustomProgressBar"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_centerInParent="true"
android:layout_marginLeft="90dp"
android:layout_marginRight="90dp"
android:indeterminate="false"
android:max="100"
android:visibility="gone" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/progressBar"
android:layout_centerInParent="true"
android:background="@android:color/transparent"
android:text="@string/loading"
android:textColor="#FEFEFE"
android:visibility="gone" />
</RelativeLayout>

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="label">Minetest</string>
<string name="loading">Loading&#8230;</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>
</resources>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="windowActionBar">false</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowBackground">@drawable/bg</item>
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="p">shortEdges</item>
</style>
<style name="InputTheme" parent="Theme.AppCompat.DayNight.Dialog">
<item name="windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
<style name="CustomProgressBar" parent="@style/Widget.AppCompat.ProgressBar.Horizontal">
<item name="android:indeterminateOnly">false</item>
<item name="android:minHeight">10dip</item>
<item name="android:maxHeight">20dip</item>
</style>
</resources>

View File

@ -1,170 +1,34 @@
// 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", 3) // Version Minor
project.ext.set("versionPatch", 0) // Version Patch
project.ext.set("versionExtra", "-dev") // Version Extra
project.ext.set("versionCode", 30) // 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 {
repositories {
maven { url 'https://maven.google.com' }
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
classpath 'com.android.tools.build:gradle:3.6.3'
classpath 'org.ajoberstar.grgit:grgit-gradle:4.0.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
maven { url 'https://maven.google.com' }
google()
jcenter()
}
}
def curl_version = "7.60.0"
def irrlicht_revision = "5150"
def openal_version = "1.18.2"
def openssl_version = "1.0.2n"
def sqlite3_version = "3240000"
apply plugin: "com.android.application"
android {
compileSdkVersion 28
buildToolsVersion "28.0.3"
defaultConfig {
versionCode 23
versionName "${System.env.VERSION_STR}.${versionCode}"
minSdkVersion 14
targetSdkVersion 28
applicationId "net.minetest.minetest"
manifestPlaceholders = [package: "net.minetest.minetest", project: project.name]
ndk {
// Specifies the ABI configurations of your native
// libraries Gradle should build and package with your APK.
// abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
abiFilters 'armeabi-v7a', 'x86'
}
}
lintOptions {
disable "OldTargetApi", "GoogleAppIndexingWarning"
}
Properties props = new Properties()
props.load(new FileInputStream(file("local.properties")))
if (props.getProperty("keystore") != null) {
signingConfigs {
release {
storeFile file(props["keystore"])
storePassword props["keystore.password"]
keyAlias props["key"]
keyPassword props["key.password"]
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
}
task cleanAssets(type: Delete) {
delete 'src/main/assets'
}
task copyAssets {
dependsOn 'cleanAssets'
mkdir "src/main/assets"
def mtAssetsFolder = "src/main/assets/Minetest"
def projRoot = "../.."
def gameToCopy = "minetest_game"
doLast {
mkdir "${mtAssetsFolder}"
mkdir "${mtAssetsFolder}/client"
mkdir "${mtAssetsFolder}/fonts"
mkdir "${mtAssetsFolder}/games"
mkdir "${mtAssetsFolder}/media"
copy {
from "${projRoot}/minetest.conf.example", "${projRoot}/README.md" into mtAssetsFolder
}
copy {
from "${projRoot}/doc/lgpl-2.1.txt" into "${mtAssetsFolder}/LICENSE.txt"
}
copy {
from "${projRoot}/builtin" into "${mtAssetsFolder}/builtin"
}
copy {
from "${projRoot}/client/shaders" into "${mtAssetsFolder}/client/shaders"
}
copy {
from "${projRoot}/fonts" include "*.ttf" into "${mtAssetsFolder}/fonts"
}
copy {
from "${projRoot}/games/${gameToCopy}" into "${mtAssetsFolder}/games/${gameToCopy}"
}
copy {
from "${projRoot}/po" into "${mtAssetsFolder}/po"
}
copy {
from "${projRoot}/textures" into "${mtAssetsFolder}/textures"
}
}
}
task cleanIconv(type: Delete) {
delete 'deps/libiconv'
}
task cleanIrrlicht(type: Delete) {
delete 'deps/irrlicht'
}
task cleanLevelDB(type: Delete) {
delete 'deps/leveldb'
}
task cleanCURL(type: Delete) {
delete 'deps/curl'
delete 'deps/curl-' + curl_version
}
task cleanOpenSSL(type: Delete) {
delete 'deps/openssl'
delete 'deps/openssl-' + openssl_version
delete 'deps/openssl-' + openssl_version + '.tar.gz'
}
task cleanOpenAL(type: Delete) {
delete 'deps/openal-soft'
}
task cleanFreetype(type: Delete) {
delete 'deps/freetype2-android'
}
task cleanOgg(type: Delete) {
delete 'deps/libvorbis-libogg-android'
}
task cleanSQLite3(type: Delete) {
delete 'deps/sqlite-amalgamation-' + sqlite3_version
delete 'deps/sqlite-amalgamation-' + sqlite3_version + '.zip'
}
task cleanAll(type: Delete, dependsOn: [clean, cleanAssets, cleanIconv,
cleanFreetype, cleanIrrlicht, cleanLevelDB, cleanSQLite3, cleanCURL,
cleanOpenSSL, cleanOpenAL, cleanOgg]) {
delete 'deps'
delete 'gen'
delete 'libs'
delete 'obj'
delete 'bin'
delete 'Debug'
delete 'and_env'
}
dependencies {
implementation 'com.android.support:support-v4:28.0.0'
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@ -0,0 +1,11 @@
<#if isLowMemory>
org.gradle.jvmargs=-Xmx4G -XX:MaxPermSize=2G -XX:+HeapDumpOnOutOfMemoryError
<#else>
org.gradle.jvmargs=-Xmx16G -XX:MaxPermSize=8G -XX:+HeapDumpOnOutOfMemoryError
</#if>
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.parallel.threads=8
org.gradle.configureondemand=true
android.enableJetifier=true
android.useAndroidX=true

Binary file not shown.

View File

@ -1,6 +1,2 @@
#Mon Oct 15 00:47:03 CEST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
#Mon Apr 06 00:06:16 CEST 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip

22
build/android/gradlew vendored
View File

@ -1,5 +1,21 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@ -109,8 +125,8 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`

View File

@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

View File

@ -1,434 +0,0 @@
LOCAL_PATH := $(call my-dir)/..
#LOCAL_ADDRESS_SANITIZER:=true
include $(CLEAR_VARS)
LOCAL_MODULE := Irrlicht
LOCAL_SRC_FILES := deps/irrlicht/lib/Android/libIrrlicht.a
include $(PREBUILT_STATIC_LIBRARY)
ifeq ($(HAVE_LEVELDB), 1)
include $(CLEAR_VARS)
LOCAL_MODULE := LevelDB
LOCAL_SRC_FILES := deps/leveldb/libleveldb.a
include $(PREBUILT_STATIC_LIBRARY)
endif
include $(CLEAR_VARS)
LOCAL_MODULE := curl
LOCAL_SRC_FILES := deps/curl/lib/.libs/libcurl.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := freetype
LOCAL_SRC_FILES := deps/freetype2-android/Android/obj/local/$(TARGET_ARCH_ABI)/libfreetype2-static.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := iconv
LOCAL_SRC_FILES := deps/libiconv/lib/.libs/libiconv.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := openal
LOCAL_SRC_FILES := deps/openal-soft/libs/$(TARGET_LIBDIR)/libopenal.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := ogg
LOCAL_SRC_FILES := deps/libvorbis-libogg-android/libs/$(TARGET_LIBDIR)/libogg.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := vorbis
LOCAL_SRC_FILES := deps/libvorbis-libogg-android/libs/$(TARGET_LIBDIR)/libvorbis.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := ssl
LOCAL_SRC_FILES := deps/openssl/libssl.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := crypto
LOCAL_SRC_FILES := deps/openssl/libcrypto.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := minetest
LOCAL_CPP_FEATURES += exceptions
ifdef GPROF
GPROF_DEF=-DGPROF
endif
LOCAL_CFLAGS := -D_IRR_ANDROID_PLATFORM_ \
-DHAVE_TOUCHSCREENGUI \
-DUSE_CURL=1 \
-DUSE_SOUND=1 \
-DUSE_FREETYPE=1 \
-DUSE_LEVELDB=$(HAVE_LEVELDB) \
$(GPROF_DEF) \
-pipe -fstrict-aliasing
ifndef NDEBUG
LOCAL_CFLAGS += -g -D_DEBUG -O0 -fno-omit-frame-pointer
else
LOCAL_CFLAGS += $(TARGET_CFLAGS_ADDON)
endif
ifdef GPROF
PROFILER_LIBS := android-ndk-profiler
LOCAL_CFLAGS += -pg
endif
# LOCAL_CFLAGS += -fsanitize=address
# LOCAL_LDFLAGS += -fsanitize=address
ifeq ($(TARGET_ABI),x86)
LOCAL_CFLAGS += -fno-stack-protector
endif
LOCAL_C_INCLUDES := \
jni/src \
jni/src/script \
jni/lib/gmp \
jni/lib/lua/src \
jni/lib/jsoncpp \
jni/src/cguittfont \
deps/irrlicht/include \
deps/libiconv/include \
deps/freetype2-android/include \
deps/curl/include \
deps/openal-soft/jni/OpenAL/include \
deps/libvorbis-libogg-android/jni/include \
deps/leveldb/include \
deps/sqlite/
LOCAL_SRC_FILES := \
jni/src/ban.cpp \
jni/src/chat.cpp \
jni/src/client/activeobjectmgr.cpp \
jni/src/client/camera.cpp \
jni/src/client/client.cpp \
jni/src/client/clientenvironment.cpp \
jni/src/client/clientlauncher.cpp \
jni/src/client/clientmap.cpp \
jni/src/client/clientmedia.cpp \
jni/src/client/clientobject.cpp \
jni/src/client/clouds.cpp \
jni/src/client/content_cao.cpp \
jni/src/client/content_cso.cpp \
jni/src/client/content_mapblock.cpp \
jni/src/client/filecache.cpp \
jni/src/client/fontengine.cpp \
jni/src/client/game.cpp \
jni/src/client/gameui.cpp \
jni/src/client/guiscalingfilter.cpp \
jni/src/client/hud.cpp \
jni/src/clientiface.cpp \
jni/src/client/imagefilters.cpp \
jni/src/client/inputhandler.cpp \
jni/src/client/joystick_controller.cpp \
jni/src/client/keycode.cpp \
jni/src/client/localplayer.cpp \
jni/src/client/mapblock_mesh.cpp \
jni/src/client/mesh.cpp \
jni/src/client/meshgen/collector.cpp \
jni/src/client/mesh_generator_thread.cpp \
jni/src/client/minimap.cpp \
jni/src/client/particles.cpp \
jni/src/client/render/anaglyph.cpp \
jni/src/client/render/core.cpp \
jni/src/client/render/factory.cpp \
jni/src/client/renderingengine.cpp \
jni/src/client/render/interlaced.cpp \
jni/src/client/render/pageflip.cpp \
jni/src/client/render/plain.cpp \
jni/src/client/render/sidebyside.cpp \
jni/src/client/render/stereo.cpp \
jni/src/client/shader.cpp \
jni/src/client/sky.cpp \
jni/src/client/sound.cpp \
jni/src/client/sound_openal.cpp \
jni/src/client/tile.cpp \
jni/src/client/wieldmesh.cpp \
jni/src/collision.cpp \
jni/src/content/content.cpp \
jni/src/content_mapnode.cpp \
jni/src/content/mods.cpp \
jni/src/content_nodemeta.cpp \
jni/src/content/packages.cpp \
jni/src/content_sao.cpp \
jni/src/content/subgames.cpp \
jni/src/convert_json.cpp \
jni/src/craftdef.cpp \
jni/src/database/database.cpp \
jni/src/database/database-dummy.cpp \
jni/src/database/database-files.cpp \
jni/src/database/database-leveldb.cpp \
jni/src/database/database-sqlite3.cpp \
jni/src/debug.cpp \
jni/src/defaultsettings.cpp \
jni/src/emerge.cpp \
jni/src/environment.cpp \
jni/src/face_position_cache.cpp \
jni/src/filesys.cpp \
jni/src/genericobject.cpp \
jni/src/gettext.cpp \
jni/src/gui/guiChatConsole.cpp \
jni/src/gui/guiConfirmRegistration.cpp \
jni/src/gui/guiEditBoxWithScrollbar.cpp \
jni/src/gui/guiEngine.cpp \
jni/src/gui/guiFormSpecMenu.cpp \
jni/src/gui/guiKeyChangeMenu.cpp \
jni/src/gui/guiPasswordChange.cpp \
jni/src/gui/guiPathSelectMenu.cpp \
jni/src/gui/guiTable.cpp \
jni/src/gui/guiVolumeChange.cpp \
jni/src/gui/intlGUIEditBox.cpp \
jni/src/gui/modalMenu.cpp \
jni/src/gui/profilergraph.cpp \
jni/src/gui/touchscreengui.cpp \
jni/src/httpfetch.cpp \
jni/src/hud.cpp \
jni/src/inventory.cpp \
jni/src/inventorymanager.cpp \
jni/src/irrlicht_changes/CGUITTFont.cpp \
jni/src/irrlicht_changes/static_text.cpp \
jni/src/itemdef.cpp \
jni/src/itemstackmetadata.cpp \
jni/src/light.cpp \
jni/src/log.cpp \
jni/src/main.cpp \
jni/src/mapblock.cpp \
jni/src/map.cpp \
jni/src/mapgen/cavegen.cpp \
jni/src/mapgen/dungeongen.cpp \
jni/src/mapgen/mapgen_carpathian.cpp \
jni/src/mapgen/mapgen.cpp \
jni/src/mapgen/mapgen_flat.cpp \
jni/src/mapgen/mapgen_fractal.cpp \
jni/src/mapgen/mapgen_singlenode.cpp \
jni/src/mapgen/mapgen_v5.cpp \
jni/src/mapgen/mapgen_v6.cpp \
jni/src/mapgen/mapgen_v7.cpp \
jni/src/mapgen/mapgen_valleys.cpp \
jni/src/mapgen/mg_biome.cpp \
jni/src/mapgen/mg_decoration.cpp \
jni/src/mapgen/mg_ore.cpp \
jni/src/mapgen/mg_schematic.cpp \
jni/src/mapgen/treegen.cpp \
jni/src/mapnode.cpp \
jni/src/mapsector.cpp \
jni/src/map_settings_manager.cpp \
jni/src/metadata.cpp \
jni/src/modchannels.cpp \
jni/src/nameidmapping.cpp \
jni/src/nodedef.cpp \
jni/src/nodemetadata.cpp \
jni/src/nodetimer.cpp \
jni/src/noise.cpp \
jni/src/objdef.cpp \
jni/src/object_properties.cpp \
jni/src/pathfinder.cpp \
jni/src/player.cpp \
jni/src/porting_android.cpp \
jni/src/porting.cpp \
jni/src/profiler.cpp \
jni/src/quicktune.cpp \
jni/src/raycast.cpp \
jni/src/reflowscan.cpp \
jni/src/remoteplayer.cpp \
jni/src/rollback.cpp \
jni/src/rollback_interface.cpp \
jni/src/serialization.cpp \
jni/src/server/activeobjectmgr.cpp \
jni/src/server.cpp \
jni/src/serverenvironment.cpp \
jni/src/serverlist.cpp \
jni/src/server/mods.cpp \
jni/src/serverobject.cpp \
jni/src/settings.cpp \
jni/src/staticobject.cpp \
jni/src/tileanimation.cpp \
jni/src/tool.cpp \
jni/src/translation.cpp \
jni/src/unittest/test_authdatabase.cpp \
jni/src/unittest/test_collision.cpp \
jni/src/unittest/test_compression.cpp \
jni/src/unittest/test_connection.cpp \
jni/src/unittest/test.cpp \
jni/src/unittest/test_filepath.cpp \
jni/src/unittest/test_gameui.cpp \
jni/src/unittest/test_inventory.cpp \
jni/src/unittest/test_mapnode.cpp \
jni/src/unittest/test_map_settings_manager.cpp \
jni/src/unittest/test_nodedef.cpp \
jni/src/unittest/test_noderesolver.cpp \
jni/src/unittest/test_noise.cpp \
jni/src/unittest/test_objdef.cpp \
jni/src/unittest/test_profiler.cpp \
jni/src/unittest/test_random.cpp \
jni/src/unittest/test_schematic.cpp \
jni/src/unittest/test_serialization.cpp \
jni/src/unittest/test_settings.cpp \
jni/src/unittest/test_socket.cpp \
jni/src/unittest/test_utilities.cpp \
jni/src/unittest/test_voxelalgorithms.cpp \
jni/src/unittest/test_voxelmanipulator.cpp \
jni/src/util/areastore.cpp \
jni/src/util/auth.cpp \
jni/src/util/base64.cpp \
jni/src/util/directiontables.cpp \
jni/src/util/enriched_string.cpp \
jni/src/util/ieee_float.cpp \
jni/src/util/numeric.cpp \
jni/src/util/pointedthing.cpp \
jni/src/util/serialize.cpp \
jni/src/util/sha1.cpp \
jni/src/util/srp.cpp \
jni/src/util/string.cpp \
jni/src/util/timetaker.cpp \
jni/src/version.cpp \
jni/src/voxelalgorithms.cpp \
jni/src/voxel.cpp
# intentionally kept out (we already build openssl itself): jni/src/util/sha256.c
# Network
LOCAL_SRC_FILES += \
jni/src/network/address.cpp \
jni/src/network/connection.cpp \
jni/src/network/networkpacket.cpp \
jni/src/network/clientopcodes.cpp \
jni/src/network/clientpackethandler.cpp \
jni/src/network/connectionthreads.cpp \
jni/src/network/serveropcodes.cpp \
jni/src/network/serverpackethandler.cpp \
jni/src/network/socket.cpp \
# lua api
LOCAL_SRC_FILES += \
jni/src/script/common/c_content.cpp \
jni/src/script/common/c_converter.cpp \
jni/src/script/common/c_internal.cpp \
jni/src/script/common/c_types.cpp \
jni/src/script/common/helper.cpp \
jni/src/script/cpp_api/s_async.cpp \
jni/src/script/cpp_api/s_base.cpp \
jni/src/script/cpp_api/s_client.cpp \
jni/src/script/cpp_api/s_entity.cpp \
jni/src/script/cpp_api/s_env.cpp \
jni/src/script/cpp_api/s_inventory.cpp \
jni/src/script/cpp_api/s_item.cpp \
jni/src/script/cpp_api/s_mainmenu.cpp \
jni/src/script/cpp_api/s_modchannels.cpp \
jni/src/script/cpp_api/s_node.cpp \
jni/src/script/cpp_api/s_nodemeta.cpp \
jni/src/script/cpp_api/s_player.cpp \
jni/src/script/cpp_api/s_security.cpp \
jni/src/script/cpp_api/s_server.cpp \
jni/src/script/lua_api/l_areastore.cpp \
jni/src/script/lua_api/l_auth.cpp \
jni/src/script/lua_api/l_base.cpp \
jni/src/script/lua_api/l_camera.cpp \
jni/src/script/lua_api/l_client.cpp \
jni/src/script/lua_api/l_craft.cpp \
jni/src/script/lua_api/l_env.cpp \
jni/src/script/lua_api/l_inventory.cpp \
jni/src/script/lua_api/l_item.cpp \
jni/src/script/lua_api/l_itemstackmeta.cpp\
jni/src/script/lua_api/l_localplayer.cpp \
jni/src/script/lua_api/l_mainmenu.cpp \
jni/src/script/lua_api/l_mapgen.cpp \
jni/src/script/lua_api/l_metadata.cpp \
jni/src/script/lua_api/l_minimap.cpp \
jni/src/script/lua_api/l_modchannels.cpp \
jni/src/script/lua_api/l_nodemeta.cpp \
jni/src/script/lua_api/l_nodetimer.cpp \
jni/src/script/lua_api/l_noise.cpp \
jni/src/script/lua_api/l_object.cpp \
jni/src/script/lua_api/l_playermeta.cpp \
jni/src/script/lua_api/l_particles.cpp \
jni/src/script/lua_api/l_particles_local.cpp\
jni/src/script/lua_api/l_rollback.cpp \
jni/src/script/lua_api/l_server.cpp \
jni/src/script/lua_api/l_settings.cpp \
jni/src/script/lua_api/l_sound.cpp \
jni/src/script/lua_api/l_http.cpp \
jni/src/script/lua_api/l_storage.cpp \
jni/src/script/lua_api/l_util.cpp \
jni/src/script/lua_api/l_vmanip.cpp \
jni/src/script/scripting_client.cpp \
jni/src/script/scripting_server.cpp \
jni/src/script/scripting_mainmenu.cpp
#freetype2 support
#LOCAL_SRC_FILES += jni/src/cguittfont/xCGUITTFont.cpp
# GMP
LOCAL_SRC_FILES += jni/lib/gmp/mini-gmp.c
# Lua
LOCAL_SRC_FILES += \
jni/lib/lua/src/lapi.c \
jni/lib/lua/src/lauxlib.c \
jni/lib/lua/src/lbaselib.c \
jni/lib/lua/src/lcode.c \
jni/lib/lua/src/ldblib.c \
jni/lib/lua/src/ldebug.c \
jni/lib/lua/src/ldo.c \
jni/lib/lua/src/ldump.c \
jni/lib/lua/src/lfunc.c \
jni/lib/lua/src/lgc.c \
jni/lib/lua/src/linit.c \
jni/lib/lua/src/liolib.c \
jni/lib/lua/src/llex.c \
jni/lib/lua/src/lmathlib.c \
jni/lib/lua/src/lmem.c \
jni/lib/lua/src/loadlib.c \
jni/lib/lua/src/lobject.c \
jni/lib/lua/src/lopcodes.c \
jni/lib/lua/src/loslib.c \
jni/lib/lua/src/lparser.c \
jni/lib/lua/src/lstate.c \
jni/lib/lua/src/lstring.c \
jni/lib/lua/src/lstrlib.c \
jni/lib/lua/src/ltable.c \
jni/lib/lua/src/ltablib.c \
jni/lib/lua/src/ltm.c \
jni/lib/lua/src/lundump.c \
jni/lib/lua/src/lvm.c \
jni/lib/lua/src/lzio.c \
jni/lib/lua/src/print.c
# SQLite3
LOCAL_SRC_FILES += deps/sqlite/sqlite3.c
# Threading
LOCAL_SRC_FILES += \
jni/src/threading/event.cpp \
jni/src/threading/semaphore.cpp \
jni/src/threading/thread.cpp
# JSONCPP
LOCAL_SRC_FILES += jni/lib/jsoncpp/jsoncpp.cpp
LOCAL_SHARED_LIBRARIES := iconv openal ogg vorbis
LOCAL_STATIC_LIBRARIES := Irrlicht freetype curl ssl crypto android_native_app_glue $(PROFILER_LIBS)
ifeq ($(HAVE_LEVELDB), 1)
LOCAL_STATIC_LIBRARIES += LevelDB
endif
LOCAL_LDLIBS := -lEGL -llog -lGLESv1_CM -lGLESv2 -lz -landroid
include $(BUILD_SHARED_LIBRARY)
# at the end of Android.mk
ifdef GPROF
$(call import-module,android-ndk-profiler)
endif
$(call import-module,android/native_app_glue)

View File

@ -1,9 +0,0 @@
APP_PLATFORM := ${APP_PLATFORM}
APP_ABI := ${TARGET_ABI}
APP_STL := c++_shared
APP_MODULES := minetest
ifndef NDEBUG
APP_OPTIM := debug
endif
APP_CPPFLAGS += -fexceptions -std=c++11 -frtti

View File

@ -1,7 +0,0 @@
APP_PLATFORM := ${APP_PLATFORM}
APP_ABI := ${TARGET_ABI}
APP_STL := c++_shared
APP_DEPRECATED_HEADERS := true
APP_CFLAGS += ${TARGET_CFLAGS_ADDON}
APP_CPPFLAGS += ${TARGET_CXXFLAGS_ADDON} -fexceptions -std=c++11

View File

@ -1,8 +0,0 @@
APP_PLATFORM := ${APP_PLATFORM}
APP_ABI := ${TARGET_ABI}
APP_STL := c++_shared
APP_DEPRECATED_HEADERS := true
APP_MODULES := Irrlicht
APP_CLAFGS += ${TARGET_CFLAGS_ADDON}
APP_CPPFLAGS += ${TARGET_CXXFLAGS_ADDON} -fexceptions

View File

@ -0,0 +1,59 @@
apply plugin: 'com.android.library'
import org.ajoberstar.grgit.Grgit
android {
compileSdkVersion 29
buildToolsVersion '29.0.3'
ndkVersion '21.1.6352462'
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
externalNativeBuild {
ndkBuild {
arguments '-j8',
"versionMajor=${versionMajor}",
"versionMinor=${versionMinor}",
"versionPatch=${versionPatch}",
"versionExtra=${versionExtra}"
}
}
}
externalNativeBuild {
ndkBuild {
path file('jni/Android.mk')
}
}
// supported architectures
splits {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a'//, 'x86'
}
}
buildTypes {
release {
externalNativeBuild {
ndkBuild {
arguments 'NDEBUG=1'
}
}
}
}
}
task cloneGitRepo() {
def destination = file('deps')
if(!destination.exists()) {
def grgit = Grgit.clone(
dir: destination,
uri: 'https://github.com/minetest/minetest_android_deps_binaries'
)
grgit.close()
}
}
preBuild.dependsOn cloneGitRepo

View File

@ -0,0 +1,219 @@
LOCAL_PATH := $(call my-dir)/..
#LOCAL_ADDRESS_SANITIZER:=true
include $(CLEAR_VARS)
LOCAL_MODULE := Curl
LOCAL_SRC_FILES := deps/Android/Curl/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libcurl.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := Freetype
LOCAL_SRC_FILES := deps/Android/Freetype/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libfreetype.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := Irrlicht
LOCAL_SRC_FILES := deps/Android/Irrlicht/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libIrrlicht.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
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := OpenAL
LOCAL_SRC_FILES := deps/Android/OpenAL-Soft/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libopenal.a
include $(PREBUILT_STATIC_LIBRARY)
# You can use `OpenSSL and Crypto` instead `mbedTLS mbedx509 mbedcrypto`,
#but it increase APK size on ~0.7MB
#include $(CLEAR_VARS)
#LOCAL_MODULE := OpenSSL
#LOCAL_SRC_FILES := deps/Android/OpenSSL/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libssl.a
#include $(PREBUILT_STATIC_LIBRARY)
#include $(CLEAR_VARS)
#LOCAL_MODULE := Crypto
#LOCAL_SRC_FILES := deps/Android/OpenSSL/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libcrypto.a
#include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := Vorbis
LOCAL_SRC_FILES := deps/Android/Vorbis/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libvorbis.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := Minetest
LOCAL_CFLAGS += \
-DJSONCPP_NO_LOCALE_SUPPORT \
-DHAVE_TOUCHSCREENGUI \
-DENABLE_GLES=1 \
-DUSE_CURL=1 \
-DUSE_SOUND=1 \
-DUSE_FREETYPE=1 \
-DUSE_LEVELDB=0 \
-DUSE_LUAJIT=1 \
-DVERSION_MAJOR=${versionMajor} \
-DVERSION_MINOR=${versionMinor} \
-DVERSION_PATCH=${versionPatch} \
-DVERSION_EXTRA=${versionExtra} \
$(GPROF_DEF)
ifdef NDEBUG
LOCAL_CFLAGS += -DNDEBUG=1
endif
ifdef GPROF
GPROF_DEF := -DGPROF
PROFILER_LIBS := android-ndk-profiler
LOCAL_CFLAGS += -pg
endif
LOCAL_C_INCLUDES := \
../../../src \
../../../src/script \
../../../lib/gmp \
../../../lib/jsoncpp \
deps/Android/Curl/include \
deps/Android/Freetype/include \
deps/Android/Irrlicht/include \
deps/Android/LevelDB/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
LOCAL_SRC_FILES := \
$(wildcard ../../../src/client/*.cpp) \
$(wildcard ../../../src/client/*/*.cpp) \
$(wildcard ../../../src/content/*.cpp) \
../../../src/database/database.cpp \
../../../src/database/database-dummy.cpp \
../../../src/database/database-files.cpp \
../../../src/database/database-sqlite3.cpp \
$(wildcard ../../../src/gui/*.cpp) \
$(wildcard ../../../src/irrlicht_changes/*.cpp) \
$(wildcard ../../../src/mapgen/*.cpp) \
$(wildcard ../../../src/network/*.cpp) \
$(wildcard ../../../src/script/*.cpp) \
$(wildcard ../../../src/script/*/*.cpp) \
$(wildcard ../../../src/server/*.cpp) \
$(wildcard ../../../src/threading/*.cpp) \
$(wildcard ../../../src/util/*.c) \
$(wildcard ../../../src/util/*.cpp) \
../../../src/ban.cpp \
../../../src/chat.cpp \
../../../src/clientiface.cpp \
../../../src/collision.cpp \
../../../src/content_mapnode.cpp \
../../../src/content_nodemeta.cpp \
../../../src/convert_json.cpp \
../../../src/craftdef.cpp \
../../../src/debug.cpp \
../../../src/defaultsettings.cpp \
../../../src/emerge.cpp \
../../../src/environment.cpp \
../../../src/face_position_cache.cpp \
../../../src/filesys.cpp \
../../../src/gettext.cpp \
../../../src/httpfetch.cpp \
../../../src/hud.cpp \
../../../src/inventory.cpp \
../../../src/inventorymanager.cpp \
../../../src/itemdef.cpp \
../../../src/itemstackmetadata.cpp \
../../../src/light.cpp \
../../../src/log.cpp \
../../../src/main.cpp \
../../../src/map.cpp \
../../../src/map_settings_manager.cpp \
../../../src/mapblock.cpp \
../../../src/mapnode.cpp \
../../../src/mapsector.cpp \
../../../src/metadata.cpp \
../../../src/modchannels.cpp \
../../../src/nameidmapping.cpp \
../../../src/nodedef.cpp \
../../../src/nodemetadata.cpp \
../../../src/nodetimer.cpp \
../../../src/noise.cpp \
../../../src/objdef.cpp \
../../../src/object_properties.cpp \
../../../src/particles.cpp \
../../../src/pathfinder.cpp \
../../../src/player.cpp \
../../../src/porting.cpp \
../../../src/porting_android.cpp \
../../../src/profiler.cpp \
../../../src/raycast.cpp \
../../../src/reflowscan.cpp \
../../../src/remoteplayer.cpp \
../../../src/rollback.cpp \
../../../src/rollback_interface.cpp \
../../../src/serialization.cpp \
../../../src/server.cpp \
../../../src/serverenvironment.cpp \
../../../src/serverlist.cpp \
../../../src/settings.cpp \
../../../src/staticobject.cpp \
../../../src/texture_override.cpp \
../../../src/tileanimation.cpp \
../../../src/tool.cpp \
../../../src/translation.cpp \
../../../src/version.cpp \
../../../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 android_native_app_glue $(PROFILER_LIBS) #LevelDB
#OpenSSL Crypto
LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES
include $(BUILD_SHARED_LIBRARY)
ifdef GPROF
$(call import-module,android-ndk-profiler)
endif
$(call import-module,android/native_app_glue)

View File

@ -0,0 +1,32 @@
APP_PLATFORM := ${APP_PLATFORM}
APP_ABI := ${TARGET_ABI}
APP_STL := c++_shared
NDK_TOOLCHAIN_VERSION := clang
APP_SHORT_COMMANDS := true
APP_MODULES := Minetest
APP_CPPFLAGS := -Ofast -fvisibility=hidden -fexceptions -Wno-deprecated-declarations -Wno-extra-tokens
ifeq ($(APP_ABI),armeabi-v7a)
APP_CPPFLAGS += -march=armv7-a -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
ifndef NDEBUG
APP_CPPFLAGS := -g -D_DEBUG -O0 -fno-omit-frame-pointer -fexceptions
endif
APP_CFLAGS := $(APP_CPPFLAGS) -Wno-parentheses-equality #-Werror=shorten-64-to-32
APP_CXXFLAGS := $(APP_CPPFLAGS) -frtti -std=gnu++17
APP_LDFLAGS := -Wl,--no-warn-mismatch,--gc-sections,--icf=safe
ifeq ($(APP_ABI),arm64-v8a)
APP_LDFLAGS := -Wl,--no-warn-mismatch,--gc-sections
endif
ifndef NDEBUG
APP_LDFLAGS :=
endif

View File

@ -0,0 +1 @@
<manifest package="net.minetest" />

View File

@ -1,20 +0,0 @@
--- irrlicht/source/Irrlicht/Android/CIrrDeviceAndroid.cpp.orig 2015-08-29 15:43:09.000000000 +0300
+++ irrlicht/source/Irrlicht/Android/CIrrDeviceAndroid.cpp 2016-05-13 21:36:22.880388505 +0300
@@ -486,7 +486,7 @@
event.KeyInput.Char = 0;
}
- device->postEventFromUser(event);
+ status = device->postEventFromUser(event);
}
break;
default:
@@ -543,7 +543,7 @@
KeyMap[1] = KEY_LBUTTON; // AKEYCODE_SOFT_LEFT
KeyMap[2] = KEY_RBUTTON; // AKEYCODE_SOFT_RIGHT
KeyMap[3] = KEY_HOME; // AKEYCODE_HOME
- KeyMap[4] = KEY_BACK; // AKEYCODE_BACK
+ KeyMap[4] = KEY_CANCEL; // AKEYCODE_BACK
KeyMap[5] = KEY_UNKNOWN; // AKEYCODE_CALL
KeyMap[6] = KEY_UNKNOWN; // AKEYCODE_ENDCALL
KeyMap[7] = KEY_KEY_0; // AKEYCODE_0

View File

@ -1,13 +0,0 @@
--- irrlicht/source/Irrlicht/CEGLManager.cpp.orig 2018-09-11 18:19:51.453403631 +0300
+++ irrlicht/source/Irrlicht/CEGLManager.cpp 2018-09-11 18:36:24.603471869 +0300
@@ -9,6 +9,10 @@
#include "irrString.h"
#include "os.h"
+#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
+#include <android/native_activity.h>
+#endif
+
namespace irr
{
namespace video

View File

@ -1,240 +0,0 @@
--- irrlicht/source/Irrlicht/COGLESTexture.cpp.orig 2014-06-22 17:01:13.266568869 +0200
+++ irrlicht/source/Irrlicht/COGLESTexture.cpp 2014-06-22 17:03:59.298572810 +0200
@@ -366,112 +366,140 @@
void(*convert)(const void*, s32, void*) = 0;
getFormatParameters(ColorFormat, InternalFormat, filtering, PixelFormat, PixelType, convert);
- // make sure we don't change the internal format of existing images
- if (!newTexture)
- InternalFormat = oldInternalFormat;
-
- Driver->setActiveTexture(0, this);
-
- if (Driver->testGLError())
- os::Printer::log("Could not bind Texture", ELL_ERROR);
-
- // mipmap handling for main texture
- if (!level && newTexture)
- {
- // auto generate if possible and no mipmap data is given
- if (!IsCompressed && HasMipMaps && !mipmapData && Driver->queryFeature(EVDF_MIP_MAP_AUTO_UPDATE))
- {
- if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED))
- glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
- else if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_QUALITY))
- glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
- else
- glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE);
+ bool retry = false;
+
+ do {
+ if (retry) {
+ InternalFormat = GL_RGBA;
+ PixelFormat = GL_RGBA;
+ convert = CColorConverter::convert_A8R8G8B8toA8B8G8R8;
+ }
+ // make sure we don't change the internal format of existing images
+ if (!newTexture)
+ InternalFormat = oldInternalFormat;
- glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
- AutomaticMipmapUpdate=true;
- }
+ Driver->setActiveTexture(0, this);
- // enable bilinear filter without mipmaps
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
- }
+ if (Driver->testGLError())
+ os::Printer::log("Could not bind Texture", ELL_ERROR);
- // now get image data and upload to GPU
+ // mipmap handling for main texture
+ if (!level && newTexture)
+ {
+ // auto generate if possible and no mipmap data is given
+ if (!IsCompressed && HasMipMaps && !mipmapData && Driver->queryFeature(EVDF_MIP_MAP_AUTO_UPDATE))
+ {
+ if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED))
+ glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
+ else if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_QUALITY))
+ glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
+ else
+ glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
+ AutomaticMipmapUpdate=true;
+ }
+
+ // enable bilinear filter without mipmaps
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
+ }
- u32 compressedImageSize = IImage::getCompressedImageSize(ColorFormat, image->getDimension().Width, image->getDimension().Height);
+ // now get image data and upload to GPU
- void* source = image->lock();
+ u32 compressedImageSize = IImage::getCompressedImageSize(ColorFormat, image->getDimension().Width, image->getDimension().Height);
- IImage* tmpImage = 0;
+ void* source = image->lock();
- if (convert)
- {
- tmpImage = new CImage(image->getColorFormat(), image->getDimension());
- void* dest = tmpImage->lock();
- convert(source, image->getDimension().getArea(), dest);
- image->unlock();
- source = dest;
- }
+ IImage* tmpImage = 0;
- if (newTexture)
- {
- if (IsCompressed)
+ if (convert)
{
- glCompressedTexImage2D(GL_TEXTURE_2D, 0, InternalFormat, image->getDimension().Width,
- image->getDimension().Height, 0, compressedImageSize, source);
+ tmpImage = new CImage(image->getColorFormat(), image->getDimension());
+ void* dest = tmpImage->lock();
+ convert(source, image->getDimension().getArea(), dest);
+ image->unlock();
+ source = dest;
}
- else
- glTexImage2D(GL_TEXTURE_2D, level, InternalFormat, image->getDimension().Width,
- image->getDimension().Height, 0, PixelFormat, PixelType, source);
- }
- else
- {
- if (IsCompressed)
+
+ if (newTexture)
{
- glCompressedTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width,
- image->getDimension().Height, PixelFormat, compressedImageSize, source);
+ if (IsCompressed)
+ {
+ glCompressedTexImage2D(GL_TEXTURE_2D, 0, InternalFormat, image->getDimension().Width,
+ image->getDimension().Height, 0, compressedImageSize, source);
+ }
+ else
+ glTexImage2D(GL_TEXTURE_2D, level, InternalFormat, image->getDimension().Width,
+ image->getDimension().Height, 0, PixelFormat, PixelType, source);
}
else
- glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width,
- image->getDimension().Height, PixelFormat, PixelType, source);
- }
-
- if (convert)
- {
- tmpImage->unlock();
- tmpImage->drop();
- }
- else
- image->unlock();
-
- if (!level && newTexture)
- {
- if (IsCompressed && !mipmapData)
{
- if (image->hasMipMaps())
- mipmapData = static_cast<u8*>(image->lock())+compressedImageSize;
+ if (IsCompressed)
+ {
+ glCompressedTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width,
+ image->getDimension().Height, PixelFormat, compressedImageSize, source);
+ }
else
- HasMipMaps = false;
+ glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width,
+ image->getDimension().Height, PixelFormat, PixelType, source);
}
- regenerateMipMapLevels(mipmapData);
-
- if (HasMipMaps) // might have changed in regenerateMipMapLevels
+ if (convert)
{
- // enable bilinear mipmap filter
- GLint filteringMipMaps = GL_LINEAR_MIPMAP_NEAREST;
-
- if (filtering != GL_LINEAR)
- filteringMipMaps = GL_NEAREST_MIPMAP_NEAREST;
+ tmpImage->unlock();
+ tmpImage->drop();
+ }
+ else
+ image->unlock();
+
+ if (glGetError() != GL_NO_ERROR) {
+ static bool warned = false;
+ if ((!retry) && (ColorFormat == ECF_A8R8G8B8)) {
+
+ if (!warned) {
+ os::Printer::log("Your driver claims to support GL_BGRA but fails on trying to upload a texture, converting to GL_RGBA and trying again", ELL_ERROR);
+ warned = true;
+ }
+ }
+ else if (retry) {
+ os::Printer::log("Neither uploading texture as GL_BGRA nor, converted one using GL_RGBA succeeded", ELL_ERROR);
+ }
+ retry = !retry;
+ continue;
+ } else {
+ retry = false;
+ }
- glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filteringMipMaps);
- glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
+ if (!level && newTexture)
+ {
+ if (IsCompressed && !mipmapData)
+ {
+ if (image->hasMipMaps())
+ mipmapData = static_cast<u8*>(image->lock())+compressedImageSize;
+ else
+ HasMipMaps = false;
+ }
+
+ regenerateMipMapLevels(mipmapData);
+
+ if (HasMipMaps) // might have changed in regenerateMipMapLevels
+ {
+ // enable bilinear mipmap filter
+ GLint filteringMipMaps = GL_LINEAR_MIPMAP_NEAREST;
+
+ if (filtering != GL_LINEAR)
+ filteringMipMaps = GL_NEAREST_MIPMAP_NEAREST;
+
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filteringMipMaps);
+ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
+ }
}
- }
- if (Driver->testGLError())
- os::Printer::log("Could not glTexImage2D", ELL_ERROR);
+ if (Driver->testGLError())
+ os::Printer::log("Could not glTexImage2D", ELL_ERROR);
+ }
+ while(retry);
}
--- irrlicht/source/Irrlicht/COGLESTexture.cpp.orig 2014-06-25 00:28:50.820501856 +0200
+++ irrlicht/source/Irrlicht/COGLESTexture.cpp 2014-06-25 00:08:37.712544692 +0200
@@ -422,6 +422,9 @@
source = dest;
}
+ //clear old error
+ glGetError();
+
if (newTexture)
{
if (IsCompressed)

View File

@ -1,30 +0,0 @@
--- irrlicht.orig/include/IEventReceiver.h 2014-06-03 19:43:50.433713133 +0200
+++ irrlicht/include/IEventReceiver.h 2014-06-03 19:44:36.993711489 +0200
@@ -375,6 +375,9 @@
// Y position of simple touch.
s32 Y;
+ // number of current touches
+ s32 touchedCount;
+
//! Type of touch event.
ETOUCH_INPUT_EVENT Event;
};
--- irrlicht.orig/source/Irrlicht/Android/CIrrDeviceAndroid.cpp 2014-06-03 19:43:50.505713130 +0200
+++ irrlicht/source/Irrlicht/Android/CIrrDeviceAndroid.cpp 2014-06-03 19:45:37.265709359 +0200
@@ -315,6 +315,7 @@
event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, i);
event.TouchInput.X = AMotionEvent_getX(androidEvent, i);
event.TouchInput.Y = AMotionEvent_getY(androidEvent, i);
+ event.TouchInput.touchedCount = AMotionEvent_getPointerCount(androidEvent);
device->postEventFromUser(event);
}
@@ -326,6 +327,7 @@
event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, pointerIndex);
event.TouchInput.X = AMotionEvent_getX(androidEvent, pointerIndex);
event.TouchInput.Y = AMotionEvent_getY(androidEvent, pointerIndex);
+ event.TouchInput.touchedCount = AMotionEvent_getPointerCount(androidEvent);
device->postEventFromUser(event);
}

View File

@ -1,39 +0,0 @@
--- a/libcharset/lib/localcharset.c 2015-06-10 11:55:25.933870724 +0200
+++ b/libcharset/lib/localcharset.c 2015-06-10 11:55:39.578063493 +0200
@@ -47,7 +47,7 @@
#if !defined WIN32_NATIVE
# include <unistd.h>
-# if HAVE_LANGINFO_CODESET
+# if HAVE_LANGINFO_CODESET && !(defined __ANDROID__)
# include <langinfo.h>
# else
# if 0 /* see comment below */
@@ -124,7 +124,7 @@ get_charset_aliases (void)
cp = charset_aliases;
if (cp == NULL)
{
-#if !(defined DARWIN7 || defined VMS || defined WIN32_NATIVE || defined __CYGWIN__)
+#if !(defined DARWIN7 || defined VMS || defined WIN32_NATIVE || defined __CYGWIN__ || defined __ANDROID__)
const char *dir;
const char *base = "charset.alias";
char *file_name;
@@ -338,6 +338,9 @@ get_charset_aliases (void)
"CP54936" "\0" "GB18030" "\0"
"CP65001" "\0" "UTF-8" "\0";
# endif
+# if defined __ANDROID__
+ cp = "*" "\0" "UTF-8" "\0";
+# endif
#endif
charset_aliases = cp;
@@ -361,7 +364,7 @@ locale_charset (void)
const char *codeset;
const char *aliases;
-#if !(defined WIN32_NATIVE || defined OS2)
+#if !(defined WIN32_NATIVE || defined OS2 || defined __ANDROID__)
# if HAVE_LANGINFO_CODESET

View File

@ -1,13 +0,0 @@
--- a/srclib/stdio.in.h 2011-08-07 15:42:06.000000000 +0200
+++ b/srclib/stdio.in.h 2015-06-10 09:27:58.129056262 +0200
@@ -695,8 +696,9 @@ _GL_CXXALIASWARN (gets);
/* It is very rare that the developer ever has full control of stdin,
so any use of gets warrants an unconditional warning. Assume it is
always declared, since it is required by C89. */
-_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");
+/*_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");*/
+#define gets(a) fgets( a, sizeof(*(a)), stdin)
#endif
#if @GNULIB_OBSTACK_PRINTF@ || @GNULIB_OBSTACK_PRINTF_POSIX@

View File

@ -1,37 +0,0 @@
--- libvorbis-libogg-android/jni/libvorbis-jni/Android.mk.orig 2014-06-17 19:22:50.621559073 +0200
+++ libvorbis-libogg-android/jni/libvorbis-jni/Android.mk 2014-06-17 19:38:20.641581140 +0200
@@ -4,9 +4,6 @@
LOCAL_MODULE := vorbis-jni
LOCAL_CFLAGS += -I$(LOCAL_PATH)/../include -fsigned-char
-ifeq ($(TARGET_ARCH),arm)
- LOCAL_CFLAGS += -march=armv6 -marm -mfloat-abi=softfp -mfpu=vfp
-endif
LOCAL_SHARED_LIBRARIES := libogg libvorbis
--- libvorbis-libogg-android/jni/libvorbis/Android.mk.orig 2014-06-17 19:22:39.077558797 +0200
+++ libvorbis-libogg-android/jni/libvorbis/Android.mk 2014-06-17 19:38:52.121581887 +0200
@@ -4,9 +4,6 @@
LOCAL_MODULE := libvorbis
LOCAL_CFLAGS += -I$(LOCAL_PATH)/../include -ffast-math -fsigned-char
-ifeq ($(TARGET_ARCH),arm)
- LOCAL_CFLAGS += -march=armv6 -marm -mfloat-abi=softfp -mfpu=vfp
-endif
LOCAL_SHARED_LIBRARIES := libogg
LOCAL_SRC_FILES := \
--- libvorbis-libogg-android/jni/libogg/Android.mk.orig 2014-06-17 19:22:33.965558675 +0200
+++ libvorbis-libogg-android/jni/libogg/Android.mk 2014-06-17 19:38:25.337581252 +0200
@@ -4,10 +4,6 @@
LOCAL_MODULE := libogg
LOCAL_CFLAGS += -I$(LOCAL_PATH)/../include -ffast-math -fsigned-char
-ifeq ($(TARGET_ARCH),arm)
- LOCAL_CFLAGS += -march=armv6 -marm -mfloat-abi=softfp -mfpu=vfp
-endif
-
LOCAL_SRC_FILES := \
bitwise.c \

View File

@ -1,13 +0,0 @@
--- openssl-1.0.2e.orig/Configure 2015-12-03 15:04:23.000000000 +0100
+++ openssl-1.0.2e/Configure 2015-12-14 21:01:40.351265968 +0100
@@ -464,8 +464,10 @@
# Android: linux-* but without pointers to headers and libs.
"android","gcc:-mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -fomit-frame-pointer -Wall::-D_REENTRANT::-ldl:BN_LLONG RC4_CHAR RC4_CHUNK DES_INT DES_UNROLL BF_PTR:${no_asm}:dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)",
"android-x86","gcc:-mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -fomit-frame-pointer -Wall::-D_REENTRANT::-ldl:BN_LLONG ${x86_gcc_des} ${x86_gcc_opts}:".eval{my $asm=${x86_elf_asm};$asm=~s/:elf/:android/;$asm}.":dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)",
+"android-arm","gcc:-march=armv4 -mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -fomit-frame-pointer -Wall::-D_REENTRANT::-ldl:BN_LLONG RC4_CHAR RC4_CHUNK DES_INT DES_UNROLL BF_PTR:${armv4_asm}:dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)",
"android-armv7","gcc:-march=armv7-a -mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -fomit-frame-pointer -Wall::-D_REENTRANT::-ldl:BN_LLONG RC4_CHAR RC4_CHUNK DES_INT DES_UNROLL BF_PTR:${armv4_asm}:dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)",
"android-mips","gcc:-mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -Wall::-D_REENTRANT::-ldl:BN_LLONG RC4_CHAR RC4_CHUNK DES_INT DES_UNROLL BF_PTR:${mips32_asm}:o32:dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)",
+"android-mips32","gcc:-mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -Wall::-D_REENTRANT::-ldl:BN_LLONG RC4_CHAR RC4_CHUNK DES_INT DES_UNROLL BF_PTR:${mips32_asm}:o32:dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)",
#### *BSD [do see comment about ${BSDthreads} above!]
"BSD-generic32","gcc:-O3 -fomit-frame-pointer -Wall::${BSDthreads}:::BN_LLONG RC2_CHAR RC4_INDEX DES_INT DES_UNROLL:${no_asm}:dlfcn:bsd-gcc-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)",

View File

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

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.SET_DEBUG_APP" />
</manifest>

View File

@ -1,79 +0,0 @@
package net.minetest.minetest;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MainActivity extends Activity {
private final static int PERMISSIONS = 1;
private static final String[] REQUIRED_SDK_PERMISSIONS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
checkPermission();
} else {
next();
}
}
protected void checkPermission() {
final List<String> missingPermissions = new ArrayList<String>();
// check required permission
for (final String permission : REQUIRED_SDK_PERMISSIONS) {
final int result = ContextCompat.checkSelfPermission(this, permission);
if (result != PackageManager.PERMISSION_GRANTED) {
missingPermissions.add(permission);
}
}
if (!missingPermissions.isEmpty()) {
// request permission
final String[] permissions = missingPermissions
.toArray(new String[missingPermissions.size()]);
ActivityCompat.requestPermissions(this, permissions, PERMISSIONS);
} else {
final int[] grantResults = new int[REQUIRED_SDK_PERMISSIONS.length];
Arrays.fill(grantResults, PackageManager.PERMISSION_GRANTED);
onRequestPermissionsResult(PERMISSIONS, REQUIRED_SDK_PERMISSIONS,
grantResults);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
@NonNull int[] grantResults) {
switch (requestCode) {
case PERMISSIONS:
for (int index = 0; index < permissions.length; index++) {
if (grantResults[index] != PackageManager.PERMISSION_GRANTED) {
// permission not granted - toast and exit
Toast.makeText(this, R.string.not_granted, Toast.LENGTH_LONG).show();
finish();
return;
}
}
// permission were granted - run
next();
break;
}
}
public void next() {
Intent intent = new Intent(this, MtNativeActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
}
}

View File

@ -1,373 +0,0 @@
package net.minetest.minetest;
import android.app.Activity;
import android.content.res.AssetFileDescriptor;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Vector;
public class MinetestAssetCopy extends Activity {
ProgressBar m_ProgressBar;
TextView m_Filename;
copyAssetTask m_AssetCopy;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.assetcopy);
m_ProgressBar = findViewById(R.id.progressBar1);
m_Filename = findViewById(R.id.textView1);
Display display = getWindowManager().getDefaultDisplay();
m_ProgressBar.getLayoutParams().width = (int) (display.getWidth() * 0.8);
m_ProgressBar.invalidate();
/* check if there's already a copy in progress and reuse in case it is*/
MinetestAssetCopy prevActivity =
(MinetestAssetCopy) getLastNonConfigurationInstance();
if (prevActivity != null) {
m_AssetCopy = prevActivity.m_AssetCopy;
} else {
m_AssetCopy = new copyAssetTask();
m_AssetCopy.execute();
}
}
@Override
protected void onResume() {
super.onResume();
makeFullScreen();
}
public void makeFullScreen() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
this.getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
);
}
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
makeFullScreen();
}
}
/* preserve asset copy background task to prevent restart of copying */
/* this way of doing it is not recommended for latest android version */
/* but the recommended way isn't available on android 2.x */
public Object onRetainNonConfigurationInstance() {
return this;
}
private class copyAssetTask extends AsyncTask<String, Integer, String> {
boolean m_copy_started = false;
String m_Foldername = "media";
Vector<String> m_foldernames;
Vector<String> m_filenames;
Vector<String> m_tocopy;
Vector<String> m_asset_size_unknown;
private long getFullSize(String filename) {
long size = 0;
try {
InputStream src = getAssets().open(filename);
byte[] buf = new byte[4096];
int len = 0;
while ((len = src.read(buf)) > 0) {
size += len;
}
} catch (IOException e) {
e.printStackTrace();
}
return size;
}
@Override
protected String doInBackground(String... files) {
m_foldernames = new Vector<String>();
m_filenames = new Vector<String>();
m_tocopy = new Vector<String>();
m_asset_size_unknown = new Vector<String>();
String baseDir =
Environment.getExternalStorageDirectory().getAbsolutePath()
+ "/";
// prepare temp folder
File TempFolder = new File(baseDir + "Minetest/tmp/");
if (!TempFolder.exists()) {
TempFolder.mkdir();
} else {
File[] todel = TempFolder.listFiles();
for (int i = 0; i < todel.length; i++) {
Log.v("MinetestAssetCopy", "deleting: " + todel[i].getAbsolutePath());
todel[i].delete();
}
}
// add a .nomedia file
try {
OutputStream dst = new FileOutputStream(baseDir + "Minetest/.nomedia");
dst.close();
} catch (IOException e) {
Log.e("MinetestAssetCopy", "Failed to create .nomedia file");
e.printStackTrace();
}
// build lists from prepared data
BuildFolderList();
BuildFileList();
// scan filelist
ProcessFileList();
// doing work
m_copy_started = true;
m_ProgressBar.setMax(m_tocopy.size());
for (int i = 0; i < m_tocopy.size(); i++) {
try {
String filename = m_tocopy.get(i);
publishProgress(i);
boolean asset_size_unknown = false;
long filesize = -1;
if (m_asset_size_unknown.contains(filename)) {
File testme = new File(baseDir + "/" + filename);
if (testme.exists()) {
filesize = testme.length();
}
asset_size_unknown = true;
}
InputStream src;
try {
src = getAssets().open(filename);
} catch (IOException e) {
Log.e("MinetestAssetCopy", "Copying file: " + filename + " FAILED (not in assets)");
e.printStackTrace();
continue;
}
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len = src.read(buf, 0, 1024);
/* following handling is crazy but we need to deal with */
/* compressed assets.Flash chips limited livetime due to */
/* write operations, we can't allow large files to destroy */
/* users flash. */
if (asset_size_unknown) {
if ((len > 0) && (len < buf.length) && (len == filesize)) {
src.close();
continue;
}
if (len == buf.length) {
src.close();
long size = getFullSize(filename);
if (size == filesize) {
continue;
}
src = getAssets().open(filename);
len = src.read(buf, 0, 1024);
}
}
if (len > 0) {
int total_filesize = 0;
OutputStream dst;
try {
dst = new FileOutputStream(baseDir + "/" + filename);
} catch (IOException e) {
Log.e("MinetestAssetCopy", "Copying file: " + baseDir +
"/" + filename + " FAILED (couldn't open output file)");
e.printStackTrace();
src.close();
continue;
}
dst.write(buf, 0, len);
total_filesize += len;
while ((len = src.read(buf)) > 0) {
dst.write(buf, 0, len);
total_filesize += len;
}
dst.close();
Log.v("MinetestAssetCopy", "Copied file: " +
m_tocopy.get(i) + " (" + total_filesize +
" bytes)");
} else if (len < 0) {
Log.e("MinetestAssetCopy", "Copying file: " +
m_tocopy.get(i) + " failed, size < 0");
}
src.close();
} catch (IOException e) {
Log.e("MinetestAssetCopy", "Copying file: " +
m_tocopy.get(i) + " failed");
e.printStackTrace();
}
}
return "";
}
/**
* update progress bar
*/
protected void onProgressUpdate(Integer... progress) {
if (m_copy_started) {
boolean shortened = false;
String todisplay = m_tocopy.get(progress[0]);
m_ProgressBar.setProgress(progress[0]);
m_Filename.setText(todisplay);
} else {
boolean shortened = false;
String todisplay = m_Foldername;
String full_text = "scanning " + todisplay + " ...";
m_Filename.setText(full_text);
}
}
/**
* check all files and folders in filelist
*/
protected void ProcessFileList() {
String FlashBaseDir =
Environment.getExternalStorageDirectory().getAbsolutePath();
Iterator itr = m_filenames.iterator();
while (itr.hasNext()) {
String current_path = (String) itr.next();
String FlashPath = FlashBaseDir + "/" + current_path;
if (isAssetFolder(current_path)) {
/* store information and update gui */
m_Foldername = current_path;
publishProgress(0);
/* open file in order to check if it's a folder */
File current_folder = new File(FlashPath);
if (!current_folder.exists()) {
if (!current_folder.mkdirs()) {
Log.e("MinetestAssetCopy", "\t failed create folder: " +
FlashPath);
} else {
Log.v("MinetestAssetCopy", "\t created folder: " +
FlashPath);
}
}
continue;
}
/* if it's not a folder it's most likely a file */
boolean refresh = true;
File testme = new File(FlashPath);
long asset_filesize = -1;
long stored_filesize = -1;
if (testme.exists()) {
try {
AssetFileDescriptor fd = getAssets().openFd(current_path);
asset_filesize = fd.getLength();
fd.close();
} catch (IOException e) {
refresh = true;
m_asset_size_unknown.add(current_path);
Log.e("MinetestAssetCopy", "Failed to open asset file \"" +
FlashPath + "\" for size check");
}
stored_filesize = testme.length();
if (asset_filesize == stored_filesize) {
refresh = false;
}
}
if (refresh) {
m_tocopy.add(current_path);
}
}
}
/**
* read list of folders prepared on package build
*/
protected void BuildFolderList() {
try {
InputStream is = getAssets().open("index.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line = reader.readLine();
while (line != null) {
m_foldernames.add(line);
line = reader.readLine();
}
is.close();
} catch (IOException e1) {
Log.e("MinetestAssetCopy", "Error on processing index.txt");
e1.printStackTrace();
}
}
/**
* read list of asset files prepared on package build
*/
protected void BuildFileList() {
long entrycount = 0;
try {
InputStream is = getAssets().open("filelist.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line = reader.readLine();
while (line != null) {
m_filenames.add(line);
line = reader.readLine();
entrycount++;
}
is.close();
} catch (IOException e1) {
Log.e("MinetestAssetCopy", "Error on processing filelist.txt");
e1.printStackTrace();
}
}
protected void onPostExecute(String result) {
finish();
}
protected boolean isAssetFolder(String path) {
return m_foldernames.contains(path);
}
}
}

View File

@ -1,87 +0,0 @@
package net.minetest.minetest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.text.InputType;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.widget.EditText;
public class MinetestTextEntry extends Activity {
private final int MultiLineTextInput = 1;
private final int SingleLineTextInput = 2;
private final int SingleLinePasswordInput = 3;
public AlertDialog mTextInputDialog;
public EditText mTextInputWidget;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle b = getIntent().getExtras();
String acceptButton = b.getString("EnterButton");
String hint = b.getString("hint");
String current = b.getString("current");
int editType = b.getInt("editType");
AlertDialog.Builder builder = new AlertDialog.Builder(this);
mTextInputWidget = new EditText(this);
mTextInputWidget.setHint(hint);
mTextInputWidget.setText(current);
mTextInputWidget.setMinWidth(300);
if (editType == SingleLinePasswordInput) {
mTextInputWidget.setInputType(InputType.TYPE_CLASS_TEXT |
InputType.TYPE_TEXT_VARIATION_PASSWORD);
} else {
mTextInputWidget.setInputType(InputType.TYPE_CLASS_TEXT);
}
builder.setView(mTextInputWidget);
if (editType == MultiLineTextInput) {
builder.setPositiveButton(acceptButton, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
pushResult(mTextInputWidget.getText().toString());
}
});
}
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
cancelDialog();
}
});
mTextInputWidget.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View view, int KeyCode, KeyEvent event) {
if (KeyCode == KeyEvent.KEYCODE_ENTER) {
pushResult(mTextInputWidget.getText().toString());
return true;
}
return false;
}
});
mTextInputDialog = builder.create();
mTextInputDialog.show();
}
public void pushResult(String text) {
Intent resultData = new Intent();
resultData.putExtra("text", text);
setResult(Activity.RESULT_OK, resultData);
mTextInputDialog.dismiss();
finish();
}
public void cancelDialog() {
setResult(Activity.RESULT_CANCELED);
mTextInputDialog.dismiss();
finish();
}
}

View File

@ -1,111 +0,0 @@
package net.minetest.minetest;
import android.app.NativeActivity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
public class MtNativeActivity extends NativeActivity {
static {
System.loadLibrary("c++_shared");
System.loadLibrary("openal");
System.loadLibrary("ogg");
System.loadLibrary("vorbis");
System.loadLibrary("iconv");
System.loadLibrary("minetest");
}
private int m_MessagReturnCode;
private String m_MessageReturnValue;
public static native void putMessageBoxResult(String text);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
m_MessagReturnCode = -1;
m_MessageReturnValue = "";
}
@Override
protected void onResume() {
super.onResume();
makeFullScreen();
}
public void makeFullScreen() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
this.getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
);
}
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
makeFullScreen();
}
}
public void copyAssets() {
Intent intent = new Intent(this, MinetestAssetCopy.class);
startActivity(intent);
}
public void showDialog(String acceptButton, String hint, String current,
int editType) {
Intent intent = new Intent(this, MinetestTextEntry.class);
Bundle params = new Bundle();
params.putString("acceptButton", acceptButton);
params.putString("hint", hint);
params.putString("current", current);
params.putInt("editType", editType);
intent.putExtras(params);
startActivityForResult(intent, 101);
m_MessageReturnValue = "";
m_MessagReturnCode = -1;
}
/* ugly code to workaround putMessageBoxResult not beeing found */
public int getDialogState() {
return m_MessagReturnCode;
}
public String getDialogValue() {
m_MessagReturnCode = -1;
return m_MessageReturnValue;
}
public float getDensity() {
return getResources().getDisplayMetrics().density;
}
public int getDisplayWidth() {
return getResources().getDisplayMetrics().widthPixels;
}
public int getDisplayHeight() {
return getResources().getDisplayMetrics().heightPixels;
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode == 101) {
if (resultCode == RESULT_OK) {
String text = data.getStringExtra("text");
m_MessagReturnCode = 0;
m_MessageReturnValue = text;
} else {
m_MessagReturnCode = 1;
}
}
}
}

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_centerInParent="true"
android:layout_marginLeft="90dp"
android:layout_marginRight="90dp" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/progressBar1"
android:layout_centerInParent="true"
android:text="@string/preparing_media" />
</RelativeLayout>

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="@android:style/android:Theme.Material.Light.NoActionBar.Fullscreen">
<item name="android:windowNoTitle">true</item>
<item name="android:windowAnimationStyle">@null</item>
<item name="android:windowBackground">@drawable/bg</item>
</style>
<style name="Theme.Dialog" parent="@android:style/Theme.Material.Light.Dialog.NoActionBar"/>
</resources>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="preparing_media">Preparing media&#8230;</string>
<string name="not_granted">Required permission wasn\'t granted, Minetest can\'t run without it</string>
</resources>

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="@android:style/android:Theme.Holo.Light.NoActionBar.Fullscreen">
<item name="android:windowNoTitle">true</item>
<item name="android:windowAnimationStyle">@null</item>
<item name="android:windowBackground">@drawable/bg</item>
</style>
<style name="Theme.Dialog" parent="@android:style/android:Theme.Holo.Light.Dialog.NoActionBar"/>
</resources>

View File

@ -16,7 +16,7 @@ core.register_on_sending_chat_message(function(message)
end
local cmd, param = string.match(message, "^%.([^ ]+) *(.*)")
param = param or ""
param = param or ""
if not cmd then
core.display_chat_message(core.gettext("-!- Empty command"))
@ -26,9 +26,9 @@ core.register_on_sending_chat_message(function(message)
local cmd_def = core.registered_chatcommands[cmd]
if cmd_def then
core.set_last_run_mod(cmd_def.mod_origin)
local _, message = cmd_def.func(param)
if message then
core.display_chat_message(message)
local _, result = cmd_def.func(param)
if result then
core.display_chat_message(result)
end
else
core.display_chat_message(core.gettext("-!- Invalid command: ") .. cmd)

View File

@ -1,33 +1,43 @@
local jobs = {}
local time = 0.0
local time_next = math.huge
core.register_globalstep(function(dtime)
time = time + dtime
if #jobs < 1 then
if time < time_next then
return
end
time_next = math.huge
-- Iterate backwards so that we miss any new timers added by
-- a timer callback, and so that we don't skip the next timer
-- in the list if we remove one.
-- a timer callback.
for i = #jobs, 1, -1 do
local job = jobs[i]
if time >= job.expire then
core.set_last_run_mod(job.mod_origin)
job.func(unpack(job.arg))
table.remove(jobs, i)
local jobs_l = #jobs
jobs[i] = jobs[jobs_l]
jobs[jobs_l] = nil
elseif job.expire < time_next then
time_next = job.expire
end
end
end)
function core.after(after, func, ...)
assert(tonumber(after) and type(func) == "function",
"Invalid core.after invocation")
jobs[#jobs + 1] = {
"Invalid minetest.after invocation")
local expire = time + after
local new_job = {
func = func,
expire = time + after,
expire = expire,
arg = {...},
mod_origin = core.get_last_run_mod()
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 }
end

View File

@ -250,7 +250,6 @@ end
--------------------------------------------------------------------------------
function compare_worlds(world1,world2)
if world1.path ~= world2.path then
return false
end

View File

@ -0,0 +1,152 @@
local COLOR_BLUE = "#7AF"
local COLOR_GREEN = "#7F7"
local COLOR_GRAY = "#BBB"
local LIST_FORMSPEC = [[
size[13,6.5]
label[0,-0.1;%s]
tablecolumns[color;tree;text;text]
table[0,0.5;12.8,5.5;list;%s;0]
button_exit[5,6;3,1;quit;%s]
]]
local LIST_FORMSPEC_DESCRIPTION = [[
size[13,7.5]
label[0,-0.1;%s]
tablecolumns[color;tree;text;text]
table[0,0.5;12.8,4.8;list;%s;%i]
box[0,5.5;12.8,1.5;#000]
textarea[0.3,5.5;13.05,1.9;;;%s]
button_exit[5,7;3,1;quit;%s]
]]
local formspec_escape = core.formspec_escape
local check_player_privs = core.check_player_privs
-- CHAT COMMANDS FORMSPEC
local mod_cmds = {}
local function load_mod_command_tree()
mod_cmds = {}
for name, def in pairs(core.registered_chatcommands) do
mod_cmds[def.mod_origin] = mod_cmds[def.mod_origin] or {}
local cmds = mod_cmds[def.mod_origin]
-- Could be simplified, but avoid the priv checks whenever possible
cmds[#cmds + 1] = { name, def }
end
local sorted_mod_cmds = {}
for modname, cmds in pairs(mod_cmds) do
table.sort(cmds, function(a, b) return a[1] < b[1] end)
sorted_mod_cmds[#sorted_mod_cmds + 1] = { modname, cmds }
end
table.sort(sorted_mod_cmds, function(a, b) return a[1] < b[1] end)
mod_cmds = sorted_mod_cmds
end
core.after(0, load_mod_command_tree)
local function build_chatcommands_formspec(name, sel, copy)
local rows = {}
rows[1] = "#FFF,0,Command,Parameters"
local description = "For more information, click on any entry in the list.\n" ..
"Double-click to copy the entry to the chat history."
for i, data in ipairs(mod_cmds) do
rows[#rows + 1] = COLOR_BLUE .. ",0," .. formspec_escape(data[1]) .. ","
for j, cmds in ipairs(data[2]) do
local has_priv = check_player_privs(name, cmds[2].privs)
rows[#rows + 1] = ("%s,1,%s,%s"):format(
has_priv and COLOR_GREEN or COLOR_GRAY,
cmds[1], formspec_escape(cmds[2].params))
if sel == #rows then
description = cmds[2].description
if copy then
core.chat_send_player(name, ("Command: %s %s"):format(
core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params))
end
end
end
end
return LIST_FORMSPEC_DESCRIPTION:format(
"Available commands: (see also: /help <cmd>)",
table.concat(rows, ","), sel or 0,
description, "Close"
)
end
-- PRIVILEGES FORMSPEC
local function build_privs_formspec(name)
local privs = {}
for priv_name, def in pairs(core.registered_privileges) do
privs[#privs + 1] = { priv_name, def }
end
table.sort(privs, function(a, b) return a[1] < b[1] end)
local rows = {}
rows[1] = "#FFF,0,Privilege,Description"
local player_privs = core.get_player_privs(name)
for i, data in ipairs(privs) do
rows[#rows + 1] = ("%s,0,%s,%s"):format(
player_privs[data[1]] and COLOR_GREEN or COLOR_GRAY,
data[1], formspec_escape(data[2].description))
end
return LIST_FORMSPEC:format(
"Available privileges:",
table.concat(rows, ","),
"Close"
)
end
-- DETAILED CHAT COMMAND INFORMATION
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "__builtin:help_cmds" or fields.quit then
return
end
local event = minetest.explode_table_event(fields.list)
if event.type ~= "INV" then
local name = player:get_player_name()
core.show_formspec(name, "__builtin:help_cmds",
build_chatcommands_formspec(name, event.row, event.type == "DCL"))
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)
end

View File

@ -5,7 +5,7 @@
local string_sub, string_find = string.sub, string.find
--------------------------------------------------------------------------------
function basic_dump(o)
local function basic_dump(o)
local tp = type(o)
if tp == "number" then
return tostring(o)
@ -20,6 +20,8 @@ function basic_dump(o)
-- dump's output is intended for humans.
--elseif tp == "function" then
-- return string.format("loadstring(%q)", string.dump(o))
elseif tp == "userdata" then
return tostring(o)
else
return string.format("<%s>", tp)
end
@ -128,6 +130,7 @@ function dump(o, indent, nested, level)
if t ~= "table" then
return basic_dump(o)
end
-- Contains table -> true/nil of currently nested tables
nested = nested or {}
if nested[o] then
@ -136,10 +139,11 @@ function dump(o, indent, nested, level)
nested[o] = true
indent = indent or "\t"
level = level or 1
local t = {}
local ret = {}
local dumped_indexes = {}
for i, v in ipairs(o) do
t[#t + 1] = dump(v, indent, nested, level + 1)
ret[#ret + 1] = dump(v, indent, nested, level + 1)
dumped_indexes[i] = true
end
for k, v in pairs(o) do
@ -148,7 +152,7 @@ function dump(o, indent, nested, level)
k = "["..dump(k, indent, nested, level + 1).."]"
end
v = dump(v, indent, nested, level + 1)
t[#t + 1] = k.." = "..v
ret[#ret + 1] = k.." = "..v
end
end
nested[o] = nil
@ -157,10 +161,10 @@ function dump(o, indent, nested, level)
local end_indent_str = "\n"..string.rep(indent, level - 1)
return string.format("{%s%s%s}",
indent_str,
table.concat(t, ","..indent_str),
table.concat(ret, ","..indent_str),
end_indent_str)
end
return "{"..table.concat(t, ", ").."}"
return "{"..table.concat(ret, ", ").."}"
end
--------------------------------------------------------------------------------
@ -198,28 +202,11 @@ function table.indexof(list, val)
return -1
end
assert(table.indexof({"foo", "bar"}, "foo") == 1)
assert(table.indexof({"foo", "bar"}, "baz") == -1)
--------------------------------------------------------------------------------
if INIT ~= "client" then
function file_exists(filename)
local f = io.open(filename, "r")
if f == nil then
return false
else
f:close()
return true
end
end
end
--------------------------------------------------------------------------------
function string:trim()
return (self:gsub("^%s*(.-)%s*$", "%1"))
end
assert(string.trim("\n \t\tfoo bar\t ") == "foo bar")
--------------------------------------------------------------------------------
function math.hypot(x, y)
local t
@ -257,64 +244,6 @@ function math.factorial(x)
return v
end
--------------------------------------------------------------------------------
function get_last_folder(text,count)
local parts = text:split(DIR_DELIM)
if count == nil then
return parts[#parts]
end
local retval = ""
for i=1,count,1 do
retval = retval .. parts[#parts - (count-i)] .. DIR_DELIM
end
return retval
end
--------------------------------------------------------------------------------
function cleanup_path(temppath)
local parts = temppath:split("-")
temppath = ""
for i=1,#parts,1 do
if temppath ~= "" then
temppath = temppath .. "_"
end
temppath = temppath .. parts[i]
end
parts = temppath:split(".")
temppath = ""
for i=1,#parts,1 do
if temppath ~= "" then
temppath = temppath .. "_"
end
temppath = temppath .. parts[i]
end
parts = temppath:split("'")
temppath = ""
for i=1,#parts,1 do
if temppath ~= "" then
temppath = temppath .. ""
end
temppath = temppath .. parts[i]
end
parts = temppath:split(" ")
temppath = ""
for i=1,#parts,1 do
if temppath ~= "" then
temppath = temppath
end
temppath = temppath .. parts[i]
end
return temppath
end
function core.formspec_escape(text)
if text ~= nil then
text = string.gsub(text,"\\","\\\\")
@ -363,7 +292,8 @@ if INIT == "game" then
return
end
local undef = core.registered_nodes[unode.name]
if undef and undef.on_rightclick then
local sneaking = placer and placer:get_player_control().sneak
if undef and undef.on_rightclick and not sneaking then
return undef.on_rightclick(pointed_thing.under, unode, placer,
itemstack, pointed_thing)
end
@ -407,9 +337,8 @@ if INIT == "game" then
end
local old_itemstack = ItemStack(itemstack)
local new_itemstack, removed = core.item_place_node(
itemstack, placer, pointed_thing, param2, prevent_after_place
)
local new_itemstack = core.item_place_node(itemstack, placer,
pointed_thing, param2, prevent_after_place)
return infinitestacks and old_itemstack or new_itemstack
end
@ -418,19 +347,12 @@ if INIT == "game" then
--Wrapper for rotate_and_place() to check for sneak and assume Creative mode
--implies infinite stacks when performing a 6d rotation.
--------------------------------------------------------------------------------
local creative_mode_cache = core.settings:get_bool("creative_mode")
local function is_creative(name)
return creative_mode_cache or
core.check_player_privs(name, {creative = true})
end
core.rotate_node = function(itemstack, placer, pointed_thing)
local name = placer and placer:get_player_name() or ""
local invert_wall = placer and placer:get_player_control().sneak or false
core.rotate_and_place(itemstack, placer, pointed_thing,
is_creative(name),
{invert_wall = invert_wall}, true)
return itemstack
return core.rotate_and_place(itemstack, placer, pointed_thing,
core.is_creative_enabled(name),
{invert_wall = invert_wall}, true)
end
end
@ -520,9 +442,6 @@ function core.string_to_pos(value)
return nil
end
assert(core.string_to_pos("10.0, 5, -2").x == 10)
assert(core.string_to_pos("( 10.0, 5, -2)").z == -2)
assert(core.string_to_pos("asd, 5, -2)") == nil)
--------------------------------------------------------------------------------
function core.string_to_area(value)
@ -575,6 +494,29 @@ function table.insert_all(t, other)
end
function table.key_value_swap(t)
local ti = {}
for k,v in pairs(t) do
ti[v] = k
end
return ti
end
function table.shuffle(t, from, to, random)
from = from or 1
to = to or #t
random = random or math.random
local n = to - from + 1
while n > 1 do
local r = from + n-1
local l = from + random(0, n-1)
t[l], t[r] = t[r], t[l]
n = n-1
end
end
--------------------------------------------------------------------------------
-- mainmenu only functions
--------------------------------------------------------------------------------
@ -755,6 +697,3 @@ function core.privs_to_string(privs, delim)
end
return table.concat(list, delim)
end
assert(core.string_to_privs("a,b").b == true)
assert(core.privs_to_string({a=true,b=true}) == "a,b")

View File

@ -120,15 +120,8 @@ function core.serialize(x)
elseif tp == "function" then
return string.format("loadstring(%q)", string.dump(x))
elseif tp == "number" then
-- Serialize integers with string.format to prevent
-- scientific notation, which doesn't preserve
-- precision and breaks things like node position
-- hashes. Serialize floats normally.
if math.floor(x) == x then
return string.format("%d", x)
else
return tostring(x)
end
-- Serialize numbers reversibly with string.format
return string.format("%.17g", x)
elseif tp == "table" then
local vals = {}
local idx_dumped = {}
@ -177,13 +170,16 @@ end
-- Deserialization
local env = {
loadstring = loadstring,
}
local function safe_loadstring(...)
local func, err = loadstring(...)
if func then
setfenv(func, {})
return func
end
return nil, err
end
local safe_env = {
loadstring = function() end,
}
local function dummy_func() end
function core.deserialize(str, safe)
if type(str) ~= "string" then
@ -195,7 +191,10 @@ function core.deserialize(str, safe)
end
local f, err = loadstring(str)
if not f then return nil, err end
setfenv(f, safe and safe_env or env)
-- The environment is recreated every time so deseralized code cannot
-- pollute it with permanent references.
setfenv(f, {loadstring = safe and dummy_func or safe_loadstring})
local good, data = pcall(f)
if good then
@ -204,18 +203,3 @@ function core.deserialize(str, safe)
return nil, data
end
end
-- Unit tests
local test_in = {cat={sound="nyan", speed=400}, dog={sound="woof"}}
local test_out = core.deserialize(core.serialize(test_in))
assert(test_in.cat.sound == test_out.cat.sound)
assert(test_in.cat.speed == test_out.cat.speed)
assert(test_in.dog.sound == test_out.dog.sound)
test_in = {escape_chars="\n\r\t\v\\\"\'", non_european="θשׁ٩∂"}
test_out = core.deserialize(core.serialize(test_in))
assert(test_in.escape_chars == test_out.escape_chars)
assert(test_in.non_european == test_out.non_european)

View File

@ -0,0 +1,73 @@
_G.core = {}
dofile("builtin/common/misc_helpers.lua")
describe("string", function()
it("trim()", function()
assert.equal("foo bar", string.trim("\n \t\tfoo bar\t "))
end)
describe("split()", function()
it("removes empty", function()
assert.same({ "hello" }, string.split("hello"))
assert.same({ "hello", "world" }, string.split("hello,world"))
assert.same({ "hello", "world" }, string.split("hello,world,,,"))
assert.same({ "hello", "world" }, string.split(",,,hello,world"))
assert.same({ "hello", "world", "2" }, string.split("hello,,,world,2"))
assert.same({ "hello ", " world" }, string.split("hello :| world", ":|"))
end)
it("keeps empty", function()
assert.same({ "hello" }, string.split("hello", ",", true))
assert.same({ "hello", "world" }, string.split("hello,world", ",", true))
assert.same({ "hello", "world", "" }, string.split("hello,world,", ",", true))
assert.same({ "hello", "", "", "world", "2" }, string.split("hello,,,world,2", ",", true))
assert.same({ "", "", "hello", "world", "2" }, string.split(",,hello,world,2", ",", true))
assert.same({ "hello ", " world | :" }, string.split("hello :| world | :", ":|"))
end)
it("max_splits", function()
assert.same({ "one" }, string.split("one", ",", true, 2))
assert.same({ "one,two,three,four" }, string.split("one,two,three,four", ",", true, 0))
assert.same({ "one", "two", "three,four" }, string.split("one,two,three,four", ",", true, 2))
assert.same({ "one", "", "two,three,four" }, string.split("one,,two,three,four", ",", true, 2))
assert.same({ "one", "two", "three,four" }, string.split("one,,,,,,two,three,four", ",", false, 2))
end)
it("pattern", function()
assert.same({ "one", "two" }, string.split("one,two", ",", false, -1, true))
assert.same({ "one", "two", "three" }, string.split("one2two3three", "%d", false, -1, true))
end)
end)
end)
describe("privs", function()
it("from string", function()
assert.same({ a = true, b = true }, core.string_to_privs("a,b"))
end)
it("to string", function()
assert.equal("one", core.privs_to_string({ one=true }))
local ret = core.privs_to_string({ a=true, b=true })
assert(ret == "a,b" or ret == "b,a")
end)
end)
describe("pos", function()
it("from string", function()
assert.same({ x = 10, y = 5.1, z = -2}, core.string_to_pos("10.0, 5.1, -2"))
assert.same({ x = 10, y = 5.1, z = -2}, core.string_to_pos("( 10.0, 5.1, -2)"))
assert.is_nil(core.string_to_pos("asd, 5, -2)"))
end)
it("to string", function()
assert.equal("(10.1,5.2,-2.3)", core.pos_to_string({ x = 10.1, y = 5.2, z = -2.3}))
end)
end)
describe("table", function()
it("indexof()", function()
assert.equal(1, table.indexof({"foo", "bar"}, "foo"))
assert.equal(-1, table.indexof({"foo", "bar"}, "baz"))
end)
end)

View File

@ -0,0 +1,56 @@
_G.core = {}
_G.setfenv = require 'busted.compatibility'.setfenv
dofile("builtin/common/serialize.lua")
describe("serialize", function()
it("works", function()
local test_in = {cat={sound="nyan", speed=400}, dog={sound="woof"}}
local test_out = core.deserialize(core.serialize(test_in))
assert.same(test_in, test_out)
end)
it("handles characters", function()
local test_in = {escape_chars="\n\r\t\v\\\"\'", non_european="θשׁ٩∂"}
local test_out = core.deserialize(core.serialize(test_in))
assert.same(test_in, test_out)
end)
it("handles precise numbers", function()
local test_in = 0.2695949158945771
local test_out = core.deserialize(core.serialize(test_in))
assert.same(test_in, test_out)
end)
it("handles big integers", function()
local test_in = 269594915894577
local test_out = core.deserialize(core.serialize(test_in))
assert.same(test_in, test_out)
end)
it("handles recursive structures", function()
local test_in = { hello = "world" }
test_in.foo = test_in
local test_out = core.deserialize(core.serialize(test_in))
assert.same(test_in, test_out)
end)
it("strips functions in safe mode", function()
local test_in = {
func = function(a, b)
error("test")
end,
foo = "bar"
}
local str = core.serialize(test_in)
assert.not_nil(str:find("loadstring"))
local test_out = core.deserialize(str, true)
assert.is_nil(test_out.func)
assert.equals(test_out.foo, "bar")
end)
end)

View File

@ -0,0 +1,192 @@
_G.vector = {}
dofile("builtin/common/vector.lua")
describe("vector", function()
describe("new()", function()
it("constructs", function()
assert.same({ x = 0, y = 0, z = 0 }, vector.new())
assert.same({ x = 1, y = 2, z = 3 }, vector.new(1, 2, 3))
assert.same({ x = 3, y = 2, z = 1 }, vector.new({ x = 3, y = 2, z = 1 }))
local input = vector.new({ x = 3, y = 2, z = 1 })
local output = vector.new(input)
assert.same(input, output)
assert.are_not.equal(input, output)
end)
it("throws on invalid input", function()
assert.has.errors(function()
vector.new({ x = 3 })
end)
assert.has.errors(function()
vector.new({ d = 3 })
end)
end)
end)
it("equal()", function()
local function assertE(a, b)
assert.is_true(vector.equals(a, b))
end
local function assertNE(a, b)
assert.is_false(vector.equals(a, b))
end
assertE({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0})
assertE({x = -1, y = 0, z = 1}, {x = -1, y = 0, z = 1})
local a = { x = 2, y = 4, z = -10 }
assertE(a, a)
assertNE({x = -1, y = 0, z = 1}, a)
end)
it("add()", function()
assert.same({ x = 2, y = 4, z = 6 }, vector.add(vector.new(1, 2, 3), { x = 1, y = 2, z = 3 }))
end)
it("offset()", function()
assert.same({ x = 41, y = 52, z = 63 }, vector.offset(vector.new(1, 2, 3), 40, 50, 60))
end)
-- This function is needed because of floating point imprecision.
local function almost_equal(a, b)
if type(a) == "number" then
return math.abs(a - b) < 0.00000000001
end
return vector.distance(a, b) < 0.000000000001
end
describe("rotate_around_axis()", function()
it("rotates", function()
assert.True(almost_equal({x = -1, y = 0, z = 0},
vector.rotate_around_axis({x = 1, y = 0, z = 0}, {x = 0, y = 1, z = 0}, math.pi)))
assert.True(almost_equal({x = 0, y = 1, z = 0},
vector.rotate_around_axis({x = 0, y = 0, z = 1}, {x = 1, y = 0, z = 0}, math.pi / 2)))
assert.True(almost_equal({x = 4, y = 1, z = 1},
vector.rotate_around_axis({x = 4, y = 1, z = 1}, {x = 4, y = 1, z = 1}, math.pi / 6)))
end)
it("keeps distance to axis", function()
local rotate1 = {x = 1, y = 3, z = 1}
local axis1 = {x = 1, y = 3, z = 2}
local rotated1 = vector.rotate_around_axis(rotate1, axis1, math.pi / 13)
assert.True(almost_equal(vector.distance(axis1, rotate1), vector.distance(axis1, rotated1)))
local rotate2 = {x = 1, y = 1, z = 3}
local axis2 = {x = 2, y = 6, z = 100}
local rotated2 = vector.rotate_around_axis(rotate2, axis2, math.pi / 23)
assert.True(almost_equal(vector.distance(axis2, rotate2), vector.distance(axis2, rotated2)))
local rotate3 = {x = 1, y = -1, z = 3}
local axis3 = {x = 2, y = 6, z = 100}
local rotated3 = vector.rotate_around_axis(rotate3, axis3, math.pi / 2)
assert.True(almost_equal(vector.distance(axis3, rotate3), vector.distance(axis3, rotated3)))
end)
it("rotates back", function()
local rotate1 = {x = 1, y = 3, z = 1}
local axis1 = {x = 1, y = 3, z = 2}
local rotated1 = vector.rotate_around_axis(rotate1, axis1, math.pi / 13)
rotated1 = vector.rotate_around_axis(rotated1, axis1, -math.pi / 13)
assert.True(almost_equal(rotate1, rotated1))
local rotate2 = {x = 1, y = 1, z = 3}
local axis2 = {x = 2, y = 6, z = 100}
local rotated2 = vector.rotate_around_axis(rotate2, axis2, math.pi / 23)
rotated2 = vector.rotate_around_axis(rotated2, axis2, -math.pi / 23)
assert.True(almost_equal(rotate2, rotated2))
local rotate3 = {x = 1, y = -1, z = 3}
local axis3 = {x = 2, y = 6, z = 100}
local rotated3 = vector.rotate_around_axis(rotate3, axis3, math.pi / 2)
rotated3 = vector.rotate_around_axis(rotated3, axis3, -math.pi / 2)
assert.True(almost_equal(rotate3, rotated3))
end)
it("is right handed", function()
local v_before1 = {x = 0, y = 1, z = -1}
local v_after1 = vector.rotate_around_axis(v_before1, {x = 1, y = 0, z = 0}, math.pi / 4)
assert.True(almost_equal(vector.normalize(vector.cross(v_after1, v_before1)), {x = 1, y = 0, z = 0}))
local v_before2 = {x = 0, y = 3, z = 4}
local v_after2 = vector.rotate_around_axis(v_before2, {x = 1, y = 0, z = 0}, 2 * math.pi / 5)
assert.True(almost_equal(vector.normalize(vector.cross(v_after2, v_before2)), {x = 1, y = 0, z = 0}))
local v_before3 = {x = 1, y = 0, z = -1}
local v_after3 = vector.rotate_around_axis(v_before3, {x = 0, y = 1, z = 0}, math.pi / 4)
assert.True(almost_equal(vector.normalize(vector.cross(v_after3, v_before3)), {x = 0, y = 1, z = 0}))
local v_before4 = {x = 3, y = 0, z = 4}
local v_after4 = vector.rotate_around_axis(v_before4, {x = 0, y = 1, z = 0}, 2 * math.pi / 5)
assert.True(almost_equal(vector.normalize(vector.cross(v_after4, v_before4)), {x = 0, y = 1, z = 0}))
local v_before5 = {x = 1, y = -1, z = 0}
local v_after5 = vector.rotate_around_axis(v_before5, {x = 0, y = 0, z = 1}, math.pi / 4)
assert.True(almost_equal(vector.normalize(vector.cross(v_after5, v_before5)), {x = 0, y = 0, z = 1}))
local v_before6 = {x = 3, y = 4, z = 0}
local v_after6 = vector.rotate_around_axis(v_before6, {x = 0, y = 0, z = 1}, 2 * math.pi / 5)
assert.True(almost_equal(vector.normalize(vector.cross(v_after6, v_before6)), {x = 0, y = 0, z = 1}))
end)
end)
describe("rotate()", function()
it("rotates", function()
assert.True(almost_equal({x = -1, y = 0, z = 0},
vector.rotate({x = 1, y = 0, z = 0}, {x = 0, y = math.pi, z = 0})))
assert.True(almost_equal({x = 0, y = -1, z = 0},
vector.rotate({x = 1, y = 0, z = 0}, {x = 0, y = 0, z = math.pi / 2})))
assert.True(almost_equal({x = 1, y = 0, z = 0},
vector.rotate({x = 1, y = 0, z = 0}, {x = math.pi / 123, y = 0, z = 0})))
end)
it("is counterclockwise", function()
local v_before1 = {x = 0, y = 1, z = -1}
local v_after1 = vector.rotate(v_before1, {x = math.pi / 4, y = 0, z = 0})
assert.True(almost_equal(vector.normalize(vector.cross(v_after1, v_before1)), {x = 1, y = 0, z = 0}))
local v_before2 = {x = 0, y = 3, z = 4}
local v_after2 = vector.rotate(v_before2, {x = 2 * math.pi / 5, y = 0, z = 0})
assert.True(almost_equal(vector.normalize(vector.cross(v_after2, v_before2)), {x = 1, y = 0, z = 0}))
local v_before3 = {x = 1, y = 0, z = -1}
local v_after3 = vector.rotate(v_before3, {x = 0, y = math.pi / 4, z = 0})
assert.True(almost_equal(vector.normalize(vector.cross(v_after3, v_before3)), {x = 0, y = 1, z = 0}))
local v_before4 = {x = 3, y = 0, z = 4}
local v_after4 = vector.rotate(v_before4, {x = 0, y = 2 * math.pi / 5, z = 0})
assert.True(almost_equal(vector.normalize(vector.cross(v_after4, v_before4)), {x = 0, y = 1, z = 0}))
local v_before5 = {x = 1, y = -1, z = 0}
local v_after5 = vector.rotate(v_before5, {x = 0, y = 0, z = math.pi / 4})
assert.True(almost_equal(vector.normalize(vector.cross(v_after5, v_before5)), {x = 0, y = 0, z = 1}))
local v_before6 = {x = 3, y = 4, z = 0}
local v_after6 = vector.rotate(v_before6, {x = 0, y = 0, z = 2 * math.pi / 5})
assert.True(almost_equal(vector.normalize(vector.cross(v_after6, v_before6)), {x = 0, y = 0, z = 1}))
end)
end)
it("dir_to_rotation()", function()
-- Comparing rotations (pitch, yaw, roll) is hard because of certain ambiguities,
-- e.g. (pi, 0, pi) looks exactly the same as (0, pi, 0)
-- So instead we convert the rotation back to vectors and compare these.
local function forward_at_rot(rot)
return vector.rotate(vector.new(0, 0, 1), rot)
end
local function up_at_rot(rot)
return vector.rotate(vector.new(0, 1, 0), rot)
end
local rot1 = vector.dir_to_rotation({x = 1, y = 0, z = 0}, {x = 0, y = 1, z = 0})
assert.True(almost_equal({x = 1, y = 0, z = 0}, forward_at_rot(rot1)))
assert.True(almost_equal({x = 0, y = 1, z = 0}, up_at_rot(rot1)))
local rot2 = vector.dir_to_rotation({x = 1, y = 1, z = 0}, {x = 0, y = 0, z = 1})
assert.True(almost_equal({x = 1/math.sqrt(2), y = 1/math.sqrt(2), z = 0}, forward_at_rot(rot2)))
assert.True(almost_equal({x = 0, y = 0, z = 1}, up_at_rot(rot2)))
for i = 1, 1000 do
local rand_vec = vector.new(math.random(), math.random(), math.random())
if vector.length(rand_vec) ~= 0 then
local rot_1 = vector.dir_to_rotation(rand_vec)
local rot_2 = {
x = math.atan2(rand_vec.y, math.sqrt(rand_vec.z * rand_vec.z + rand_vec.x * rand_vec.x)),
y = -math.atan2(rand_vec.x, rand_vec.z),
z = 0
}
assert.True(almost_equal(rot_1, rot_2))
end
end
end)
end)

View File

@ -70,6 +70,25 @@ function vector.direction(pos1, pos2)
})
end
function vector.angle(a, b)
local dotp = vector.dot(a, b)
local cp = vector.cross(a, b)
local crossplen = vector.length(cp)
return math.atan2(crossplen, dotp)
end
function vector.dot(a, b)
return a.x * b.x + a.y * b.y + a.z * b.z
end
function vector.cross(a, b)
return {
x = a.y * b.z - a.z * b.y,
y = a.z * b.x - a.x * b.z,
z = a.x * b.y - a.y * b.x
}
end
function vector.add(a, b)
if type(b) == "table" then
return {x = a.x + b.x,
@ -118,7 +137,106 @@ function vector.divide(a, b)
end
end
function vector.offset(v, x, y, z)
return {x = v.x + x,
y = v.y + y,
z = v.z + z}
end
function vector.sort(a, b)
return {x = math.min(a.x, b.x), y = math.min(a.y, b.y), z = math.min(a.z, b.z)},
{x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z)}
end
local function sin(x)
if x % math.pi == 0 then
return 0
else
return math.sin(x)
end
end
local function cos(x)
if x % math.pi == math.pi / 2 then
return 0
else
return math.cos(x)
end
end
function vector.rotate_around_axis(v, axis, angle)
local cosangle = cos(angle)
local sinangle = sin(angle)
axis = vector.normalize(axis)
-- https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula
local dot_axis = vector.multiply(axis, vector.dot(axis, v))
local cross = vector.cross(v, axis)
return vector.new(
cross.x * sinangle + (v.x - dot_axis.x) * cosangle + dot_axis.x,
cross.y * sinangle + (v.y - dot_axis.y) * cosangle + dot_axis.y,
cross.z * sinangle + (v.z - dot_axis.z) * cosangle + dot_axis.z
)
end
function vector.rotate(v, rot)
local sinpitch = sin(-rot.x)
local sinyaw = sin(-rot.y)
local sinroll = sin(-rot.z)
local cospitch = cos(rot.x)
local cosyaw = cos(rot.y)
local cosroll = math.cos(rot.z)
-- Rotation matrix that applies yaw, pitch and roll
local matrix = {
{
sinyaw * sinpitch * sinroll + cosyaw * cosroll,
sinyaw * sinpitch * cosroll - cosyaw * sinroll,
sinyaw * cospitch,
},
{
cospitch * sinroll,
cospitch * cosroll,
-sinpitch,
},
{
cosyaw * sinpitch * sinroll - sinyaw * cosroll,
cosyaw * sinpitch * cosroll + sinyaw * sinroll,
cosyaw * cospitch,
},
}
-- Compute matrix multiplication: `matrix` * `v`
return vector.new(
matrix[1][1] * v.x + matrix[1][2] * v.y + matrix[1][3] * v.z,
matrix[2][1] * v.x + matrix[2][2] * v.y + matrix[2][3] * v.z,
matrix[3][1] * v.x + matrix[3][2] * v.y + matrix[3][3] * v.z
)
end
function vector.dir_to_rotation(forward, up)
forward = vector.normalize(forward)
local rot = {x = math.asin(forward.y), y = -math.atan2(forward.x, forward.z), z = 0}
if not up then
return rot
end
assert(vector.dot(forward, up) < 0.000001,
"Invalid vectors passed to vector.dir_to_rotation().")
up = vector.normalize(up)
-- Calculate vector pointing up with roll = 0, just based on forward vector.
local forwup = vector.rotate({x = 0, y = 1, z = 0}, rot)
-- 'forwup' and 'up' are now in a plane with 'forward' as normal.
-- The angle between them is the absolute of the roll value we're looking for.
rot.z = vector.angle(forwup, up)
-- Since vector.angle never returns a negative value or a value greater
-- than math.pi, rot.z has to be inverted sometimes.
-- To determine wether this is the case, we rotate the up vector back around
-- the forward vector and check if it worked out.
local back = vector.rotate_around_axis(up, forward, -rot.z)
-- We don't use vector.equals for this because of floating point imprecision.
if (back.x - forwup.x) * (back.x - forwup.x) +
(back.y - forwup.y) * (back.y - forwup.y) +
(back.z - forwup.z) * (back.z - forwup.z) > 0.0000001 then
rot.z = -rot.z
end
return rot
end

View File

@ -67,3 +67,22 @@ function dialog_create(name,get_formspec,buttonhandler,eventhandler)
ui.add(self)
return self
end
function messagebox(name, message)
return dialog_create(name,
function()
return ([[
formspec_version[3]
size[8,3]
textarea[0.375,0.375;7.25,1.2;;;%s]
button[3,1.825;2,0.8;ok;%s]
]]):format(message, fgettext("OK"))
end,
function(this, fields)
if fields.ok then
this:delete()
return true
end
end,
nil)
end

View File

@ -54,52 +54,41 @@ end
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local function wordwrap_quickhack(str)
local res = ""
local ar = str:split("\n")
for i = 1, #ar do
local text = ar[i]
-- Hack to add word wrapping.
-- TODO: Add engine support for wrapping in formspecs
while #text > 80 do
if res ~= "" then
res = res .. ","
end
res = res .. core.formspec_escape(string.sub(text, 1, 79))
text = string.sub(text, 80, #text)
end
if res ~= "" then
res = res .. ","
end
res = res .. core.formspec_escape(text)
end
return res
end
--------------------------------------------------------------------------------
function ui.update()
local formspec = ""
local formspec = {}
-- handle errors
if gamedata ~= nil and gamedata.reconnect_requested then
formspec = wordwrap_quickhack(gamedata.errormessage or "")
formspec = "size[12,5]" ..
"label[0.5,0;" .. fgettext("The server has requested a reconnect:") ..
"]textlist[0.2,0.8;11.5,3.5;;" .. formspec ..
"]button[6,4.6;3,0.5;btn_reconnect_no;" .. fgettext("Main menu") .. "]" ..
"button[3,4.6;3,0.5;btn_reconnect_yes;" .. fgettext("Reconnect") .. "]"
local error_message = core.formspec_escape(
gamedata.errormessage or "<none available>")
formspec = {
"size[14,8]",
"real_coordinates[true]",
"set_focus[btn_reconnect_yes;true]",
"box[0.5,1.2;13,5;#000]",
("textarea[0.5,1.2;13,5;;%s;%s]"):format(
fgettext("The server has requested a reconnect:"), error_message),
"button[2,6.6;4,1;btn_reconnect_yes;" .. fgettext("Reconnect") .. "]",
"button[8,6.6;4,1;btn_reconnect_no;" .. fgettext("Main menu") .. "]"
}
elseif gamedata ~= nil and gamedata.errormessage ~= nil then
formspec = wordwrap_quickhack(gamedata.errormessage)
local error_message = core.formspec_escape(gamedata.errormessage)
local error_title
if string.find(gamedata.errormessage, "ModError") then
error_title = fgettext("An error occurred in a Lua script, such as a mod:")
error_title = fgettext("An error occurred in a Lua script:")
else
error_title = fgettext("An error occurred:")
end
formspec = "size[12,5]" ..
"label[0.5,0;" .. error_title ..
"]textlist[0.2,0.8;11.5,3.5;;" .. formspec ..
"]button[4.5,4.6;3,0.5;btn_error_confirm;" .. fgettext("Ok") .. "]"
formspec = {
"size[14,8]",
"real_coordinates[true]",
"set_focus[btn_error_confirm;true]",
"box[0.5,1.2;13,5;#000]",
("textarea[0.5,1.2;13,5;;%s;%s]"):format(
error_title, error_message),
"button[5,6.6;4,1;btn_error_confirm;" .. fgettext("OK") .. "]"
}
else
local active_toplevel_ui_elements = 0
for key,value in pairs(ui.childlist) do
@ -107,8 +96,8 @@ function ui.update()
local retval = value:get_formspec()
if retval ~= nil and retval ~= "" then
active_toplevel_ui_elements = active_toplevel_ui_elements +1
formspec = formspec .. retval
active_toplevel_ui_elements = active_toplevel_ui_elements + 1
table.insert(formspec, retval)
end
end
end
@ -120,7 +109,7 @@ function ui.update()
local retval = value:get_formspec()
if retval ~= nil and retval ~= "" then
formspec = formspec .. retval
table.insert(formspec, retval)
end
end
end
@ -135,10 +124,10 @@ function ui.update()
core.log("warning", "no toplevel ui element "..
"active; switching to default")
ui.childlist[ui.default]:show()
formspec = ui.childlist[ui.default]:get_formspec()
formspec = {ui.childlist[ui.default]:get_formspec()}
end
end
core.update_formspec(formspec)
core.update_formspec(table.concat(formspec))
end
--------------------------------------------------------------------------------

View File

@ -41,7 +41,6 @@ core.builtin_auth_handler = {
return {
password = auth_entry.password,
privileges = privileges,
-- Is set to nil if unknown
last_login = auth_entry.last_login,
}
end,
@ -53,7 +52,7 @@ core.builtin_auth_handler = {
name = name,
password = password,
privileges = core.string_to_privs(core.settings:get("default_privs")),
last_login = os.time(),
last_login = -1, -- Defer login time calculation until record_login (called by on_joinplayer)
})
end,
delete_auth = function(name)

View File

@ -1,4 +1,43 @@
-- Minetest: builtin/game/chatcommands.lua
-- Minetest: builtin/game/chat.lua
-- Helper function that implements search and replace without pattern matching
-- Returns the string and a boolean indicating whether or not the string was modified
local function safe_gsub(s, replace, with)
local i1, i2 = s:find(replace, 1, true)
if not i1 then
return s, false
end
return s:sub(1, i1 - 1) .. with .. s:sub(i2 + 1), true
end
--
-- Chat message formatter
--
-- Implemented in Lua to allow redefinition
function core.format_chat_message(name, message)
local error_str = "Invalid chat message format - missing %s"
local str = core.settings:get("chat_message_format")
local replaced
-- Name
str, replaced = safe_gsub(str, "@name", name)
if not replaced then
error(error_str:format("@name"), 2)
end
-- Timestamp
str = safe_gsub(str, "@timestamp", os.date("%H:%M:%S", os.time()))
-- Insert the message into the string only after finishing all other processing
str, replaced = safe_gsub(str, "@message", message)
if not replaced then
error(error_str:format("@message"), 2)
end
return str
end
--
-- Chat command handler
@ -27,9 +66,9 @@ core.register_on_chat_message(function(name, message)
local has_privs, missing_privs = core.check_player_privs(name, cmd_def.privs)
if has_privs then
core.set_last_run_mod(cmd_def.mod_origin)
local success, message = cmd_def.func(name, param)
if message then
core.chat_send_player(name, core.colorize("#66BEF5", message))
local _, result = cmd_def.func(name, param)
if result then
core.chat_send_player(name, core.colorize("#66BEF5", result))
end
else
core.chat_send_player(name, core.colorize("#F35400", "# Illuna: You don't have permission"
@ -76,6 +115,7 @@ core.register_chatcommand("me", {
privs = {shout=true},
func = function(name, param)
core.chat_send_all(core.colorize("#2BCBEE", "* " .. name .. " " .. param))
return true
end,
})
@ -125,10 +165,10 @@ core.register_chatcommand("haspriv", {
if core.check_player_privs(player_name, privs) then
table.insert(players_with_priv, player_name)
end
end
end
return true, "Players online with the \"" .. param .. "\" privilege: " ..
table.concat(players_with_priv, ", ")
end
end
})
local function handle_grant_command(caller, grantname, grantprivstr)
@ -160,6 +200,7 @@ local function handle_grant_command(caller, grantname, grantprivstr)
return false, privs_unknown
end
for priv, _ in pairs(grantprivs) do
-- call the on_grant callbacks
core.run_priv_callbacks(grantname, priv, caller, "grant")
end
core.set_player_privs(grantname, privs)
@ -197,56 +238,76 @@ core.register_chatcommand("grantme", {
end,
})
local function handle_revoke_command(caller, revokename, revokeprivstr)
local caller_privs = core.get_player_privs(caller)
if not (caller_privs.privs or caller_privs.basic_privs) then
return false, "Your privileges are insufficient."
end
if not core.get_auth_handler().get_auth(revokename) then
return false, "Player " .. revokename .. " does not exist."
end
local revokeprivs = core.string_to_privs(revokeprivstr)
local privs = core.get_player_privs(revokename)
local basic_privs =
core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
for priv, _ in pairs(revokeprivs) do
if not basic_privs[priv] and not caller_privs.privs then
return false, "Your privileges are insufficient."
end
end
if revokeprivstr == "all" then
revokeprivs = privs
privs = {}
else
for priv, _ in pairs(revokeprivs) do
privs[priv] = nil
end
end
for priv, _ in pairs(revokeprivs) do
-- call the on_revoke callbacks
core.run_priv_callbacks(revokename, priv, caller, "revoke")
end
core.set_player_privs(revokename, privs)
core.log("action", caller..' revoked ('
..core.privs_to_string(revokeprivs, ', ')
..') privileges from '..revokename)
if revokename ~= caller then
core.chat_send_player(revokename, caller
.. " revoked privileges from you: "
.. core.privs_to_string(revokeprivs, ' '))
end
return true, "Privileges of " .. revokename .. ": "
.. core.privs_to_string(
core.get_player_privs(revokename), ' ')
end
core.register_chatcommand("revoke", {
params = "<name> (<privilege> | all)",
description = "Remove privileges from player",
privs = {},
func = function(name, param)
if not core.check_player_privs(name, {privs=true}) and
not core.check_player_privs(name, {basic_privs=true}) then
return false, core.colorize("#F35400", "# Illuna: I am sorry, but your privileges are insufficient... .")
end
local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)")
if not revoke_name or not revoke_priv_str then
return false, core.colorize("#F35400", "# Illuna: Sorry, invalid parameters (try /help revoke)")
elseif not core.get_auth_handler().get_auth(revoke_name) then
return false, core.colorize("#F35400", "# Illuna: Whoops, there is no player with name " .. revoke_name ..". I can't help you here.")
end
local revoke_privs = core.string_to_privs(revoke_priv_str)
local privs = core.get_player_privs(revoke_name)
local basic_privs =
core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
for priv, _ in pairs(revoke_privs) do
if not basic_privs[priv] and
not core.check_player_privs(name, {privs=true}) then
return false, core.colorize("#F35400", "# Illuna: I am sorry, but your privileges are insufficient... .")
end
end
if revoke_priv_str == "all" then
revoke_privs = privs
privs = {}
else
for priv, _ in pairs(revoke_privs) do
privs[priv] = nil
end
local revokename, revokeprivstr = string.match(param, "([^ ]+) (.+)")
if not revokename or not revokeprivstr then
return false, "Invalid parameters (see /help revoke)"
end
return handle_revoke_command(name, revokename, revokeprivstr)
end,
})
for priv, _ in pairs(revoke_privs) do
core.run_priv_callbacks(revoke_name, priv, name, "revoke")
core.register_chatcommand("revokeme", {
params = "<privilege> | all",
description = "Revoke privileges from yourself",
privs = {},
func = function(name, param)
if param == "" then
return false, "Invalid parameters (see /help revokeme)"
end
core.set_player_privs(revoke_name, privs)
core.log("action", name..' revoked ('
..core.privs_to_string(revoke_privs, ', ')
..') privileges from '..revoke_name)
if revoke_name ~= name then
core.chat_send_player(revoke_name, name
.. core.colorize("#F35400", " revoked privileges from you: "
.. core.privs_to_string(revoke_privs, ' ')))
end
return true, core.colorize("#F6A10A", "Privileges of " .. revoke_name .. ": "
.. core.privs_to_string(
core.get_player_privs(revoke_name), ' '))
return handle_revoke_command(name, name, param)
end,
})
@ -260,11 +321,12 @@ core.register_chatcommand("setpassword", {
toname = param:match("^([^ ]+) *$")
raw_password = nil
end
if not toname then
return false, core.colorize("#F35400", "Illuna: Name field required!")
end
local act_str_past = "?"
local act_str_pres = "?"
local act_str_past, act_str_pres
if not raw_password then
core.set_player_password(toname, "")
act_str_past = "cleared"
@ -276,13 +338,14 @@ core.register_chatcommand("setpassword", {
act_str_past = "set"
act_str_pres = "sets"
end
if toname ~= name then
core.chat_send_player(toname, core.colorize("#37CA39", "Your password was "
.. act_str_past .. " by " .. name))
end
core.log("action", name .. " " .. act_str_pres
.. " password of " .. toname .. ".")
core.log("action", name .. " " .. act_str_pres ..
" password of " .. toname .. ".")
return true, core.colorize("#F6A10A", "Password of player \"" .. toname .. "\" " .. act_str_past)
end,
@ -366,36 +429,42 @@ core.register_chatcommand("teleport", {
return pos, false
end
local teleportee = nil
local p = {}
p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
p.x = tonumber(p.x)
p.y = tonumber(p.y)
p.z = tonumber(p.z)
if p.x and p.y and p.z then
local lm = 31000
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, core.colorize("#F35400", "# Illuna: Aww... cannot teleport out of map bounds!")
end
teleportee = core.get_player_by_name(name)
end
local teleportee = core.get_player_by_name(name)
if teleportee then
if teleportee:get_attach() then
return false, "Can't teleport, you're attached to an object!"
end
teleportee:set_pos(p)
return true, core.colorize("#37CA39", "# Illuna: Aye, Aye! Teleporting to "..core.pos_to_string(p))
end
end
local teleportee = nil
local p = nil
local target_name = nil
target_name = param:match("^([^ ]+)$")
teleportee = core.get_player_by_name(name)
local target_name = param:match("^([^ ]+)$")
local teleportee = core.get_player_by_name(name)
p = nil
if target_name then
local target = core.get_player_by_name(target_name)
if target then
p = target:get_pos()
end
end
if teleportee and p then
if teleportee:get_attach() then
return false, "Can't teleport, you're attached to an object!"
end
p = find_free_position_near(p)
teleportee:set_pos(p)
return true, core.colorize("#37CA39", "# Illuna: You got it, teleporting to " .. target_name
@ -406,9 +475,9 @@ core.register_chatcommand("teleport", {
return false, core.colorize("#F35400", "# Illuna: Aww... you don't have permission to teleport other players (missing bring privilege)")
end
local teleportee = nil
local p = {}
local teleportee_name = nil
teleportee = nil
p = {}
local teleportee_name
teleportee_name, p.x, p.y, p.z = param:match(
"^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
@ -416,15 +485,16 @@ core.register_chatcommand("teleport", {
teleportee = core.get_player_by_name(teleportee_name)
end
if teleportee and p.x and p.y and p.z then
if teleportee:get_attach() then
return false, "Can't teleport, player is attached to an object!"
end
teleportee:set_pos(p)
return true, core.colorize("#37CA39", "# Illuna: Okey, teleporting " .. teleportee_name
.. " to " .. core.pos_to_string(p))
end
local teleportee = nil
local p = nil
local teleportee_name = nil
local target_name = nil
teleportee = nil
p = nil
teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
if teleportee_name then
teleportee = core.get_player_by_name(teleportee_name)
@ -436,6 +506,9 @@ core.register_chatcommand("teleport", {
end
end
if teleportee and p then
if teleportee:get_attach() then
return false, "Can't teleport, player is attached to an object!"
end
p = find_free_position_near(p)
teleportee:set_pos(p)
return true, core.colorize("#37CA39", "Teleporting " .. teleportee_name
@ -458,7 +531,8 @@ core.register_chatcommand("set", {
core.settings:set(setname, setvalue)
return true, setname .. " = " .. setvalue
end
local setname, setvalue = string.match(param, "([^ ]+) (.+)")
setname, setvalue = string.match(param, "([^ ]+) (.+)")
if setname and setvalue then
if not core.settings:get(setname) then
return false, core.colorize("#F35400", "# Illuna: Failed. Use '/set -n <name> <value>' to create a new setting.")
@ -466,9 +540,10 @@ core.register_chatcommand("set", {
core.settings:set(setname, setvalue)
return true, setname .. " = " .. setvalue
end
local setname = string.match(param, "([^ ]+)")
setname = string.match(param, "([^ ]+)")
if setname then
local setvalue = core.settings:get(setname)
setvalue = core.settings:get(setname)
if not setvalue then
setvalue = "<not set>"
end
@ -671,8 +746,9 @@ core.register_chatcommand("spawnentity", {
end
end
p.y = p.y + 1
core.add_entity(p, entityname)
return true, (core.colorize("#37CA39", "# Illuna: %q spawned.")):format(entityname)
local obj = core.add_entity(p, entityname)
local msg = obj and "%q spawned." or "%q failed to spawn."
return true, msg:format(entityname)
end,
})
@ -691,8 +767,8 @@ core.register_chatcommand("pulverize", {
end
core.log("action", name .. " pulverized \"" ..
wielded_item:get_name() .. " " .. wielded_item:get_count() .. "\"")
player:set_wielded_item(nil)
return true, core.colorize("#37CA39", "# Illuna: An item was pulverized.")
player:set_wielded_item(nil)
return true, "An item was pulverized."
end,
})
@ -711,7 +787,7 @@ core.register_chatcommand("rollback_check", {
params = "[<range>] [<seconds>] [<limit>]",
description = "Check who last touched a node or a node near it"
.. " within the time specified by <seconds>. Default: range = 0,"
.. " seconds = 86400 = 24h, limit = 5",
.. " seconds = 86400 = 24h, limit = 5. Set <seconds> to inf for no time limit",
privs = {rollback=true},
func = function(name, param)
if not core.settings:get_bool("enable_rollback_recording") then
@ -762,7 +838,7 @@ core.register_chatcommand("rollback_check", {
core.register_chatcommand("rollback", {
params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
description = "Revert actions of a player. Default for <seconds> is 60",
description = "Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit",
privs = {rollback=true},
func = function(name, param)
if not core.settings:get_bool("enable_rollback_recording") then
@ -770,7 +846,7 @@ core.register_chatcommand("rollback", {
end
local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
if not target_name then
local player_name = nil
local player_name
player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
if not player_name then
return false, core.colorize("#F35400", "# Illuna: Invalid parameters. See /help rollback"
@ -874,12 +950,13 @@ core.register_chatcommand("shutdown", {
core.chat_send_all("*** Server shutting down (operator request).")
end
core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
return true
end,
})
core.register_chatcommand("ban", {
params = "[<name> | <IP_address>]",
description = "Ban player or show ban list",
params = "[<name>]",
description = "Ban the IP of a player or show the ban list",
privs = {ban=true},
func = function(name, param)
if param == "" then
@ -904,7 +981,7 @@ core.register_chatcommand("ban", {
core.register_chatcommand("unban", {
params = "<name> | <IP_address>",
description = "Remove player ban",
description = "Remove IP ban belonging to a player/IP",
privs = {ban=true},
func = function(name, param)
if not core.unban_player_or_ip(param) then
@ -950,18 +1027,19 @@ core.register_chatcommand("clearobjects", {
core.log("action", name .. " clears all objects ("
.. options.mode .. " mode).")
core.chat_send_all("# Illuna: Clearing all objects. This may take long."
.. " You may experience a timeout. (by "
core.chat_send_all("Clearing all objects. This may take a long time."
.. " You may experience a timeout. (by "
.. name .. ")")
core.clear_objects(options)
core.log("action", "Object clearing done.")
core.chat_send_all(core.colorize("#F6A10A", "*** Cleared all objects."))
core.chat_send_all("*** Cleared all objects.")
return true
end,
})
core.register_chatcommand("msg", {
params = "<name> <message>",
description = "Send a private message",
description = "Send a direct message to a player",
privs = {shout=true},
func = function(name, param)
local sendto, message = param:match("^(%S+)%s(.+)$")
@ -972,9 +1050,9 @@ core.register_chatcommand("msg", {
return false, core.colorize("#F35400", "# Illuna: The player " .. sendto
.. " is not online.")
end
core.log("action", "PM from " .. name .. " to " .. sendto
core.log("action", "DM from " .. name .. " to " .. sendto
.. ": " .. message)
core.chat_send_player(sendto, core.colorize("#1EBC9A", "PM from " .. name .. ": "
core.chat_send_player(sendto, core.colorize("#1EBC9A", "DM from " .. name .. ": "
.. message))
return true, core.colorize("#37CA39", "Message sent.")
end,
@ -988,12 +1066,12 @@ core.register_chatcommand("last-login", {
param = name
end
local pauth = core.get_auth_handler().get_auth(param)
if pauth and pauth.last_login then
if pauth and pauth.last_login and pauth.last_login ~= -1 then
-- Time in UTC, ISO 8601 format
return true, core.colorize("#F6A10A", "# Illuna: Last login time was " ..
os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login))
return true, param.."'s last login time was " ..
os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
end
return false, core.colorize("#F35400", "# Illuna: Last login time is unknown")
return false, param.."'s last login time is unknown"
end,
})

View File

@ -24,7 +24,7 @@ core.MAP_BLOCKSIZE = 16
-- Default maximal HP of a player
core.PLAYER_MAX_HP_DEFAULT = 20
-- Default maximal breath of a player
core.PLAYER_MAX_BREATH_DEFAULT = 11
core.PLAYER_MAX_BREATH_DEFAULT = 10
-- light.h
-- Maximum value for node 'light_source' parameter

View File

@ -20,7 +20,7 @@ function core.node_metadata_inventory_move_allow_all()
end
function core.add_to_creative_inventory(itemstring)
core.log("deprecated", "core.add_to_creative_inventory: This function is deprecated and does nothing.")
core.log("deprecated", "core.add_to_creative_inventory is obsolete and does nothing.")
end
--
@ -70,3 +70,19 @@ core.setting_get = setting_proxy("get")
core.setting_setbool = setting_proxy("set_bool")
core.setting_getbool = setting_proxy("get_bool")
core.setting_save = setting_proxy("write")
--
-- core.register_on_auth_fail
--
function core.register_on_auth_fail(func)
core.log("deprecated", "core.register_on_auth_fail " ..
"is obsolete and should be replaced by " ..
"core.register_on_authplayer instead.")
core.register_on_authplayer(function (player_name, ip, is_success)
if not is_success then
func(player_name, ip)
end
end)
end

View File

@ -1,6 +1,36 @@
-- Minetest: builtin/item.lua
local builtin_shared = ...
local SCALE = 0.667
local facedir_to_euler = {
{y = 0, x = 0, z = 0},
{y = -math.pi/2, x = 0, z = 0},
{y = math.pi, x = 0, z = 0},
{y = math.pi/2, x = 0, z = 0},
{y = math.pi/2, x = -math.pi/2, z = math.pi/2},
{y = math.pi/2, x = math.pi, z = math.pi/2},
{y = math.pi/2, x = math.pi/2, z = math.pi/2},
{y = math.pi/2, x = 0, z = math.pi/2},
{y = -math.pi/2, x = math.pi/2, z = math.pi/2},
{y = -math.pi/2, x = 0, z = math.pi/2},
{y = -math.pi/2, x = -math.pi/2, z = math.pi/2},
{y = -math.pi/2, x = math.pi, z = math.pi/2},
{y = 0, x = 0, z = math.pi/2},
{y = 0, x = -math.pi/2, z = math.pi/2},
{y = 0, x = math.pi, z = math.pi/2},
{y = 0, x = math.pi/2, z = math.pi/2},
{y = math.pi, x = math.pi, z = math.pi/2},
{y = math.pi, x = math.pi/2, z = math.pi/2},
{y = math.pi, x = 0, z = math.pi/2},
{y = math.pi, x = -math.pi/2, z = math.pi/2},
{y = math.pi, x = math.pi, z = 0},
{y = -math.pi/2, x = math.pi, z = 0},
{y = 0, x = math.pi, z = 0},
{y = math.pi/2, x = math.pi, z = 0}
}
local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
--
-- Falling stuff
@ -8,25 +38,157 @@ local builtin_shared = ...
core.register_entity(":__builtin:falling_node", {
initial_properties = {
visual = "wielditem",
visual_size = {x = 0.667, y = 0.667},
visual = "item",
visual_size = {x = SCALE, y = SCALE, z = SCALE},
textures = {},
physical = true,
is_visible = false,
collide_with_objects = false,
collide_with_objects = true,
collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
},
node = {},
meta = {},
floats = false,
set_node = function(self, node, meta)
node.param2 = node.param2 or 0
self.node = node
self.meta = meta or {}
self.object:set_properties({
is_visible = true,
textures = {node.name},
})
meta = meta or {}
if type(meta.to_table) == "function" then
meta = meta:to_table()
end
for _, list in pairs(meta.inventory or {}) do
for i, stack in pairs(list) do
if type(stack) == "userdata" then
list[i] = stack:to_string()
end
end
end
local def = core.registered_nodes[node.name]
if not def then
-- Don't allow unknown nodes to fall
core.log("info",
"Unknown falling node removed at "..
core.pos_to_string(self.object:get_pos()))
self.object:remove()
return
end
self.meta = meta
-- Cache whether we're supposed to float on water
self.floats = core.get_item_group(node.name, "float") ~= 0
-- Set entity visuals
if def.drawtype == "torchlike" or def.drawtype == "signlike" then
local textures
if def.tiles and def.tiles[1] then
local tile = def.tiles[1]
if type(tile) == "table" then
tile = tile.name
end
if def.drawtype == "torchlike" then
textures = { "("..tile..")^[transformFX", tile }
else
textures = { tile, "("..tile..")^[transformFX" }
end
end
local vsize
if def.visual_scale then
local s = def.visual_scale
vsize = {x = s, y = s, z = s}
end
self.object:set_properties({
is_visible = true,
visual = "upright_sprite",
visual_size = vsize,
textures = textures,
glow = def.light_source,
})
elseif def.drawtype ~= "airlike" then
local itemstring = node.name
if core.is_colored_paramtype(def.paramtype2) then
itemstring = core.itemstring_with_palette(itemstring, node.param2)
end
-- FIXME: solution needed for paramtype2 == "leveled"
local vsize
if def.visual_scale then
local s = def.visual_scale * SCALE
vsize = {x = s, y = s, z = s}
end
self.object:set_properties({
is_visible = true,
wield_item = itemstring,
visual_size = vsize,
glow = def.light_source,
})
end
-- Set collision box (certain nodeboxes only for now)
local nb_types = {fixed=true, leveled=true, connected=true}
if def.drawtype == "nodebox" and def.node_box and
nb_types[def.node_box.type] then
local box = table.copy(def.node_box.fixed)
if type(box[1]) == "table" then
box = #box == 1 and box[1] or nil -- We can only use a single box
end
if box then
if def.paramtype2 == "leveled" and (self.node.level or 0) > 0 then
box[5] = -0.5 + self.node.level / 64
end
self.object:set_properties({
collisionbox = box
})
end
end
-- Rotate entity
if def.drawtype == "torchlike" then
self.object:set_yaw(math.pi*0.25)
elseif (node.param2 ~= 0 and (def.wield_image == ""
or def.wield_image == nil))
or def.drawtype == "signlike"
or def.drawtype == "mesh"
or def.drawtype == "normal"
or def.drawtype == "nodebox" then
if (def.paramtype2 == "facedir" or def.paramtype2 == "colorfacedir") then
local fdir = node.param2 % 32
-- Get rotation from a precalculated lookup table
local euler = facedir_to_euler[fdir + 1]
if euler then
self.object:set_rotation(euler)
end
elseif (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted") then
local rot = node.param2 % 8
local pitch, yaw, roll = 0, 0, 0
if rot == 1 then
pitch, yaw = math.pi, math.pi
elseif rot == 2 then
pitch, yaw = math.pi/2, math.pi/2
elseif rot == 3 then
pitch, yaw = math.pi/2, -math.pi/2
elseif rot == 4 then
pitch, yaw = math.pi/2, math.pi
elseif rot == 5 then
pitch, yaw = math.pi/2, 0
end
if def.drawtype == "signlike" then
pitch = pitch - math.pi/2
if rot == 0 then
yaw = yaw + math.pi/2
elseif rot == 1 then
yaw = yaw - math.pi/2
end
elseif def.drawtype == "mesh" or def.drawtype == "normal" then
if rot >= 0 and rot <= 1 then
roll = roll + math.pi
else
yaw = yaw + math.pi
end
end
self.object:set_rotation({x=pitch, y=yaw, z=roll})
end
end
end,
get_staticdata = function(self)
@ -39,6 +201,7 @@ core.register_entity(":__builtin:falling_node", {
on_activate = function(self, staticdata)
self.object:set_armor_groups({immortal = 1})
self.object:set_acceleration({x = 0, y = -gravity, z = 0})
local ds = core.deserialize(staticdata)
if ds and ds.node then
@ -50,85 +213,159 @@ core.register_entity(":__builtin:falling_node", {
end
end,
on_step = function(self, dtime)
-- Set gravity
local acceleration = self.object:get_acceleration()
if not vector.equals(acceleration, {x = 0, y = -10, z = 0}) then
self.object:set_acceleration({x = 0, y = -10, z = 0})
try_place = function(self, bcp, bcn)
local bcd = core.registered_nodes[bcn.name]
-- Add levels if dropped on same leveled node
if bcd and bcd.paramtype2 == "leveled" and
bcn.name == self.node.name then
local addlevel = self.node.level
if (addlevel or 0) <= 0 then
addlevel = bcd.leveled
end
if core.add_node_level(bcp, addlevel) < addlevel then
return true
elseif bcd.buildable_to then
-- Node level has already reached max, don't place anything
return true
end
end
-- Turn to actual node when colliding with ground, or continue to move
local pos = self.object:get_pos()
-- Position of bottom center point
local bcp = {x = pos.x, y = pos.y - 0.7, z = pos.z}
-- 'bcn' is nil for unloaded nodes
local bcn = core.get_node_or_nil(bcp)
-- Delete on contact with ignore at world edges
if bcn and bcn.name == "ignore" then
self.object:remove()
return
-- Decide if we're replacing the node or placing on top
local np = vector.new(bcp)
if bcd and bcd.buildable_to and
(not self.floats or bcd.liquidtype == "none") then
core.remove_node(bcp)
else
np.y = np.y + 1
end
local bcd = bcn and core.registered_nodes[bcn.name]
if bcn and
(not bcd or bcd.walkable or
(core.get_item_group(self.node.name, "float") ~= 0 and
bcd.liquidtype ~= "none")) then
if bcd and bcd.leveled and
bcn.name == self.node.name then
local addlevel = self.node.level
if not addlevel or addlevel <= 0 then
addlevel = bcd.leveled
-- Check what's here
local n2 = core.get_node(np)
local nd = core.registered_nodes[n2.name]
-- If it's not air or liquid, remove node and replace it with
-- it's drops
if n2.name ~= "air" and (not nd or nd.liquidtype == "none") then
if nd and nd.buildable_to == false then
nd.on_dig(np, n2, nil)
-- If it's still there, it might be protected
if core.get_node(np).name == n2.name then
return false
end
if core.add_node_level(bcp, addlevel) == 0 then
else
core.remove_node(np)
end
end
-- Create node
local def = core.registered_nodes[self.node.name]
if def then
core.add_node(np, self.node)
if self.meta then
core.get_meta(np):from_table(self.meta)
end
if def.sounds and def.sounds.place then
core.sound_play(def.sounds.place, {pos = np}, true)
end
end
core.check_for_falling(np)
return true
end,
on_step = function(self, dtime, moveresult)
-- Fallback code since collision detection can't tell us
-- about liquids (which do not collide)
if self.floats then
local pos = self.object:get_pos()
local bcp = vector.round({x = pos.x, y = pos.y - 0.7, z = pos.z})
local bcn = core.get_node(bcp)
local bcd = core.registered_nodes[bcn.name]
if bcd and bcd.liquidtype ~= "none" then
if self:try_place(bcp, bcn) then
self.object:remove()
return
end
elseif bcd and bcd.buildable_to and
(core.get_item_group(self.node.name, "float") == 0 or
bcd.liquidtype == "none") then
core.remove_node(bcp)
return
end
local np = {x = bcp.x, y = bcp.y + 1, z = bcp.z}
-- Check what's here
local n2 = core.get_node(np)
local nd = core.registered_nodes[n2.name]
-- If it's not air or liquid, remove node and replace it with
-- it's drops
if n2.name ~= "air" and (not nd or nd.liquidtype == "none") then
core.remove_node(np)
if nd and nd.buildable_to == false then
-- Add dropped items
local drops = core.get_node_drops(n2, "")
for _, dropped_item in pairs(drops) do
core.add_item(np, dropped_item)
end
assert(moveresult)
if not moveresult.collides then
return -- Nothing to do :)
end
local bcp, bcn
local player_collision
if moveresult.touching_ground then
for _, info in ipairs(moveresult.collisions) do
if info.type == "object" then
if info.axis == "y" and info.object:is_player() then
player_collision = info
end
end
-- Run script hook
for _, callback in pairs(core.registered_on_dignodes) do
callback(np, n2)
elseif info.axis == "y" then
bcp = info.node_pos
bcn = core.get_node(bcp)
break
end
end
-- Create node and remove entity
local def = core.registered_nodes[self.node.name]
if def then
core.add_node(np, self.node)
if self.meta then
local meta = core.get_meta(np)
meta:from_table(self.meta)
end
if def.sounds and def.sounds.place then
core.sound_play(def.sounds.place, {pos = np})
end
end
if not bcp then
-- We're colliding with something, but not the ground. Irrelevant to us.
if player_collision then
-- Continue falling through players by moving a little into
-- their collision box
-- TODO: this hack could be avoided in the future if objects
-- could choose who to collide with
local vel = self.object:get_velocity()
self.object:set_velocity({
x = vel.x,
y = player_collision.old_velocity.y,
z = vel.z
})
self.object:set_pos(vector.add(self.object:get_pos(),
{x = 0, y = -0.5, z = 0}))
end
return
elseif bcn.name == "ignore" then
-- Delete on contact with ignore at world edges
self.object:remove()
core.check_for_falling(np)
return
end
local vel = self.object:get_velocity()
if vector.equals(vel, {x = 0, y = 0, z = 0}) then
local npos = self.object:get_pos()
self.object:set_pos(vector.round(npos))
local failure = false
local pos = self.object:get_pos()
local distance = vector.apply(vector.subtract(pos, bcp), math.abs)
if distance.x >= 1 or distance.z >= 1 then
-- We're colliding with some part of a node that's sticking out
-- Since we don't want to visually teleport, drop as item
failure = true
elseif distance.y >= 2 then
-- Doors consist of a hidden top node and a bottom node that is
-- the actual door. Despite the top node being solid, the moveresult
-- almost always indicates collision with the bottom node.
-- Compensate for this by checking the top node
bcp.y = bcp.y + 1
bcn = core.get_node(bcp)
local def = core.registered_nodes[bcn.name]
if not (def and def.walkable) then
failure = true -- This is unexpected, fail
end
end
-- Try to actually place ourselves
if not failure then
failure = not self:try_place(bcp, bcn)
end
if failure then
local drops = core.get_node_drops(self.node, "")
for _, item in pairs(drops) do
core.add_item(pos, item)
end
end
self.object:remove()
end
})
@ -137,13 +374,14 @@ local function convert_to_falling_node(pos, node)
if not obj then
return false
end
-- remember node level, the entities' set_node() uses this
node.level = core.get_node_level(pos)
local meta = core.get_meta(pos)
local metatable = meta and meta:to_table() or {}
local def = core.registered_nodes[node.name]
if def and def.sounds and def.sounds.fall then
core.sound_play(def.sounds.fall, {pos = pos})
core.sound_play(def.sounds.fall, {pos = pos}, true)
end
obj:get_luaentity():set_node(node, metatable)
@ -176,7 +414,7 @@ local function drop_attached_node(p)
def.preserve_metadata(pos_copy, node_copy, oldmeta, drops)
end
if def and def.sounds and def.sounds.fall then
core.sound_play(def.sounds.fall, {pos = p})
core.sound_play(def.sounds.fall, {pos = p}, true)
end
core.remove_node(p)
for _, item in pairs(drops) do
@ -222,18 +460,23 @@ function core.check_single_for_falling(p)
-- Only spawn falling node if node below is loaded
local n_bottom = core.get_node_or_nil(p_bottom)
local d_bottom = n_bottom and core.registered_nodes[n_bottom.name]
if d_bottom and
(core.get_item_group(n.name, "float") == 0 or
d_bottom.liquidtype == "none") and
(n.name ~= n_bottom.name or (d_bottom.leveled and
core.get_node_level(p_bottom) <
core.get_node_max_level(p_bottom))) and
(not d_bottom.walkable or d_bottom.buildable_to) then
convert_to_falling_node(p, n)
return true
if d_bottom then
local same = n.name == n_bottom.name
-- Let leveled nodes fall if it can merge with the bottom node
if same and d_bottom.paramtype2 == "leveled" and
core.get_node_level(p_bottom) <
core.get_node_max_level(p_bottom) then
convert_to_falling_node(p, n)
return true
end
-- Otherwise only if the bottom node is considered "fall through"
if not same and
(not d_bottom.walkable or d_bottom.buildable_to) and
(core.get_item_group(n.name, "float") == 0 or
d_bottom.liquidtype == "none") then
convert_to_falling_node(p, n)
return true
end
end
end

View File

@ -12,6 +12,11 @@ core.features = {
no_chat_message_prediction = true,
object_use_texture_alpha = true,
object_independent_selectionbox = true,
httpfetch_binary_data = true,
formspec_version_element = true,
area_store_persistent_ids = true,
pathfinder_works = true,
object_step_has_moveresult = true,
}
function core.has_feature(arg)

View File

@ -8,6 +8,9 @@ local blocks_forceloaded
local blocks_temploaded = {}
local total_forceloaded = 0
-- true, if the forceloaded blocks got changed (flag for persistence on-disk)
local forceload_blocks_changed = false
local BLOCKSIZE = core.MAP_BLOCKSIZE
local function get_blockpos(pos)
return {
@ -31,6 +34,9 @@ local function get_relevant_tables(transient)
end
function core.forceload_block(pos, transient)
-- set changed flag
forceload_blocks_changed = true
local blockpos = get_blockpos(pos)
local hash = core.hash_node_position(blockpos)
local relevant_table, other_table = get_relevant_tables(transient)
@ -51,6 +57,9 @@ function core.forceload_block(pos, transient)
end
function core.forceload_free_block(pos, transient)
-- set changed flag
forceload_blocks_changed = true
local blockpos = get_blockpos(pos)
local hash = core.hash_node_position(blockpos)
local relevant_table, other_table = get_relevant_tables(transient)
@ -95,6 +104,28 @@ core.after(5, function()
end
end)
core.register_on_shutdown(function()
-- persists the currently forceloaded blocks to disk
local function persist_forceloaded_blocks()
write_file(wpath.."/force_loaded.txt", blocks_forceloaded)
end)
end
-- periodical forceload persistence
local function periodically_persist_forceloaded_blocks()
-- only persist if the blocks actually changed
if forceload_blocks_changed then
persist_forceloaded_blocks()
-- reset changed flag
forceload_blocks_changed = false
end
-- recheck after some time
core.after(10, periodically_persist_forceloaded_blocks)
end
-- persist periodically
core.after(5, periodically_persist_forceloaded_blocks)
-- persist on shutdown
core.register_on_shutdown(persist_forceloaded_blocks)

View File

@ -1,36 +1,38 @@
local scriptpath = core.get_builtin_path()
local commonpath = scriptpath.."common"..DIR_DELIM
local gamepath = scriptpath.."game"..DIR_DELIM
local commonpath = scriptpath .. "common" .. DIR_DELIM
local gamepath = scriptpath .. "game".. DIR_DELIM
-- Shared between builtin files, but
-- not exposed to outer context
local builtin_shared = {}
dofile(commonpath.."vector.lua")
dofile(commonpath .. "vector.lua")
dofile(gamepath.."constants.lua")
assert(loadfile(gamepath.."item.lua"))(builtin_shared)
dofile(gamepath.."register.lua")
dofile(gamepath .. "constants.lua")
assert(loadfile(gamepath .. "item.lua"))(builtin_shared)
dofile(gamepath .. "register.lua")
if core.settings:get_bool("profiler.load") then
profiler = dofile(scriptpath.."profiler"..DIR_DELIM.."init.lua")
profiler = dofile(scriptpath .. "profiler" .. DIR_DELIM .. "init.lua")
end
dofile(commonpath .. "after.lua")
dofile(gamepath.."item_entity.lua")
dofile(gamepath.."deprecated.lua")
dofile(gamepath.."misc.lua")
dofile(gamepath.."privileges.lua")
dofile(gamepath.."auth.lua")
dofile(gamepath .. "item_entity.lua")
dofile(gamepath .. "deprecated.lua")
dofile(gamepath .. "misc.lua")
dofile(gamepath .. "privileges.lua")
dofile(gamepath .. "auth.lua")
dofile(commonpath .. "chatcommands.lua")
dofile(gamepath.."chatcommands.lua")
dofile(gamepath.."static_spawn.lua")
dofile(gamepath.."detached_inventory.lua")
assert(loadfile(gamepath.."falling.lua"))(builtin_shared)
dofile(gamepath.."features.lua")
dofile(gamepath.."voxelarea.lua")
dofile(gamepath.."forceloading.lua")
dofile(gamepath.."statbars.lua")
dofile(gamepath .. "chat.lua")
dofile(commonpath .. "information_formspecs.lua")
dofile(gamepath .. "static_spawn.lua")
dofile(gamepath .. "detached_inventory.lua")
assert(loadfile(gamepath .. "falling.lua"))(builtin_shared)
dofile(gamepath .. "features.lua")
dofile(gamepath .. "voxelarea.lua")
dofile(gamepath .. "forceloading.lua")
dofile(gamepath .. "statbars.lua")
dofile(gamepath .. "knockback.lua")
profiler = nil

View File

@ -206,7 +206,6 @@ function core.get_node_drops(node, toolname)
-- Extended drop table
local got_items = {}
local got_count = 0
local _, item, tool
for _, item in ipairs(drop.items) do
local good_rarity = true
local good_tool = true
@ -251,11 +250,6 @@ local function user_name(user)
return user and user:get_player_name() or ""
end
local function is_protected(pos, name)
return core.is_protected(pos, name) and
not minetest.check_player_privs(name, "protection_bypass")
end
-- Returns a logging function. For empty names, does not log.
local function make_log(name)
return name ~= "" and core.log or function() end
@ -265,7 +259,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
prevent_after_place)
local def = itemstack:get_definition()
if def.type ~= "node" or pointed_thing.type ~= "node" then
return itemstack, false
return itemstack, nil
end
local under = pointed_thing.under
@ -278,7 +272,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
if not oldnode_under or not oldnode_above then
log("info", playername .. " tried to place"
.. " node in unloaded position " .. core.pos_to_string(above))
return itemstack, false
return itemstack, nil
end
local olddef_under = core.registered_nodes[oldnode_under.name]
@ -290,7 +284,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
log("info", playername .. " tried to place"
.. " node in invalid position " .. core.pos_to_string(above)
.. ", replacing " .. oldnode_above.name)
return itemstack, false
return itemstack, nil
end
-- Place above pointed node
@ -302,18 +296,15 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
place_to = {x = under.x, y = under.y, z = under.z}
end
if is_protected(place_to, playername) then
if core.is_protected(place_to, playername) then
log("action", playername
.. " tried to place " .. def.name
.. " at protected position "
.. core.pos_to_string(place_to))
core.record_protection_violation(place_to, playername)
return itemstack
return itemstack, nil
end
log("action", playername .. " places node "
.. def.name .. " at " .. core.pos_to_string(place_to))
local oldnode = core.get_node(place_to)
local newnode = {name = def.name, param1 = 0, param2 = param2 or 0}
@ -339,7 +330,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
z = above.z - placer_pos.z
}
newnode.param2 = core.dir_to_facedir(dir)
log("action", "facedir: " .. newnode.param2)
log("info", "facedir: " .. newnode.param2)
end
end
@ -367,12 +358,23 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
not builtin_shared.check_attached_node(place_to, newnode) then
log("action", "attached node " .. def.name ..
" can not be placed at " .. core.pos_to_string(place_to))
return itemstack, false
return itemstack, nil
end
log("action", playername .. " places node "
.. def.name .. " at " .. core.pos_to_string(place_to))
-- Add node and update
core.add_node(place_to, newnode)
-- Play sound if it was done by a player
if playername ~= "" and def.sounds and def.sounds.place then
core.sound_play(def.sounds.place, {
pos = place_to,
exclude_player = playername,
}, true)
end
local take_item = true
-- Run callback
@ -401,9 +403,10 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
if take_item then
itemstack:take_item()
end
return itemstack, true
return itemstack, place_to
end
-- deprecated, item_place does not call this
function core.item_place_object(itemstack, placer, pointed_thing)
local pos = core.get_pointed_thing_position(pointed_thing, true)
if pos ~= nil then
@ -421,14 +424,15 @@ function core.item_place(itemstack, placer, pointed_thing, param2)
local nn = n.name
if core.registered_nodes[nn] and core.registered_nodes[nn].on_rightclick then
return core.registered_nodes[nn].on_rightclick(pointed_thing.under, n,
placer, itemstack, pointed_thing) or itemstack, false
placer, itemstack, pointed_thing) or itemstack, nil
end
end
-- Place if node, otherwise do nothing
if itemstack:get_definition().type == "node" then
return core.item_place_node(itemstack, placer, pointed_thing, param2)
end
return itemstack
return itemstack, nil
end
function core.item_secondary_use(itemstack, placer)
@ -466,12 +470,15 @@ function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed
return result
end
end
local def = itemstack:get_definition()
if itemstack:take_item() ~= nil then
user:set_hp(user:get_hp() + hp_change)
local def = itemstack:get_definition()
if def and def.sound and def.sound.eat then
minetest.sound_play(def.sound.eat, { pos = user:get_pos(), max_hear_distance = 16 })
core.sound_play(def.sound.eat, {
pos = user:get_pos(),
max_hear_distance = 16
}, true)
end
if replace_with_item then
@ -552,7 +559,7 @@ function core.node_dig(pos, node, digger)
return
end
if is_protected(pos, diggername) then
if core.is_protected(pos, diggername) then
log("action", diggername
.. " tried to dig " .. node.name
.. " at protected position "
@ -575,10 +582,13 @@ function core.node_dig(pos, node, digger)
wielded = wdef.after_use(wielded, digger, node, dp) or wielded
else
-- Wear out tool
if not core.settings:get_bool("creative_mode") then
if not core.is_creative_enabled(diggername) then
wielded:add_wear(dp.wear)
if wielded:get_count() == 0 and wdef.sound and wdef.sound.breaks then
core.sound_play(wdef.sound.breaks, {pos = pos, gain = 0.5})
core.sound_play(wdef.sound.breaks, {
pos = pos,
gain = 0.5
}, true)
end
end
end
@ -610,6 +620,14 @@ function core.node_dig(pos, node, digger)
-- Remove node and update
core.remove_node(pos)
-- Play sound if it was done by a player
if diggername ~= "" and def and def.sounds and def.sounds.dug then
core.sound_play(def.sounds.dug, {
pos = pos,
exclude_player = diggername,
}, true)
end
-- Run callback
if def and def.after_dig_node then
-- Copy pos and node because callback can modify them
@ -619,15 +637,10 @@ function core.node_dig(pos, node, digger)
end
-- Run script hook
local _, callback
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)
--print("Running " .. tostring(callback) ..
-- " (a " .. origin.name .. " callback in " .. origin.mod .. ")")
else
--print("No data associated with callback")
end
-- Copy pos and node because callback can modify them
@ -662,6 +675,8 @@ end
-- Item definition defaults
--
local default_stack_max = tonumber(minetest.settings:get("default_stack_max")) or 99
core.nodedef_default = {
-- Item properties
type="node",
@ -671,7 +686,7 @@ core.nodedef_default = {
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
stack_max = 99,
stack_max = default_stack_max,
usable = false,
liquids_pointable = false,
tool_capabilities = nil,
@ -735,7 +750,7 @@ core.craftitemdef_default = {
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
stack_max = 99,
stack_max = default_stack_max,
liquids_pointable = false,
tool_capabilities = nil,
@ -773,7 +788,7 @@ core.noneitemdef_default = { -- This is used for the hand and unknown items
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
stack_max = 99,
stack_max = default_stack_max,
liquids_pointable = false,
tool_capabilities = nil,

View File

@ -27,15 +27,17 @@ core.register_entity(":__builtin:item", {
visual = "wielditem",
visual_size = {x = 0.4, y = 0.4},
textures = {""},
spritediv = {x = 1, y = 1},
initial_sprite_basepos = {x = 0, y = 0},
is_visible = false,
},
itemstring = "",
moving_state = true,
slippery_state = false,
physical_state = true,
-- Item expiry
age = 0,
-- Pushing item out of solid nodes
force_out = nil,
force_out_start = nil,
set_item = function(self, item)
local stack = ItemStack(item or self.itemstring)
@ -52,18 +54,19 @@ core.register_entity(":__builtin:item", {
local max_count = stack:get_stack_max()
local count = math.min(stack:get_count(), max_count)
local size = 0.2 + 0.1 * (count / max_count) ^ (1 / 3)
local coll_height = size * 0.75
local def = core.registered_items[itemname]
local glow = def and def.light_source and
math.floor(def.light_source / 2 + 0.5)
self.object:set_properties({
is_visible = true,
visual = "wielditem",
textures = {itemname},
visual_size = {x = size, y = size},
collisionbox = {-size, -coll_height, -size,
size, coll_height, size},
selectionbox = {-size, -size, -size, size, size, size},
collisionbox = {-size, -size, -size, size, size, size},
automatic_rotate = math.pi * 0.5 * 0.2 / size,
wield_item = self.itemstring,
glow = glow,
})
end,
@ -131,7 +134,25 @@ core.register_entity(":__builtin:item", {
return true
end,
on_step = function(self, dtime)
enable_physics = function(self)
if not self.physical_state then
self.physical_state = true
self.object:set_properties({physical = true})
self.object:set_velocity({x=0, y=0, z=0})
self.object:set_acceleration({x=0, y=-gravity, z=0})
end
end,
disable_physics = function(self)
if self.physical_state then
self.physical_state = false
self.object:set_properties({physical = false})
self.object:set_velocity({x=0, y=0, z=0})
self.object:set_acceleration({x=0, y=0, z=0})
end
end,
on_step = function(self, dtime, moveresult)
self.age = self.age + dtime
if time_to_live > 0 and self.age > time_to_live then
self.itemstring = ""
@ -152,46 +173,125 @@ core.register_entity(":__builtin:item", {
return
end
local vel = self.object:get_velocity()
local def = node and core.registered_nodes[node.name]
local is_moving = (def and not def.walkable) or
vel.x ~= 0 or vel.y ~= 0 or vel.z ~= 0
local is_slippery = false
if def and def.walkable then
local slippery = core.get_item_group(node.name, "slippery")
is_slippery = slippery ~= 0
if is_slippery and (math.abs(vel.x) > 0.2 or math.abs(vel.z) > 0.2) then
-- Horizontal deceleration
local slip_factor = 4.0 / (slippery + 4)
self.object:set_acceleration({
x = -vel.x * slip_factor,
y = 0,
z = -vel.z * slip_factor
})
elseif vel.y == 0 then
is_moving = false
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 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
(f.y > 0 and pos.y + c[2] > s.y + 0.5) or
(f.z > 0 and pos.z + c[3] > s.z + 0.5) or
(f.x < 0 and pos.x + c[4] < s.x - 0.5) or
(f.z < 0 and pos.z + c[6] < s.z - 0.5)
if ok then
-- Item was successfully forced out
self.force_out = nil
self:enable_physics()
return
end
end
if self.moving_state == is_moving and
self.slippery_state == is_slippery then
-- Do not update anything until the moving state changes
if not self.physical_state then
return -- Don't do anything
end
assert(moveresult,
"Collision info missing, this is caused by an out-of-date/buggy mod or game")
if not moveresult.collides then
-- future TODO: items should probably decelerate in air
return
end
self.moving_state = is_moving
self.slippery_state = is_slippery
if is_moving then
self.object:set_acceleration({x = 0, y = -gravity, z = 0})
else
self.object:set_acceleration({x = 0, y = 0, z = 0})
self.object:set_velocity({x = 0, y = 0, z = 0})
-- Push item out when stuck inside solid node
local is_stuck = false
local snode = core.get_node_or_nil(pos)
if snode then
local sdef = core.registered_nodes[snode.name] or {}
is_stuck = (sdef.walkable == nil or sdef.walkable == true)
and (sdef.collision_box == nil or sdef.collision_box.type == "regular")
and (sdef.node_box == nil or sdef.node_box.type == "regular")
end
--Only collect items if not moving
if is_moving then
if is_stuck then
local shootdir
local order = {
{x=1, y=0, z=0}, {x=-1, y=0, z= 0},
{x=0, y=0, z=1}, {x= 0, y=0, z=-1},
}
-- Check which one of the 4 sides is free
for o = 1, #order do
local cnode = core.get_node(vector.add(pos, order[o])).name
local cdef = core.registered_nodes[cnode] or {}
if cnode ~= "ignore" and cdef.walkable == false then
shootdir = order[o]
break
end
end
-- If none of the 4 sides is free, check upwards
if not shootdir then
shootdir = {x=0, y=1, z=0}
local cnode = core.get_node(vector.add(pos, shootdir)).name
if cnode == "ignore" then
shootdir = nil -- Do not push into ignore
end
end
if shootdir then
-- Set new item moving speed accordingly
local newv = vector.multiply(shootdir, 3)
self:disable_physics()
self.object:set_velocity(newv)
self.force_out = newv
self.force_out_start = vector.round(pos)
return
end
end
node = nil -- ground node we're colliding with
if moveresult.touching_ground then
for _, info in ipairs(moveresult.collisions) do
if info.axis == "y" then
node = core.get_node(info.node_pos)
break
end
end
end
-- Slide on slippery nodes
local def = node and core.registered_nodes[node.name]
local keep_movement = false
if def then
local slippery = core.get_item_group(node.name, "slippery")
local vel = self.object:get_velocity()
if slippery ~= 0 and (math.abs(vel.x) > 0.1 or math.abs(vel.z) > 0.1) then
-- Horizontal deceleration
local factor = math.min(4 / (slippery + 4) * dtime, 1)
self.object:set_velocity({
x = vel.x * (1 - factor),
y = 0,
z = vel.z * (1 - factor)
})
keep_movement = true
end
end
if not keep_movement then
self.object:set_velocity({x=0, y=0, z=0})
end
if self.moving_state == keep_movement then
-- Do not update anything until the moving state changes
return
end
self.moving_state = keep_movement
-- Only collect items if not moving
if self.moving_state then
return
end
-- Collect the items around to merge with

View File

@ -0,0 +1,46 @@
-- can be overriden by mods
function core.calculate_knockback(player, hitter, time_from_last_punch, tool_capabilities, dir, distance, damage)
if damage == 0 or player:get_armor_groups().immortal then
return 0.0
end
local m = 8
-- solve m - m*e^(k*4) = 4 for k
local k = -0.17328
local res = m - m * math.exp(k * damage)
if distance < 2.0 then
res = res * 1.1 -- more knockback when closer
elseif distance > 4.0 then
res = res * 0.9 -- less when far away
end
return res
end
local function vector_absmax(v)
local max, abs = math.max, math.abs
return max(max(abs(v.x), abs(v.y)), abs(v.z))
end
core.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, unused_dir, damage)
if player:get_hp() == 0 then
return -- RIP
end
-- Server::handleCommand_Interact() adds eye offset to one but not the other
-- so the direction is slightly off, calculate it ourselves
local dir = vector.subtract(player:get_pos(), hitter:get_pos())
local d = vector.length(dir)
if d ~= 0.0 then
dir = vector.divide(dir, d)
end
local k = core.calculate_knockback(player, hitter, time_from_last_punch, tool_capabilities, dir, d, damage)
local kdir = vector.multiply(dir, k)
if vector_absmax(kdir) < 1.0 then
return -- barely noticeable, so don't even send
end
player:add_player_velocity(kdir)
end)

View File

@ -63,8 +63,7 @@ end
core.register_on_joinplayer(function(player)
local player_name = player:get_player_name()
player_list[player_name] = player
if not minetest.is_singleplayer() then
if not core.is_singleplayer() then
local status = core.get_server_status(player_name, true)
if status and status ~= "" then
core.chat_send_player(player_name, status)
@ -76,22 +75,10 @@ end)
core.register_on_leaveplayer(function(player, timed_out)
local player_name = player:get_player_name()
player_list[player_name] = nil
core.send_leave_message(player_name, timed_out)
end)
function core.get_connected_players()
local temp_table = {}
for index, value in pairs(player_list) do
if value:is_player_connected() then
temp_table[#temp_table + 1] = value
end
end
return temp_table
end
function core.is_player(player)
-- a table being a player is also supported because it quacks sufficiently
-- like a player if it has the is_player function
@ -182,6 +169,12 @@ function core.record_protection_violation(pos, name)
end
end
-- To be overridden by Creative mods
local creative_mode_cache = core.settings:get_bool("creative_mode")
function core.is_creative_enabled(name)
return creative_mode_cache
end
-- Checks if specified volume intersects a protected volume

View File

@ -18,7 +18,7 @@ function core.register_privilege(name, param)
def.description = "(no description)"
end
end
local def = {}
local def
if type(param) == "table" then
def = param
else

View File

@ -79,6 +79,7 @@ end
function core.register_abm(spec)
-- Add to core.registered_abms
assert(type(spec.action) == "function", "Required field 'action' of type function")
core.registered_abms[#core.registered_abms + 1] = spec
spec.mod_origin = core.get_current_modname() or "??"
end
@ -86,6 +87,7 @@ end
function core.register_lbm(spec)
-- Add to core.registered_lbms
check_modname_prefix(spec.name)
assert(type(spec.action) == "function", "Required field 'action' of type function")
core.registered_lbms[#core.registered_lbms + 1] = spec
spec.mod_origin = core.get_current_modname() or "??"
end
@ -254,6 +256,18 @@ function core.register_tool(name, tooldef)
end
-- END Legacy stuff
-- This isn't just legacy, but more of a convenience feature
local toolcaps = tooldef.tool_capabilities
if toolcaps and toolcaps.punch_attack_uses == nil then
for _, cap in pairs(toolcaps.groupcaps or {}) do
local level = (cap.maxlevel or 0) - 1
if (cap.uses or 0) ~= 0 and level >= 0 then
toolcaps.punch_attack_uses = cap.uses * (3 ^ level)
break
end
end
end
core.register_item(name, tooldef)
end
@ -301,18 +315,17 @@ end
-- Alias the forbidden item names to "" so they can't be
-- created via itemstrings (e.g. /give)
local name
for name in pairs(forbidden_item_names) do
core.registered_aliases[name] = ""
register_alias_raw(name, "")
end
-- Deprecated:
-- Obsolete:
-- Aliases for core.register_alias (how ironic...)
--core.alias_node = core.register_alias
--core.alias_tool = core.register_alias
--core.alias_craftitem = core.register_alias
-- core.alias_node = core.register_alias
-- core.alias_tool = core.register_alias
-- core.alias_craftitem = core.register_alias
--
-- Built-in node definitions. Also defined in C.
@ -361,9 +374,9 @@ core.register_node(":ignore", {
drop = "",
groups = {not_in_creative_inventory=1},
on_place = function(itemstack, placer, pointed_thing)
minetest.chat_send_player(
core.chat_send_player(
placer:get_player_name(),
minetest.colorize("#FF0000",
core.colorize("#FF0000",
"You can't place 'ignore' nodes!"))
return ""
end,
@ -372,6 +385,7 @@ core.register_node(":ignore", {
-- The hand (bare definition)
core.register_item(":", {
type = "none",
wield_image = "wieldhand.png",
groups = {not_in_creative_inventory=1},
})
@ -411,10 +425,6 @@ function core.run_callbacks(callbacks, mode, ...)
local origin = core.callback_origins[callbacks[i]]
if origin then
core.set_last_run_mod(origin.mod)
--print("Running " .. tostring(callbacks[i]) ..
-- " (a " .. origin.name .. " callback in " .. origin.mod .. ")")
else
--print("No data associated with callback")
end
local cb_ret = callbacks[i](...)
@ -514,11 +524,17 @@ local function make_registration_wrap(reg_fn_name, clear_fn_name)
end
local function make_wrap_deregistration(reg_fn, clear_fn, list)
local unregister = function (unregistered_key)
local unregister = function (key)
if type(key) ~= "string" then
error("key is not a string", 2)
end
if not list[key] then
error("Attempt to unregister non-existent element - '" .. key .. "'", 2)
end
local temporary_list = table.copy(list)
clear_fn()
for k,v in pairs(temporary_list) do
if unregistered_key ~= k then
if key ~= k then
reg_fn(v)
end
end
@ -529,7 +545,7 @@ end
core.registered_on_player_hpchanges = { modifiers = { }, loggers = { } }
function core.registered_on_player_hpchange(player, hp_change, reason)
local last = false
local last
for i = #core.registered_on_player_hpchanges.modifiers, 1, -1 do
local func = core.registered_on_player_hpchanges.modifiers[i]
hp_change, last = func(player, hp_change, reason)
@ -564,7 +580,8 @@ core.registered_biomes = make_registration_wrap("register_biome", "cle
core.registered_ores = make_registration_wrap("register_ore", "clear_registered_ores")
core.registered_decorations = make_registration_wrap("register_decoration", "clear_registered_decorations")
core.unregister_biome = make_wrap_deregistration(core.register_biome, core.clear_registered_biomes, core.registered_biomes)
core.unregister_biome = make_wrap_deregistration(core.register_biome,
core.clear_registered_biomes, core.registered_biomes)
core.registered_on_chat_messages, core.register_on_chat_message = make_registration()
core.registered_globalsteps, core.register_globalstep = make_registration()
@ -590,9 +607,9 @@ core.registered_on_item_eats, core.register_on_item_eat = make_registration()
core.registered_on_punchplayers, core.register_on_punchplayer = make_registration()
core.registered_on_priv_grant, core.register_on_priv_grant = make_registration()
core.registered_on_priv_revoke, core.register_on_priv_revoke = make_registration()
core.registered_on_authplayers, core.register_on_authplayer = make_registration()
core.registered_can_bypass_userlimit, core.register_can_bypass_userlimit = make_registration()
core.registered_on_modchannel_message, core.register_on_modchannel_message = make_registration()
core.registered_on_auth_fail, core.register_on_auth_fail = make_registration()
core.registered_on_player_inventory_actions, core.register_on_player_inventory_action = make_registration()
core.registered_allow_player_inventory_actions, core.register_allow_player_inventory_action = make_registration()

View File

@ -1,26 +1,28 @@
-- cache setting
local enable_damage = core.settings:get_bool("enable_damage")
local health_bar_definition =
{
local health_bar_definition = {
hud_elem_type = "statbar",
position = { x=0.5, y=1 },
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)},
size = {x = 24, y = 24},
offset = {x = (-10 * 24) - 25, y = -(48 + 24 + 16)},
}
local breath_bar_definition =
{
local breath_bar_definition = {
hud_elem_type = "statbar",
position = { x=0.5, y=1 },
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)},
size = {x = 24, y = 24},
offset = {x = 25, y= -(48 + 24 + 16)},
}
local hud_ids = {}
@ -28,10 +30,10 @@ local hud_ids = {}
local function scaleToDefault(player, field)
-- Scale "hp" or "breath" to the default dimensions
local current = player["get_" .. field](player)
local nominal = core["PLAYER_MAX_".. field:upper() .. "_DEFAULT"]
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
math.max(player:get_properties()[field .. "_max"], current))
return current / max_display * nominal
end
local function update_builtin_statbars(player)
@ -50,10 +52,12 @@ local function update_builtin_statbars(player)
end
local hud = hud_ids[name]
if flags.healthbar and enable_damage then
local immortal = player:get_armor_groups().immortal == 1
if flags.healthbar and enable_damage and not immortal then
local number = scaleToDefault(player, "hp")
if hud.id_healthbar == nil then
local hud_def = table.copy(health_bar_definition)
if hud.id_healthbar == nil then
local hud_def = table.copy(health_bar_definition)
hud_def.number = number
hud.id_healthbar = player:hud_add(hud_def)
else
@ -64,19 +68,28 @@ local function update_builtin_statbars(player)
hud.id_healthbar = nil
end
local show_breathbar = flags.breathbar and enable_damage and not immortal
local breath = player:get_breath()
local breath_max = player:get_properties().breath_max
if flags.breathbar and enable_damage and
player:get_breath() < breath_max then
if show_breathbar and breath <= breath_max then
local number = 2 * scaleToDefault(player, "breath")
if hud.id_breathbar == nil then
local hud_def = table.copy(breath_bar_definition)
if not hud.id_breathbar and breath < breath_max then
local hud_def = table.copy(breath_bar_definition)
hud_def.number = number
hud.id_breathbar = player:hud_add(hud_def)
else
elseif hud.id_breathbar then
player:hud_change(hud.id_breathbar, "number", number)
end
elseif hud.id_breathbar then
player:hud_remove(hud.id_breathbar)
end
if hud.id_breathbar and (not show_breathbar or breath == breath_max) then
minetest.after(1, function(player_name, breath_bar)
local player = minetest.get_player_by_name(player_name)
if player then
player:hud_remove(breath_bar)
end
end, name, hud.id_breathbar)
hud.id_breathbar = nil
end
end
@ -116,7 +129,7 @@ local function player_event_handler(player,eventname)
end
end
if eventname == "hud_changed" then
if eventname == "hud_changed" or eventname == "properties_changed" then
update_builtin_statbars(player)
return true
end
@ -124,14 +137,14 @@ local function player_event_handler(player,eventname)
return false
end
function core.hud_replace_builtin(name, definition)
function core.hud_replace_builtin(hud_name, definition)
if type(definition) ~= "table" or
definition.hud_elem_type ~= "statbar" then
return false
end
if name == "health" then
if hud_name == "health" then
health_bar_definition = definition
for name, ids in pairs(hud_ids) do
@ -145,7 +158,7 @@ function core.hud_replace_builtin(name, definition)
return true
end
if name == "breath" then
if hud_name == "breath" then
breath_bar_definition = definition
for name, ids in pairs(hud_ids) do

View File

@ -36,6 +36,7 @@ dofile(commonpath .. "misc_helpers.lua")
if INIT == "game" then
dofile(gamepath .. "init.lua")
assert(not core.get_http_api)
elseif INIT == "mainmenu" then
local mm_script = core.settings:get("main_menu_script")
if mm_script and mm_script ~= "" then

View File

@ -8,15 +8,7 @@ local function handle_job(jobid, serialized_retval)
core.async_jobs[jobid] = nil
end
if core.register_globalstep then
core.register_globalstep(function(dtime)
for i, job in ipairs(core.get_finished_jobs()) do
handle_job(job.jobid, job.retval)
end
end)
else
core.async_event_handler = handle_job
end
core.async_event_handler = handle_job
function core.handle_async(func, parameter, callback)
-- Serialize function

View File

@ -93,9 +93,9 @@ function render_serverlist_row(spec, is_favorite)
end
end
local details = ""
local grey_out = not is_server_protocol_compat(spec.proto_min, spec.proto_max)
local details
if is_favorite then
details = "1,"
else
@ -118,11 +118,11 @@ function render_serverlist_row(spec, is_favorite)
end
if spec.clients and spec.clients_max then
local clients_color = ''
local clients_percent = 100 * spec.clients / spec.clients_max
-- Choose a color depending on how many clients are connected
-- (relatively to clients_max)
local clients_color
if grey_out then clients_color = '#aaaaaa'
elseif spec.clients == 0 then clients_color = '' -- 0 players: default/white
elseif clients_percent <= 60 then clients_color = '#a1e587' -- 0-60%: green
@ -171,6 +171,7 @@ os.tempfolder = function()
local filetocheck = os.tmpname()
os.remove(filetocheck)
-- luacheck: ignore
-- https://blogs.msdn.microsoft.com/vcblog/2014/06/18/c-runtime-crt-features-fixes-and-breaking-changes-in-visual-studio-14-ctp1/
-- The C runtime (CRT) function called by os.tmpname is tmpnam.
-- Microsofts tmpnam implementation in older CRT / MSVC releases is defective.

View File

@ -23,7 +23,49 @@ local function modname_valid(name)
return not name:find("[^a-z0-9_]")
end
local function init_data(data)
data.list = filterlist.create(
pkgmgr.preparemodlist,
pkgmgr.comparemod,
function(element, uid)
if element.name == uid then
return true
end
end,
function(element, criteria)
if criteria.hide_game and
element.is_game_content then
return false
end
if criteria.hide_modpackcontents and
element.modpack ~= nil then
return false
end
return true
end,
{
worldpath = data.worldspec.path,
gameid = data.worldspec.gameid
})
if data.selected_mod > data.list:size() then
data.selected_mod = 0
end
data.list:set_filtercriteria({
hide_game = data.hide_gamemods,
hide_modpackcontents = data.hide_modpackcontents
})
data.list:add_sort_mechanism("alphabetic", sort_mod_list)
data.list:set_sortmode("alphabetic")
end
local function get_formspec(data)
if not data.list then
init_data(data)
end
local mod = data.list:get_list()[data.selected_mod] or {name = ""}
local retval =
@ -31,8 +73,6 @@ local function get_formspec(data)
"label[0.5,0;" .. fgettext("World:") .. "]" ..
"label[1.75,0;" .. data.worldspec.name .. "]"
local hard_deps, soft_deps = pkgmgr.get_dependencies(mod.path)
if mod.is_modpack or mod.type == "game" then
local info = minetest.formspec_escape(
core.get_content_info(mod.path).description)
@ -46,21 +86,55 @@ local function get_formspec(data)
retval = retval ..
"textarea[0.25,0.7;5.75,7.2;;" .. info .. ";]"
else
local hard_deps, soft_deps = pkgmgr.get_dependencies(mod.path)
local hard_deps_str = table.concat(hard_deps, ",")
local soft_deps_str = table.concat(soft_deps, ",")
retval = retval ..
"label[0,0.7;" .. fgettext("Mod:") .. "]" ..
"label[0.75,0.7;" .. mod.name .. "]" ..
"label[0,1.25;" .. fgettext("Dependencies:") .. "]" ..
"textlist[0,1.75;5,2.125;world_config_depends;" .. hard_deps ..
";0]" ..
"label[0,3.875;" .. fgettext("Optional dependencies:") .. "]" ..
"textlist[0,4.375;5,1.8;world_config_optdepends;" ..
soft_deps .. ";0]"
"label[0.75,0.7;" .. mod.name .. "]"
if hard_deps_str == "" then
if soft_deps_str == "" then
retval = retval ..
"label[0,1.25;" ..
fgettext("No (optional) dependencies") .. "]"
else
retval = retval ..
"label[0,1.25;" .. fgettext("No hard dependencies") ..
"]" ..
"label[0,1.75;" .. fgettext("Optional dependencies:") ..
"]" ..
"textlist[0,2.25;5,4;world_config_optdepends;" ..
soft_deps_str .. ";0]"
end
else
if soft_deps_str == "" then
retval = retval ..
"label[0,1.25;" .. fgettext("Dependencies:") .. "]" ..
"textlist[0,1.75;5,4;world_config_depends;" ..
hard_deps_str .. ";0]" ..
"label[0,6;" .. fgettext("No optional dependencies") .. "]"
else
retval = retval ..
"label[0,1.25;" .. fgettext("Dependencies:") .. "]" ..
"textlist[0,1.75;5,2.125;world_config_depends;" ..
hard_deps_str .. ";0]" ..
"label[0,3.9;" .. fgettext("Optional dependencies:") ..
"]" ..
"textlist[0,4.375;5,1.8;world_config_optdepends;" ..
soft_deps_str .. ";0]"
end
end
end
retval = retval ..
"button[3.25,7;2.5,0.5;btn_config_world_save;" ..
fgettext("Save") .. "]" ..
"button[5.75,7;2.5,0.5;btn_config_world_cancel;" ..
fgettext("Cancel") .. "]"
fgettext("Cancel") .. "]" ..
"button[9,7;2.5,0.5;btn_config_world_cdb;" ..
fgettext("Find More Mods") .. "]"
if mod.name ~= "" and not mod.is_game_content then
if mod.is_modpack then
@ -169,6 +243,16 @@ local function handle_buttons(this, fields)
return true
end
if fields.btn_config_world_cdb then
this.data.list = nil
local dlg = create_store_dlg("mod")
dlg:set_parent(this)
this:hide()
dlg:show()
return true
end
if fields.btn_enable_all_mods then
local list = this.data.list:get_raw_list()
@ -218,43 +302,5 @@ function create_configure_world_dlg(worldidx)
return
end
dlg.data.list = filterlist.create(
pkgmgr.preparemodlist,
pkgmgr.comparemod,
function(element, uid)
if element.name == uid then
return true
end
end,
function(element, criteria)
if criteria.hide_game and
element.is_game_content then
return false
end
if criteria.hide_modpackcontents and
element.modpack ~= nil then
return false
end
return true
end,
{
worldpath = dlg.data.worldspec.path,
gameid = dlg.data.worldspec.gameid
}
)
if dlg.data.selected_mod > dlg.data.list:size() then
dlg.data.selected_mod = 0
end
dlg.data.list:set_filtercriteria({
hide_game = dlg.data.hide_gamemods,
hide_modpackcontents = dlg.data.hide_modpackcontents
})
dlg.data.list:add_sort_mechanism("alphabetic", sort_mod_list)
dlg.data.list:set_sortmode("alphabetic")
return dlg
end

View File

@ -1,5 +1,5 @@
--Minetest
--Copyright (C) 2018 rubenwardy
--Copyright (C) 2018-20 rubenwardy
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
@ -15,8 +15,17 @@
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
if not minetest.get_http_api then
function create_store_dlg()
return messagebox("store",
fgettext("ContentDB is not available when Minetest was compiled without cURL"))
end
return
end
local store = { packages = {}, packages_full = {} }
local package_dialog = {}
local http = minetest.get_http_api()
-- Screenshot
local screenshot_dir = core.get_cache_path() .. DIR_DELIM .. "cdb"
@ -36,6 +45,9 @@ local filter_types_titles = {
fgettext("Texture packs"),
}
local number_downloading = 0
local download_queue = {}
local filter_types_type = {
nil,
"game",
@ -44,35 +56,33 @@ local filter_types_type = {
}
local function download_package(param)
if core.download_file(param.package.url, param.filename) then
return {
package = param.package,
filename = param.filename,
successful = true,
}
else
core.log("error", "downloading " .. dump(param.package.url) .. " failed")
return {
package = param.package,
successful = false,
}
end
end
local function start_install(calling_dialog, package)
local function start_install(package)
local params = {
package = package,
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(result.package.type,
result.filename, result.package.name,
result.package.path)
local path, msg = pkgmgr.install(package.type,
result.filename, package.name,
package.path)
if not path then
gamedata.errormessage = msg
else
@ -80,36 +90,33 @@ local function start_install(calling_dialog, package)
local conf_path
local name_is_title = false
if result.package.type == "mod" then
if package.type == "mod" then
local actual_type = pkgmgr.get_folder_type(path)
if actual_type.type == "modpack" then
conf_path = path .. DIR_DELIM .. "modpack.conf"
else
conf_path = path .. DIR_DELIM .. "mod.conf"
end
elseif result.package.type == "game" then
elseif package.type == "game" then
conf_path = path .. DIR_DELIM .. "game.conf"
name_is_title = true
elseif result.package.type == "txp" then
elseif package.type == "txp" then
conf_path = path .. DIR_DELIM .. "texture_pack.conf"
end
if conf_path then
local conf = Settings(conf_path)
local function set_def(key, value)
if conf:get(key) == nil then
conf:set(key, value)
end
end
if name_is_title then
set_def("name", result.package.title)
conf:set("name", package.title)
else
set_def("title", result.package.title)
set_def("name", result.package.name)
conf:set("title", package.title)
conf:set("name", package.name)
end
set_def("description", result.package.short_description)
set_def("author", result.package.author)
conf:set("release", result.package.release)
if not conf:get("description") then
conf:set("description", package.short_description)
end
conf:set("author", package.author)
conf:set("release", package.release)
conf:write()
end
end
@ -118,37 +125,43 @@ local function start_install(calling_dialog, package)
gamedata.errormessage = fgettext("Failed to download $1", package.name)
end
if gamedata.errormessage == nil then
core.button_handler({btn_hidden_close_download=result})
else
core.button_handler({btn_hidden_close_download={successful=false}})
package.downloading = false
number_downloading = number_downloading - 1
local next = download_queue[1]
if next then
table.remove(download_queue, 1)
start_install(next)
end
ui.update()
end
package.queued = false
package.downloading = true
if not core.handle_async(download_package, params, callback) then
core.log("error", "ERROR: async event failed")
gamedata.errormessage = fgettext("Failed to download $1", package.name)
return
end
end
local new_dlg = dialog_create("store_downloading",
function(data)
return "size[7,2]label[0.25,0.75;" ..
fgettext("Downloading and installing $1, please wait...", data.title) .. "]"
end,
function(this,fields)
if fields["btn_hidden_close_download"] ~= nil then
this:delete()
return true
end
local function queue_download(package)
local max_concurrent_downloads = tonumber(minetest.settings:get("contentdb_max_concurrent_downloads"))
if number_downloading < max_concurrent_downloads then
start_install(package)
else
table.insert(download_queue, package)
package.queued = true
end
end
return false
end,
nil)
new_dlg:set_parent(calling_dialog)
new_dlg.data.title = package.title
calling_dialog:hide()
new_dlg:show()
local function get_file_extension(path)
local parts = path:split(".")
return parts[#parts]
end
local function get_screenshot(package)
@ -159,8 +172,9 @@ local function get_screenshot(package)
end
-- Get tmp screenshot path
local ext = get_file_extension(package.thumbnail)
local filepath = screenshot_dir .. DIR_DELIM ..
package.type .. "-" .. package.author .. "-" .. package.name .. ".png"
("%s-%s-%s.%s"):format(package.type, package.author, package.name, ext)
-- Return if already downloaded
local file = io.open(filepath, "r")
@ -198,85 +212,12 @@ local function get_screenshot(package)
return defaulttexturedir .. "loading_screenshot.png"
end
function package_dialog.get_formspec()
local package = package_dialog.package
store.update_paths()
local formspec = {
"size[9,4;true]",
"image[0,1;4.5,3;", core.formspec_escape(get_screenshot(package)), ']',
"label[3.8,1;",
minetest.colorize(mt_color_green, core.formspec_escape(package.title)), "\n",
minetest.colorize('#BFBFBF', "by " .. core.formspec_escape(package.author)), "]",
"textarea[4,2;5.3,2;;;", core.formspec_escape(package.short_description), "]",
"button[0,0;2,1;back;", fgettext("Back"), "]",
}
if not package.path then
formspec[#formspec + 1] = "button[7,0;2,1;install;"
formspec[#formspec + 1] = fgettext("Install")
formspec[#formspec + 1] = "]"
elseif package.installed_release < package.release then
-- The install_ action also handles updating
formspec[#formspec + 1] = "button[7,0;2,1;install;"
formspec[#formspec + 1] = fgettext("Update")
formspec[#formspec + 1] = "]"
formspec[#formspec + 1] = "button[5,0;2,1;uninstall;"
formspec[#formspec + 1] = fgettext("Uninstall")
formspec[#formspec + 1] = "]"
else
formspec[#formspec + 1] = "button[7,0;2,1;uninstall;"
formspec[#formspec + 1] = fgettext("Uninstall")
formspec[#formspec + 1] = "]"
end
return table.concat(formspec, "")
end
function package_dialog.handle_submit(this, fields)
if fields.back then
this:delete()
return true
end
if fields.install then
start_install(this, package_dialog.package)
return true
end
if fields.uninstall then
local dlg_delmod = create_delete_content_dlg(package_dialog.package)
dlg_delmod:set_parent(this)
this:hide()
dlg_delmod:show()
return true
end
return false
end
function package_dialog.create(package)
package_dialog.package = package
return dialog_create("package_view",
package_dialog.get_formspec,
package_dialog.handle_submit,
nil)
end
function store.load()
local tmpdir = os.tempfolder()
local target = tmpdir .. DIR_DELIM .. "packages.json"
assert(core.create_dir(tmpdir))
local base_url = core.settings:get("contentdb_url")
local show_nonfree = core.settings:get_bool("show_nonfree_packages")
local version = core.get_version()
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()
core.get_max_supp_proto() .. "&engine_version=" .. version.string
for _, item in pairs(core.settings:get("contentdb_flag_blacklist"):split(",")) do
item = item:trim()
@ -285,31 +226,29 @@ function store.load()
end
end
core.download_file(url, target)
local timeout = tonumber(minetest.settings:get("curl_file_download_timeout"))
local response = http.fetch_sync({ url = url, timeout = timeout })
if not response.succeeded then
return
end
local file = io.open(target, "r")
if file then
store.packages_full = core.parse_json(file:read("*all")) or {}
file:close()
store.packages_full = core.parse_json(response.data) or {}
for _, package in pairs(store.packages_full) do
package.url = base_url .. "/packages/" ..
for _, package in pairs(store.packages_full) do
package.url = base_url .. "/packages/" ..
package.author .. "/" .. package.name ..
"/releases/" .. package.release .. "/download/"
local name_len = #package.name
if package.type == "game" and name_len > 5 and package.name:sub(name_len - 4) == "_game" then
package.id = package.author .. "/" .. package.name:sub(1, name_len - 5)
else
package.id = package.author .. "/" .. package.name
end
local name_len = #package.name
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)
else
package.id = package.author:lower() .. "/" .. package.name
end
store.packages = store.packages_full
store.loaded = true
end
core.delete_dir(tmpdir)
store.packages = store.packages_full
store.loaded = true
end
function store.update_paths()
@ -317,22 +256,22 @@ function store.update_paths()
pkgmgr.refresh_globals()
for _, mod in pairs(pkgmgr.global_mods:get_list()) do
if mod.author then
mod_hash[mod.author .. "/" .. mod.name] = mod
mod_hash[mod.author:lower() .. "/" .. mod.name] = mod
end
end
local game_hash = {}
pkgmgr.update_gamelist()
for _, game in pairs(pkgmgr.games) do
if game.author then
game_hash[game.author .. "/" .. game.id] = game
if game.author ~= "" then
game_hash[game.author:lower() .. "/" .. game.id] = game
end
end
local txp_hash = {}
for _, txp in pairs(pkgmgr.get_texture_packs()) do
if txp.author then
txp_hash[txp.author .. "/" .. txp.name] = txp
txp_hash[txp.author:lower() .. "/" .. txp.name] = txp
end
end
@ -366,7 +305,7 @@ function store.filter_packages(query)
table.insert(keywords, word)
end
local function matches_keywords(package, keywords)
local function matches_keywords(package)
for k = 1, #keywords do
local keyword = keywords[k]
@ -383,7 +322,7 @@ function store.filter_packages(query)
store.packages = {}
for _, package in pairs(store.packages_full) do
if (query == "" or matches_keywords(package, keywords)) and
if (query == "" or matches_keywords(package)) and
(filter_type == 1 or package.type == filter_types_type[filter_type]) then
store.packages[#store.packages + 1] = package
end
@ -399,37 +338,70 @@ function store.get_formspec(dlgdata)
cur_page = 1
end
local W = 15.75
local H = 9.5
local formspec
if #store.packages_full > 0 then
formspec = {
"size[12,7;true]",
"formspec_version[3]",
"size[15.75,9.5]",
"position[0.5,0.55]",
"field[0.2,0.1;7.8,1;search_string;;",
core.formspec_escape(search_string), "]",
"style[status;border=false]",
"container[0.375,0.375]",
"field[0,0;7.225,0.8;search_string;;", core.formspec_escape(search_string), "]",
"field_close_on_enter[search_string;false]",
"button[7.7,-0.2;2,1;search;",
fgettext("Search"), "]",
"dropdown[9.7,-0.1;2.4;type;",
table.concat(filter_types_titles, ","),
";", filter_type, "]",
-- "textlist[0,1;2.4,5.6;a;",
-- table.concat(taglist, ","), "]",
"button[7.225,0;2,0.8;search;", fgettext("Search"), "]",
"dropdown[9.6,0;2.4,0.8;type;", table.concat(filter_types_titles, ","), ";", filter_type, "]",
"container_end[]",
-- Page nav buttons
"container[0,",
num_per_page + 1.5, "]",
"button[-0.1,0;3,1;back;",
fgettext("Back to Main Menu"), "]",
"button[7.1,0;1,1;pstart;<<]",
"button[8.1,0;1,1;pback;<]",
"label[9.2,0.2;",
tonumber(cur_page), " / ",
tonumber(dlgdata.pagemax), "]",
"button[10.1,0;1,1;pnext;>]",
"button[11.1,0;1,1;pend;>>]",
"container[0,", H - 0.8 - 0.375, "]",
"button[0.375,0;4,0.8;back;", fgettext("Back to Main Menu"), "]",
"container[", W - 0.375 - 0.8*4 - 2, ",0]",
"image_button[0,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "start_icon.png;pstart;]",
"image_button[0.8,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "prev_icon.png;pback;]",
"style[pagenum;border=false]",
"button[1.6,0;2,0.8;pagenum;", tonumber(cur_page), " / ", tonumber(dlgdata.pagemax), "]",
"image_button[3.6,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "next_icon.png;pnext;]",
"image_button[4.4,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "end_icon.png;pend;]",
"container_end[]",
"container_end[]",
}
if number_downloading > 0 then
formspec[#formspec + 1] = "button[12.75,0.375;2.625,0.8;status;"
if #download_queue > 0 then
formspec[#formspec + 1] = fgettext("$1 downloading,\n$2 queued", number_downloading, #download_queue)
else
formspec[#formspec + 1] = fgettext("$1 downloading...", number_downloading)
end
formspec[#formspec + 1] = "]"
else
local num_avail_updates = 0
for i=1, #store.packages_full do
local package = store.packages_full[i]
if package.path and package.installed_release < package.release and
not (package.downloading or package.queued) then
num_avail_updates = num_avail_updates + 1
end
end
if num_avail_updates == 0 then
formspec[#formspec + 1] = "button[12.75,0.375;2.625,0.8;status;"
formspec[#formspec + 1] = fgettext("No updates")
formspec[#formspec + 1] = "]"
else
formspec[#formspec + 1] = "button[12.75,0.375;2.625,0.8;update_all;"
formspec[#formspec + 1] = fgettext("Update All [$1]", num_avail_updates)
formspec[#formspec + 1] = "]"
end
end
if #store.packages == 0 then
formspec[#formspec + 1] = "label[4,3;"
formspec[#formspec + 1] = fgettext("No results")
@ -437,73 +409,86 @@ function store.get_formspec(dlgdata)
end
else
formspec = {
"size[12,7;true]",
"size[12,7]",
"position[0.5,0.55]",
"label[4,3;", fgettext("No packages could be retrieved"), "]",
"button[-0.1,",
num_per_page + 1.5,
";3,1;back;",
fgettext("Back to Main Menu"), "]",
"container[0,", H - 0.8 - 0.375, "]",
"button[0,0;4,0.8;back;", fgettext("Back to Main Menu"), "]",
"container_end[]",
}
end
local start_idx = (cur_page - 1) * num_per_page + 1
for i=start_idx, math.min(#store.packages, start_idx+num_per_page-1) do
local package = store.packages[i]
formspec[#formspec + 1] = "container[0.5,"
formspec[#formspec + 1] = (i - start_idx) * 1.1 + 1
formspec[#formspec + 1] = "container[0.375,"
formspec[#formspec + 1] = (i - start_idx) * 1.375 + (2*0.375 + 0.8)
formspec[#formspec + 1] = "]"
-- image
formspec[#formspec + 1] = "image[-0.4,0;1.5,1;"
formspec[#formspec + 1] = "image[0,0;1.5,1;"
formspec[#formspec + 1] = core.formspec_escape(get_screenshot(package))
formspec[#formspec + 1] = "]"
-- title
formspec[#formspec + 1] = "label[1,-0.1;"
formspec[#formspec + 1] = "label[1.875,0.1;"
formspec[#formspec + 1] = core.formspec_escape(
minetest.colorize(mt_color_green, package.title) ..
minetest.colorize("#BFBFBF", " by " .. package.author))
formspec[#formspec + 1] = "]"
-- description
if package.path and package.installed_release < package.release then
formspec[#formspec + 1] = "textarea[1.25,0.3;7.5,1;;;"
else
formspec[#formspec + 1] = "textarea[1.25,0.3;9,1;;;"
end
formspec[#formspec + 1] = core.formspec_escape(package.short_description)
formspec[#formspec + 1] = "]"
-- buttons
if not package.path then
formspec[#formspec + 1] = "button[9.9,0;1.5,1;install_"
local description_width = W - 0.375*5 - 1 - 2*1.5
formspec[#formspec + 1] = "container["
formspec[#formspec + 1] = W - 0.375*2
formspec[#formspec + 1] = ",0.1]"
if package.downloading then
formspec[#formspec + 1] = "button[-3.5,0;2,0.8;status;"
formspec[#formspec + 1] = fgettext("Downloading...")
formspec[#formspec + 1] = "]"
elseif package.queued then
formspec[#formspec + 1] = "button[-3.5,0;2,0.8;status;"
formspec[#formspec + 1] = fgettext("Queued")
formspec[#formspec + 1] = "]"
elseif not package.path then
formspec[#formspec + 1] = "button[-3,0;1.5,0.8;install_"
formspec[#formspec + 1] = tostring(i)
formspec[#formspec + 1] = ";"
formspec[#formspec + 1] = fgettext("Install")
formspec[#formspec + 1] = "]"
else
if package.installed_release < package.release then
description_width = description_width - 1.5
-- The install_ action also handles updating
formspec[#formspec + 1] = "button[8.4,0;1.5,1;install_"
formspec[#formspec + 1] = "button[-4.5,0;1.5,0.8;install_"
formspec[#formspec + 1] = tostring(i)
formspec[#formspec + 1] = ";"
formspec[#formspec + 1] = fgettext("Update")
formspec[#formspec + 1] = "]"
end
formspec[#formspec + 1] = "button[9.9,0;1.5,1;uninstall_"
formspec[#formspec + 1] = "button[-3,0;1.5,0.8;uninstall_"
formspec[#formspec + 1] = tostring(i)
formspec[#formspec + 1] = ";"
formspec[#formspec + 1] = fgettext("Uninstall")
formspec[#formspec + 1] = "]"
end
--formspec[#formspec + 1] = "button[9.9,0;1.5,1;view_"
--formspec[#formspec + 1] = tostring(i)
--formspec[#formspec + 1] = ";"
--formspec[#formspec + 1] = fgettext("View")
--formspec[#formspec + 1] = "]"
formspec[#formspec + 1] = "button[-1.5,0;1.5,0.8;view_"
formspec[#formspec + 1] = tostring(i)
formspec[#formspec + 1] = ";"
formspec[#formspec + 1] = fgettext("View")
formspec[#formspec + 1] = "]"
formspec[#formspec + 1] = "container_end[]"
-- description
formspec[#formspec + 1] = "textarea[1.855,0.3;"
formspec[#formspec + 1] = tostring(description_width)
formspec[#formspec + 1] = ",0.8;;;"
formspec[#formspec + 1] = core.formspec_escape(package.short_description)
formspec[#formspec + 1] = "]"
formspec[#formspec + 1] = "container_end[]"
end
@ -560,6 +545,17 @@ function store.handle_submit(this, fields)
end
end
if fields.update_all then
for i=1, #store.packages_full do
local package = store.packages_full[i]
if package.path and package.installed_release < package.release and
not (package.downloading or package.queued) then
queue_download(package)
end
end
return true
end
local start_idx = (cur_page - 1) * num_per_page + 1
assert(start_idx ~= nil)
for i=start_idx, math.min(#store.packages, start_idx+num_per_page-1) do
@ -567,7 +563,7 @@ function store.handle_submit(this, fields)
assert(package)
if fields["install_" .. i] then
start_install(this, package)
queue_download(package)
return true
end
@ -580,10 +576,10 @@ function store.handle_submit(this, fields)
end
if fields["view_" .. i] then
local dlg = package_dialog.create(package)
dlg:set_parent(this)
this:hide()
dlg:show()
local url = ("%s/packages/%s/%s?protocol_version=%d"):format(
core.settings:get("contentdb_url"),
package.author, package.name, core.get_max_supp_proto())
core.open_url(url)
return true
end
end
@ -598,6 +594,17 @@ function create_store_dlg(type)
search_string = ""
cur_page = 1
if type then
-- table.indexof does not work on tables that contain `nil`
for i, v in pairs(filter_types_type) do
if v == type then
filter_type = i
break
end
end
end
store.filter_packages(search_string)
return dialog_create("store",

View File

@ -17,32 +17,145 @@
local worldname = ""
local function table_to_flags(ftable)
-- Convert e.g. { jungles = true, caves = false } to "jungles,nocaves"
local str = {}
for flag, is_set in pairs(ftable) do
str[#str + 1] = is_set and flag or ("no" .. flag)
end
return table.concat(str, ",")
end
-- Same as check_flag but returns a string
local function strflag(flags, flag)
return (flags[flag] == true) and "true" or "false"
end
local cb_caverns = { "caverns", fgettext("Caverns"), "caverns",
fgettext("Very large caverns deep in the underground") }
local tt_sea_rivers = fgettext("Sea level rivers")
local flag_checkboxes = {
v5 = {
cb_caverns,
},
v7 = {
cb_caverns,
{ "ridges", fgettext("Rivers"), "ridges", tt_sea_rivers },
{ "mountains", fgettext("Mountains"), "mountains" },
{ "floatlands", fgettext("Floatlands (experimental)"), "floatlands",
fgettext("Floating landmasses in the sky") },
},
carpathian = {
cb_caverns,
{ "rivers", fgettext("Rivers"), "rivers", tt_sea_rivers },
},
valleys = {
{ "altitude-chill", fgettext("Altitude chill"), "altitude_chill",
fgettext("Reduces heat with altitude") },
{ "altitude-dry", fgettext("Altitude dry"), "altitude_dry",
fgettext("Reduces humidity with altitude") },
{ "humid-rivers", fgettext("Humid rivers"), "humid_rivers",
fgettext("Increases humidity around rivers") },
{ "vary-river-depth", fgettext("Vary river depth"), "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" },
},
fractal = {
{ "terrain", fgettext("Additional terrain"), "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") },
-- Biome settings are in mgv6_biomes below
},
}
local mgv6_biomes = {
{
fgettext("Temperate, Desert, Jungle, Tundra, Taiga"),
{jungles = true, snowbiomes = true}
},
{
fgettext("Temperate, Desert, Jungle"),
{jungles = true, snowbiomes = false}
},
{
fgettext("Temperate, Desert"),
{jungles = false, snowbiomes = false}
},
}
local function create_world_formspec(dialogdata)
-- Error out when no games found
if #pkgmgr.games == 0 then
return "size[12.25,3,true]" ..
"box[0,0;12,2;#ff8800]" ..
"textarea[0.3,0;11.7,2;;;"..
fgettext("You have no games installed.") .. "\n" ..
fgettext("Download one from minetest.net") .. "]" ..
"button[4.75,2.5;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]"
end
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 game, gameidx = nil , 0
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 gameidx = 0
if gameid ~= nil then
game, gameidx = pkgmgr.find_by_gameid(gameid)
local _
_, gameidx = pkgmgr.find_by_gameid(gameid)
if gameidx == nil then
gameidx = 0
end
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")
local allowed_mapgens = (gameconfig:get("allowed_mapgens") or ""):split()
for key, value in pairs(allowed_mapgens) do
allowed_mapgens[key] = value:trim()
end
local disallowed_mapgens = (gameconfig:get("disallowed_mapgens") or ""):split()
for key, value in pairs(disallowed_mapgens) do
disallowed_mapgens[key] = value:trim()
end
if #allowed_mapgens > 0 then
for i = #mapgens, 1, -1 do
if table.indexof(allowed_mapgens, mapgens[i]) == -1 then
table.remove(mapgens, i)
end
end
end
if disallowed_mapgens then
for i = #mapgens, 1, -1 do
if table.indexof(disallowed_mapgens, mapgens[i]) > 0 then
@ -50,49 +163,193 @@ local function create_world_formspec(dialogdata)
end
end
end
local ds = (gameconfig:get("disallowed_mapgen_settings") or ""):split()
for _, value in pairs(ds) do
disallowed_mapgen_settings[value:trim()] = true
end
end
local mglist = ""
local selindex = 1
local selindex
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 .. v .. ","
end
if not selindex then
selindex = 1
current_mg = first_mg
end
mglist = mglist:sub(1, -2)
current_seed = core.formspec_escape(current_seed)
local retval =
"size[11.5,6.5,true]" ..
"label[2,0;" .. fgettext("World name") .. "]"..
"field[4.5,0.4;6,0.5;te_world_name;;" .. minetest.formspec_escape(worldname) .. "]" ..
local mg_main_flags = function(mapgen, y)
if mapgen == "singlenode" then
return "", y
end
if disallowed_mapgen_settings["mg_flags"] then
return "", y
end
"label[2,1;" .. fgettext("Seed") .. "]"..
"field[4.5,1.4;6,0.5;te_seed;;".. current_seed .. "]" ..
local form = "checkbox[0," .. y .. ";flag_mg_caves;" ..
fgettext("Caves") .. ";"..strflag(flags.main, "caves").."]"
y = y + 0.5
"label[2,2;" .. fgettext("Mapgen") .. "]"..
"dropdown[4.2,2;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]" ..
form = form .. "checkbox[0,"..y..";flag_mg_dungeons;" ..
fgettext("Dungeons") .. ";"..strflag(flags.main, "dungeons").."]"
y = y + 0.5
"label[2,3;" .. fgettext("Game") .. "]"..
"textlist[4.2,3;5.8,2.3;games;" .. pkgmgr.gamelist() ..
";" .. gameidx .. ";true]" ..
local d_name = fgettext("Decorations")
local d_tt
if mapgen == "v6" then
d_tt = fgettext("Structures appearing on the terrain (no effect on trees and jungle grass created by v6)")
else
d_tt = fgettext("Structures appearing on the terrain, typically trees and plants")
end
form = form .. "checkbox[0,"..y..";flag_mg_decorations;" ..
d_name .. ";" ..
strflag(flags.main, "decorations").."]" ..
"tooltip[flag_mg_decorations;" ..
d_tt ..
"]"
y = y + 0.5
"button[3.25,6;2.5,0.5;world_create_confirm;" .. fgettext("Create") .. "]" ..
"button[5.75,6;2.5,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]"
if #pkgmgr.games == 0 then
retval = retval .. "box[2,4;8,1;#ff8800]label[2.25,4;" ..
fgettext("You have no games installed.") .. "]label[2.25,4.4;" ..
fgettext("Download one from minetest.net") .. "]"
elseif #pkgmgr.games == 1 and pkgmgr.games[1].id == "minimal" then
retval = retval .. "box[1.75,4;8.7,1;#ff8800]label[2,4;" ..
fgettext("Warning: The minimal development test is meant for developers.") .. "]label[2,4.4;" ..
fgettext("Download a game, such as Minetest Game, from minetest.net") .. "]"
form = form .. "tooltip[flag_mg_caves;" ..
fgettext("Network of tunnels and caves")
.. "]"
return form, y
end
local mg_specific_flags = function(mapgen, y)
if not flag_checkboxes[mapgen] then
return "", y
end
if disallowed_mapgen_settings["mg"..mapgen.."_spflags"] then
return "", y
end
local form = ""
for _,tab in pairs(flag_checkboxes[mapgen]) do
local id = "flag_mg"..mapgen.."_"..tab[1]
form = form .. ("checkbox[0,%f;%s;%s;%s]"):
format(y, id, tab[2], strflag(flags[mapgen], tab[3]))
if tab[4] then
form = form .. "tooltip["..id..";"..tab[4].."]"
end
y = y + 0.5
end
if mapgen ~= "v6" then
-- No special treatment
return form, y
end
-- Special treatment for v6 (add biome widgets)
-- Biome type (jungles, snowbiomes)
local biometype
if flags.v6.snowbiomes == true then
biometype = 1
elseif flags.v6.jungles == true then
biometype = 2
else
biometype = 3
end
y = y + 0.3
form = form .. "label[0,"..(y+0.1)..";" .. fgettext("Biomes") .. "]"
y = y + 0.6
form = form .. "dropdown[0,"..y..";6.3;mgv6_biomes;"
for b=1, #mgv6_biomes do
form = form .. mgv6_biomes[b][1]
if b < #mgv6_biomes then
form = form .. ","
end
end
form = form .. ";" .. biometype.. "]"
-- biomeblend
y = y + 0.55
form = form .. "checkbox[0,"..y..";flag_mgv6_biomeblend;" ..
fgettext("Biome blending") .. ";"..strflag(flags.v6, "biomeblend").."]" ..
"tooltip[flag_mgv6_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
local label_flags, label_spflags = "", ""
y = y + 0.3
str_flags, y = mg_main_flags(current_mg, y)
if str_flags ~= "" then
label_flags = "label[0,"..y_start..";" .. fgettext("Mapgen flags") .. "]"
y_start = y + 0.4
else
y_start = 0.0
end
y = y_start + 0.3
str_spflags = mg_specific_flags(current_mg, y)
if str_spflags ~= "" then
label_spflags = "label[0,"..y_start..";" .. fgettext("Mapgen-specific flags") .. "]"
end
-- Warning if only devtest is installed
local devtest_only = ""
local gamelist_height = 2.3
if #pkgmgr.games == 1 and pkgmgr.games[1].id == "devtest" then
devtest_only = "box[0,0;5.8,1.7;#ff8800]" ..
"textarea[0.3,0;6,1.8;;;"..
fgettext("Warning: The Development Test is meant for developers.") .. "\n" ..
fgettext("Download a game, such as Minetest Game, from minetest.net") .. "]"
gamelist_height = 0.5
end
local retval =
"size[12.25,7,true]" ..
-- Left side
"container[0,0]"..
"field[0.3,0.6;6,0.5;te_world_name;" ..
fgettext("World name") ..
";" .. core.formspec_escape(worldname) .. "]" ..
"field[0.3,1.7;6,0.5;te_seed;" ..
fgettext("Seed") ..
";".. current_seed .. "]" ..
"label[0,2;" .. fgettext("Mapgen") .. "]"..
"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[]" ..
"container_end[]" ..
-- Right side
"container[6.2,0]"..
label_flags .. str_flags ..
label_spflags .. str_spflags ..
"container_end[]"..
-- Menu buttons
"button[3.25,6.5;3,0.5;world_create_confirm;" .. fgettext("Create") .. "]" ..
"button[6.25,6.5;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]"
return retval
end
@ -111,10 +368,10 @@ local function create_world_buttonhandler(this, fields)
local random_world_name = "Unnamed" .. random_number
worldname = random_world_name
end
local message = nil
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)
@ -149,11 +406,53 @@ local function create_world_buttonhandler(this, fields)
return true
end
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)
return true
end
end
if fields["world_create_cancel"] then
this:delete()
return true
end
if fields["mgv6_biomes"] then
local entry = minetest.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")
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"])
return true
end
return false
end
@ -165,6 +464,6 @@ function create_create_world_dlg(update_worldlistfilter)
create_world_buttonhandler,
nil)
retval.update_worldlist_filter = update_worldlistfilter
return retval
end

View File

@ -22,6 +22,7 @@ local function delete_content_formspec(dialogdata)
"size[11.5,4.5,true]" ..
"label[2,2;" ..
fgettext("Are you sure you want to delete \"$1\"?", dialogdata.content.name) .. "]"..
"style[dlg_delete_content_confirm;bgcolor=red]" ..
"button[3.25,3.5;2.5,0.5;dlg_delete_content_confirm;" .. fgettext("Delete") .. "]" ..
"button[5.75,3.5;2.5,0.5;dlg_delete_content_cancel;" .. fgettext("Cancel") .. "]"

View File

@ -21,6 +21,7 @@ local function delete_world_formspec(dialogdata)
"size[10,2.5,true]" ..
"label[0.5,0.5;" ..
fgettext("Delete World \"$1\"?", dialogdata.delete_name) .. "]" ..
"style[world_delete_confirm;bgcolor=red]" ..
"button[0.5,1.5;2.5,0.5;world_delete_confirm;" .. fgettext("Delete") .. "]" ..
"button[7.0,1.5;2.5,0.5;world_delete_cancel;" .. fgettext("Cancel") .. "]"
return retval

View File

@ -148,9 +148,9 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se
local values = {}
local ti = 1
local index = 1
for line in default:gmatch("[+-]?[%d.-e]+") do -- All numeric characters
index = default:find("[+-]?[%d.-e]+", index) + line:len()
table.insert(values, line)
for match in default:gmatch("[+-]?[%d.-e]+") do -- All numeric characters
index = default:find("[+-]?[%d.-e]+", index) + match:len()
table.insert(values, match)
ti = ti + 1
if ti > 9 then
break
@ -322,18 +322,21 @@ end
-- read_all: whether to ignore certain setting types for GUI or not
-- parse_mods: whether to parse settingtypes.txt in mods and games
local function parse_config_file(read_all, parse_mods)
local builtin_path = core.get_builtin_path() .. FILENAME
local file = io.open(builtin_path, "r")
local settings = {}
if not file then
core.log("error", "Can't load " .. FILENAME)
return settings
do
local builtin_path = core.get_builtin_path() .. FILENAME
local file = io.open(builtin_path, "r")
if not file then
core.log("error", "Can't load " .. FILENAME)
return settings
end
parse_single_file(file, builtin_path, read_all, settings, 0, true)
file:close()
end
parse_single_file(file, builtin_path, read_all, settings, 0, true)
file:close()
if parse_mods then
-- Parse games
local games_category_initialized = false
@ -344,7 +347,7 @@ local function parse_config_file(read_all, parse_mods)
local file = io.open(path, "r")
if file then
if not games_category_initialized then
local translation = fgettext_ne("Games"), -- not used, but needed for xgettext
fgettext_ne("Games") -- not used, but needed for xgettext
table.insert(settings, {
name = "Games",
level = 0,
@ -377,7 +380,7 @@ local function parse_config_file(read_all, parse_mods)
local file = io.open(path, "r")
if file then
if not mods_category_initialized then
local translation = fgettext_ne("Mods"), -- not used, but needed for xgettext
fgettext_ne("Mods") -- not used, but needed for xgettext
table.insert(settings, {
name = "Mods",
level = 0,
@ -636,12 +639,23 @@ local function create_change_setting_formspec(dialogdata)
-- Flags
formspec = table.concat(fields)
.. "checkbox[0.5," .. height - 0.6 .. ";cb_defaults;"
--[[~ "defaults" is a noise parameter flag.
It describes the default processing options
for noise settings in main menu -> "All Settings". ]]
.. fgettext("defaults") .. ";" -- defaults
.. tostring(flags["defaults"] == true) .. "]" -- to get false if nil
.. "checkbox[5," .. height - 0.6 .. ";cb_eased;"
--[[~ "eased" is a noise parameter flag.
It is used to make the map smoother and
can be enabled in noise settings in
main menu -> "All Settings". ]]
.. fgettext("eased") .. ";" -- eased
.. tostring(flags["eased"] == true) .. "]"
.. "checkbox[5," .. height - 0.15 .. ";cb_absvalue;"
--[[~ "absvalue" is a noise parameter flag.
It is short for "absolute value".
It can be enabled in noise settings in
main menu -> "All Settings". ]]
.. fgettext("absvalue") .. ";" -- absvalue
.. tostring(flags["absvalue"] == true) .. "]"
height = height + 1
@ -667,34 +681,42 @@ local function create_change_setting_formspec(dialogdata)
height = height + 1.1
elseif setting.type == "flags" then
local enabled_flags = flags_to_table(get_current_value(setting))
local current_flags = flags_to_table(get_current_value(setting))
local flags = {}
for _, name in ipairs(enabled_flags) do
for _, name in ipairs(current_flags) do
-- Index by name, to avoid iterating over all enabled_flags for every possible flag.
flags[name] = true
if name:sub(1, 2) == "no" then
flags[name:sub(3)] = false
else
flags[name] = true
end
end
local flags_count = #setting.possible
local max_height = flags_count / 4
local flags_count = #setting.possible / 2
local max_height = math.ceil(flags_count / 2) / 2
-- More space for flags
description_height = description_height - 1
height = height - 1
local fields = {} -- To build formspec
for i, name in ipairs(setting.possible) do
local x = 0.5
local y = height + i / 2 - 0.75
if i - 1 >= flags_count / 2 then -- 2nd column
x = 5
y = y - max_height
end
local checkbox_name = "cb_" .. name
local is_enabled = flags[name] == true -- to get false if nil
checkboxes[checkbox_name] = is_enabled
local j = 1
for _, name in ipairs(setting.possible) do
if name:sub(1, 2) ~= "no" then
local x = 0.5
local y = height + j / 2 - 0.75
if j - 1 >= flags_count / 2 then -- 2nd column
x = 5
y = y - max_height
end
j = j + 1;
local checkbox_name = "cb_" .. name
local is_enabled = flags[name] == true -- to get false if nil
checkboxes[checkbox_name] = is_enabled
fields[#fields + 1] = ("checkbox[%f,%f;%s;%s;%s]"):format(
x, y, checkbox_name, name, tostring(is_enabled)
)
fields[#fields + 1] = ("checkbox[%f,%f;%s;%s;%s]"):format(
x, y, checkbox_name, name, tostring(is_enabled)
)
end
end
formspec = table.concat(fields)
height = height + max_height + 0.25
@ -753,7 +775,7 @@ local function create_change_setting_formspec(dialogdata)
" (" .. setting.name .. ")"
end
local comment_text = ""
local comment_text
if setting.comment == "" then
comment_text = fgettext_ne("(No description of setting given)")
else
@ -830,8 +852,12 @@ local function handle_change_setting_buttons(this, fields)
elseif setting.type == "flags" then
local values = {}
for _, name in ipairs(setting.possible) do
if checkboxes["cb_" .. name] then
table.insert(values, name)
if name:sub(1, 2) ~= "no" then
if checkboxes["cb_" .. name] then
table.insert(values, name)
else
table.insert(values, "no" .. name)
end
end
end
@ -918,7 +944,7 @@ local function handle_change_setting_buttons(this, fields)
return false
end
local function create_settings_formspec(tabview, name, tabdata)
local function create_settings_formspec(tabview, _, tabdata)
local formspec = "size[12,5.4;true]" ..
"tablecolumns[color;tree;text,width=28;text]" ..
"tableoptions[background=#00000000;border=false]" ..
@ -950,7 +976,7 @@ local function create_settings_formspec(tabview, name, tabdata)
formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
.. value .. ","
elseif entry.type == "key" then
elseif entry.type == "key" then --luacheck: ignore
-- ignore key settings, since we have a special dialog for them
elseif entry.type == "noise_params_2d" or entry.type == "noise_params_3d" then
@ -1029,8 +1055,8 @@ local function handle_settings_buttons(this, fields, tabname, tabdata)
if fields["btn_edit"] or list_enter then
local setting = settings[selected_setting]
if setting and setting.type ~= "category" then
local edit_dialog = dialog_create("change_setting", create_change_setting_formspec,
handle_change_setting_buttons)
local edit_dialog = dialog_create("change_setting",
create_change_setting_formspec, handle_change_setting_buttons)
edit_dialog:set_parent(this)
this:hide()
edit_dialog:show()
@ -1076,4 +1102,5 @@ end
-- For RUN_IN_PLACE the generated files may appear in the 'bin' folder.
-- See comment and alternative line at the end of 'generate_from_settingtypes.lua'.
--assert(loadfile(core.get_builtin_path().."mainmenu"..DIR_DELIM.."generate_from_settingtypes.lua"))(parse_config_file(true, false))
--assert(loadfile(core.get_builtin_path().."mainmenu"..DIR_DELIM..
-- "generate_from_settingtypes.lua"))(parse_config_file(true, false))

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