subrepo:
  subdir:   "minetest"
  merged:   "f5d4494"
upstream:
  origin:   "https://github.com/minetest/minetest.git"
  branch:   "master"
  commit:   "f5d4494"
git-subrepo:
  version:  "0.3.1"
  origin:   "https://github.com/ingydotnet/git-subrepo"
  commit:   "a7ee886"
master
dicebox 2017-02-10 11:09:47 +01:00
parent 85fb6dad0e
commit 38e0e86b19
949 changed files with 371560 additions and 0 deletions

2
minetest/.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
*.cpp diff=cpp
*.h diff=cpp

89
minetest/.gitignore vendored Normal file
View File

@ -0,0 +1,89 @@
## Editors and Development environments
*~
*.swp
*.bak*
*.orig
# Vim
*.vim
# Kate
.*.kate-swp
.swp.*
# KDevelop4
.kdev4/
*.kdev4
# Eclipse (CDT and LDT)
.project
.cproject
.settings/
.buildpath
.metadata
# GNU Global
tags
!tags/
gtags.files
.idea/*
## Files related to minetest development cycle
/*.patch
# GNU Patch reject file
*.rej
## Non-static Minetest directories or symlinks to these
/bin/
/games/*
!/games/minimal/
/cache
/textures/*
!/textures/base/
/screenshots
/sounds
/mods/*
!/mods/minetest/
/mods/minetest/*
!/mods/minetest/mods_here.txt
/worlds
/world/
## Configuration/log files
minetest.conf
debug.txt
## Doxygen files
doc/Doxyfile
doc/html/
doc/doxygen_*
## Build files
CMakeFiles
Makefile
!build/android/Makefile
cmake_install.cmake
CMakeCache.txt
CPackConfig.cmake
CPackSourceConfig.cmake
src/android_version.h
src/android_version_githash.h
src/cmake_config.h
src/cmake_config_githash.h
src/lua/build/
locale/
.directory
*.cbp
*.layout
*.o
*.a
*.ninja
.ninja*
*.gch
## Android build files
build/android/src/main/assets
build/android/build
build/android/deps
build/android/libs
build/android/jni/src
build/android/src/main/jniLibs
build/android/obj
build/android/local.properties
build/android/.gradle
timestamp

11
minetest/.gitrepo Normal file
View File

@ -0,0 +1,11 @@
; DO NOT EDIT (unless you know what you are doing)
;
; This subdirectory is a git "subrepo", and this file is maintained by the
; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
;
[subrepo]
remote = https://github.com/minetest/minetest.git
branch = master
commit = f5d4494a51a4f38553c10efd51a5c423cd357c87
parent = 85fb6dad0e36bb863241dd43254550292205e367
cmdver = 0.3.1

33
minetest/.mailmap Normal file
View File

@ -0,0 +1,33 @@
0gb.us <0gb.us@0gb.us> <us_0gb@laptop-0gb-us.0gb.us>
Calinou <calinou9999@gmail.com> <calinou9999spam@gmail.com>
Perttu Ahola <celeron55@gmail.com> celeron55 <celeron55@gmail.com>
Perttu Ahola <celeron55@gmail.com> celeron55 <celeron55@armada.(none)>
Craig Robbins <kde.psych@gmail.com> <crobbins@localhost.localdomain>
Diego Martínez <kaeza@users.sf.net> <lkaezadl3@gmail.com>
Ilya Zhuravlev <zhuravlevilya@ya.ru> <whatever@xyz.is>
kwolekr <kwolekr@minetest.net> <mirrorisim@gmail.com>
PilzAdam <pilzadam@minetest.net> PilzAdam <adam-k@outlook.com>
PilzAdam <pilzadam@minetest.net> Pilz Adam <PilzAdam@gmx.de>
PilzAdam <pilzadam@minetest.net> PilzAdam <PilzAdam@gmx.de>
proller <proller@github.com> <proler@github.com>
proller <proller@github.com> <proler@gmail.com>
RealBadAngel <maciej.kasatkin@o2.pl> <mk@realbadangel.pl>
RealBadAngel <maciej.kasatkin@o2.pl> <maciej.kasatkin@yahoo.com>
Selat <LongExampleTestName@gmail.com> <LongExampletestName@gmail.com>
ShadowNinja <shadowninja@minetest.net> ShadowNinja <noreply@gmail.com>
Shen Zheyu <arsdragonfly@gmail.com> arsdragonfly <arsdragonfly@gmail.com>
Pavel Elagin <elagin.pasha@gmail.com> elagin <elagin.pasha@gmail.com>
Esteban I. Ruiz Moreno <exio4.com@gmail.com> Esteban I. RM <exio4.com@gmail.com>
manuel duarte <ffrogger0@yahoo.com> manuel joaquim <ffrogger0@yahoo.com>
manuel duarte <ffrogger0@yahoo.com> sweetbomber <ffrogger _zero_ at yahoo dot com>
Diego Martínez <kaeza@users.sf.net> kaeza <kaeza@users.sf.net>
Diego Martínez <kaeza@users.sf.net> Diego Martinez <kaeza@users.sf.net>
Lord James <neftali_dtctv@hotmail.com> Lord89James <neftali_dtctv@hotmail.com>
BlockMen <nmuelll@web.de> Block Men <nmuelll@web.de>
sfan5 <sfan5@live.de> Sfan5 <sfan5@live.de>
DannyDark <the_skeleton_of_a_child@yahoo.co.uk> dannydark <the_skeleton_of_a_child@yahoo.co.uk>
Ilya Pavlov <TTChangeTheWorld@gmail.com> Ilya <TTChangeTheWorld@gmail.com>
Ilya Zhuravlev <zhuravlevilya@ya.ru> xyzz <zhuravlevilya@ya.ru>
sapier <Sapier at GMX dot net> sapier <sapier AT gmx DOT net>
sapier <Sapier at GMX dot net> sapier <sapier at gmx dot net>

31
minetest/.travis.yml Normal file
View File

@ -0,0 +1,31 @@
language: cpp
before_install: ./util/travis/before_install.sh
script: ./util/travis/script.sh
sudo: required
notifications:
email: false
matrix:
fast_finish: true
include:
- env: PLATFORM=Win32
compiler: gcc
os: linux
- env: PLATFORM=Win64
compiler: gcc
os: linux
- env: PLATFORM=Unix COMPILER=clang
compiler: clang
os: osx
- env: PLATFORM=Unix COMPILER=g++
compiler: gcc
os: linux
- env: PLATFORM=Unix COMPILER=clang
compiler: clang
os: linux
- env: PLATFORM=Unix COMPILER=g++-6
compiler: gcc
os: linux
addons:
apt:
sources: &sources
- ubuntu-toolchain-r-test

237
minetest/CMakeLists.txt Normal file
View File

@ -0,0 +1,237 @@
cmake_minimum_required(VERSION 2.6)
if(${CMAKE_VERSION} STREQUAL "2.8.2")
# Bug http://vtk.org/Bug/view.php?id=11020
message(WARNING "CMake/CPack version 2.8.2 will not create working .deb packages!")
endif()
# This can be read from ${PROJECT_NAME} after project() is called
project(minetest)
set(PROJECT_NAME_CAPITALIZED "Minetest")
# Also remember to set PROTOCOL_VERSION in network/networkprotocol.h when releasing
set(VERSION_MAJOR 0)
set(VERSION_MINOR 4)
set(VERSION_PATCH 15)
set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")
# Change to false for releases
set(DEVELOPMENT_BUILD TRUE)
set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
if(VERSION_EXTRA)
set(VERSION_STRING ${VERSION_STRING}-${VERSION_EXTRA})
elseif(DEVELOPMENT_BUILD)
set(VERSION_STRING "${VERSION_STRING}-dev")
endif()
message(STATUS "*** Will build version ${VERSION_STRING} ***")
# Configuration options
set(DEFAULT_RUN_IN_PLACE FALSE)
if(WIN32)
set(DEFAULT_RUN_IN_PLACE TRUE)
endif()
set(RUN_IN_PLACE ${DEFAULT_RUN_IN_PLACE} CACHE BOOL
"Run directly in source directory structure")
set(BUILD_CLIENT TRUE CACHE BOOL "Build client")
set(BUILD_SERVER FALSE CACHE BOOL "Build server")
set(WARN_ALL TRUE CACHE BOOL "Enable -Wall for Release build")
if(NOT CMAKE_BUILD_TYPE)
# Default to release
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type: Debug or Release" FORCE)
endif()
# Included stuff
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
# This is done here so that relative search paths are more reasonable
find_package(Irrlicht)
# Installation
if(WIN32)
set(SHAREDIR ".")
set(BINDIR "bin")
set(DOCDIR "doc")
set(EXAMPLE_CONF_DIR ".")
set(LOCALEDIR "locale")
elseif(APPLE)
set(BUNDLE_NAME ${PROJECT_NAME}.app)
set(BUNDLE_PATH "${BUNDLE_NAME}")
set(BINDIR ${BUNDLE_NAME}/Contents/MacOS)
set(SHAREDIR ${BUNDLE_NAME}/Contents/Resources)
set(DOCDIR "${SHAREDIR}/${PROJECT_NAME}")
set(EXAMPLE_CONF_DIR ${DOCDIR})
set(LOCALEDIR "${SHAREDIR}/locale")
elseif(UNIX) # Linux, BSD etc
if(RUN_IN_PLACE)
set(SHAREDIR ".")
set(BINDIR "bin")
set(DOCDIR "doc")
set(EXAMPLE_CONF_DIR ".")
set(MANDIR "unix/man")
set(XDG_APPS_DIR "unix/applications")
set(APPDATADIR "unix/appdata")
set(ICONDIR "unix/icons")
set(LOCALEDIR "locale")
else()
set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}")
set(BINDIR "${CMAKE_INSTALL_PREFIX}/bin")
set(DOCDIR "${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME}")
set(MANDIR "${CMAKE_INSTALL_PREFIX}/share/man")
set(EXAMPLE_CONF_DIR ${DOCDIR})
set(XDG_APPS_DIR "${CMAKE_INSTALL_PREFIX}/share/applications")
set(APPDATADIR "${CMAKE_INSTALL_PREFIX}/share/appdata")
set(ICONDIR "${CMAKE_INSTALL_PREFIX}/share/icons")
set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}/locale")
endif()
endif()
set(CUSTOM_SHAREDIR "" CACHE STRING "Directory to install data files into")
if(NOT CUSTOM_SHAREDIR STREQUAL "")
set(SHAREDIR "${CUSTOM_SHAREDIR}")
message(STATUS "Using SHAREDIR=${SHAREDIR}")
endif()
set(CUSTOM_BINDIR "" CACHE STRING "Directory to install binaries into")
if(NOT CUSTOM_BINDIR STREQUAL "")
set(BINDIR "${CUSTOM_BINDIR}")
message(STATUS "Using BINDIR=${BINDIR}")
endif()
set(CUSTOM_DOCDIR "" CACHE STRING "Directory to install documentation into")
if(NOT CUSTOM_DOCDIR STREQUAL "")
set(DOCDIR "${CUSTOM_DOCDIR}")
if(NOT RUN_IN_PLACE)
set(EXAMPLE_CONF_DIR ${DOCDIR})
endif()
message(STATUS "Using DOCDIR=${DOCDIR}")
endif()
set(CUSTOM_MANDIR "" CACHE STRING "Directory to install manpages into")
if(NOT CUSTOM_MANDIR STREQUAL "")
set(MANDIR "${CUSTOM_MANDIR}")
message(STATUS "Using MANDIR=${MANDIR}")
endif()
set(CUSTOM_EXAMPLE_CONF_DIR "" CACHE STRING "Directory to install example config file into")
if(NOT CUSTOM_EXAMPLE_CONF_DIR STREQUAL "")
set(EXAMPLE_CONF_DIR "${CUSTOM_EXAMPLE_CONF_DIR}")
message(STATUS "Using EXAMPLE_CONF_DIR=${EXAMPLE_CONF_DIR}")
endif()
set(CUSTOM_XDG_APPS_DIR "" CACHE STRING "Directory to install .desktop files into")
if(NOT CUSTOM_XDG_APPS_DIR STREQUAL "")
set(XDG_APPS_DIR "${CUSTOM_XDG_APPS_DIR}")
message(STATUS "Using XDG_APPS_DIR=${XDG_APPS_DIR}")
endif()
set(CUSTOM_ICONDIR "" CACHE STRING "Directory to install icons into")
if(NOT CUSTOM_ICONDIR STREQUAL "")
set(ICONDIR "${CUSTOM_ICONDIR}")
message(STATUS "Using ICONDIR=${ICONDIR}")
endif()
set(CUSTOM_LOCALEDIR "" CACHE STRING "Directory to install l10n files into")
if(NOT CUSTOM_LOCALEDIR STREQUAL "")
set(LOCALEDIR "${CUSTOM_LOCALEDIR}")
message(STATUS "Using LOCALEDIR=${LOCALEDIR}")
endif()
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/builtin" DESTINATION "${SHAREDIR}")
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/client" DESTINATION "${SHAREDIR}")
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/games" DESTINATION "${SHAREDIR}" PATTERN ".git*" EXCLUDE)
if(BUILD_CLIENT)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/textures/base/pack" DESTINATION "${SHAREDIR}/textures/base")
endif()
if(RUN_IN_PLACE)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/mods/mods_here.txt" DESTINATION "${SHAREDIR}/mods")
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/textures/texture_packs_here.txt" DESTINATION "${SHAREDIR}/textures")
endif()
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/fonts" DESTINATION "${SHAREDIR}")
install(FILES "README.txt" DESTINATION "${DOCDIR}")
install(FILES "doc/lua_api.txt" DESTINATION "${DOCDIR}")
install(FILES "doc/menu_lua_api.txt" DESTINATION "${DOCDIR}")
install(FILES "doc/texture_packs.txt" DESTINATION "${DOCDIR}")
install(FILES "doc/world_format.txt" DESTINATION "${DOCDIR}")
install(FILES "minetest.conf.example" DESTINATION "${EXAMPLE_CONF_DIR}")
if(UNIX AND NOT APPLE)
install(FILES "doc/minetest.6" "doc/minetestserver.6" DESTINATION "${MANDIR}/man6")
install(FILES "misc/minetest.desktop" DESTINATION "${XDG_APPS_DIR}")
install(FILES "misc/minetest.appdata.xml" DESTINATION "${APPDATADIR}")
install(FILES "misc/minetest.svg" DESTINATION "${ICONDIR}/hicolor/scalable/apps")
install(FILES "misc/minetest-xorg-icon-128.png"
DESTINATION "${ICONDIR}/hicolor/128x128/apps"
RENAME "minetest.png")
endif()
if(APPLE)
install(FILES "misc/minetest-icon.icns" DESTINATION "${SHAREDIR}")
install(FILES "misc/Info.plist" DESTINATION "${BUNDLE_PATH}/Contents")
endif()
# Subdirectories
# Be sure to add all relevant definitions above this
add_subdirectory(src)
# CPack
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "An InfiniMiner/Minecraft inspired game")
set(CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${VERSION_PATCH})
set(CPACK_PACKAGE_VENDOR "celeron55")
set(CPACK_PACKAGE_CONTACT "Perttu Ahola <celeron55@gmail.com>")
if(WIN32)
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-win64")
else()
set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-win32")
endif()
set(CPACK_GENERATOR ZIP)
elseif(APPLE)
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-osx")
set(CPACK_GENERATOR ZIP)
else()
set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-linux")
set(CPACK_GENERATOR TGZ)
set(CPACK_SOURCE_GENERATOR TGZ)
endif()
include(CPack)
# Add a target to generate API documentation with Doxygen
find_package(Doxygen)
if(DOXYGEN_FOUND)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/doc/Doxyfile.in
${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile @ONLY)
add_custom_target(doc
${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc
COMMENT "Generating API documentation with Doxygen" VERBATIM
)
endif()

107
minetest/CONTRIBUTING.md Normal file
View File

@ -0,0 +1,107 @@
# Contributing
Contributions are welcome! Here's how you can help:
- [Contributing code](#code)
- [Reporting issues](#issues)
- [Requesting features](#feature-requests)
- [Translating](#translations)
- [Donating](#donations)
## Code
If you are planning to start some significant coding, you would benefit from asking first on [our IRC channel](http://www.minetest.net/irc/) before starting.
1. [Fork](https://help.github.com/articles/fork-a-repo/) the repository and [clone](https://help.github.com/articles/cloning-a-repository/) your fork.
2. Start coding!
- Refer to the [Lua API](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt), [Developer Wiki](http://dev.minetest.net/Main_Page) and other [documentation](https://github.com/minetest/minetest/tree/master/doc).
- Follow the [C/C++](http://dev.minetest.net/Code_style_guidelines) and [Lua](http://dev.minetest.net/Lua_code_style_guidelines) code style guidelines.
- Check your code works as expected and document any changes to the Lua API.
3. Commit & [push](https://help.github.com/articles/pushing-to-a-remote/) your changes to a new branch (not `master`, one change per branch)
- Commit messages should:
- Use the present tense
- Have a title which begins with a capital letter
- Be descriptive. (e.g. no `Update init.lua` or `Fix a problem`)
- Have a first line with less than *80 characters* and have a second line that is *empty*
- Do **not** [sign your commits](https://git-scm.com/book/uz/v2/Git-Tools-Signing-Your-Work), as Minetest offers automatically built ppas over launchpad and it [would break](https://bugs.launchpad.net/bzr-git/+bug/1084403) if there were signed commits in master
4. Once you are happy with your changes, submit a pull request.
- Open the [pull-request form](https://github.com/minetest/minetest/pull/new/master)
- Add a short description explaining briefly what you've done (or if it's a work-in-progress - what you need to do)
##### A pull-request is considered merge-able when:
1. It follows the [roadmap](https://forum.minetest.net/viewtopic.php?t=9177) in some way and fits the whole picture of the project.
2. It works.
3. It follows the code style for [C/C++](http://dev.minetest.net/Code_style_guidelines) or [Lua](http://dev.minetest.net/Lua_code_style_guidelines).
4. The code's interfaces are well designed, regardless of other aspects that might need more work in the future.
5. It uses protocols and formats which include the required compatibility.
## Issues
If you experience an issue, we would like to know the details - especially when a stable release is on the way.
1. Do a quick search on GitHub to check if the issue has already been reported.
2. Is it an issue with the Minetest *engine*? If not, report it [elsewhere](http://www.minetest.net/development/#reporting-issues).
3. [Open an issue](https://github.com/minetest/minetest/issues/new) and describe the issue you are having - you could include:
- Error logs (check the bottom of the `debug.txt` file)
- Screenshots
- Ways you have tried to solve the issue, and whether they worked or not
- Your Minetest version and the content (subgames, mods or texture packs) you have installed
- Your platform (e.g. Windows 10 or Ubuntu 15.04 x64)
After reporting you should aim to answer questions or clarifications as this helps pinpoint the cause of the issue (if you don't do this your issue may be closed after 1 month).
## Feature requests
Feature requests are welcome but take a moment to see if your idea follows the [roadmap](https://forum.minetest.net/viewtopic.php?t=9177) in some way and fits the whole picture of the project. You should provide a clear explanation with as much detail as possible.
## Translations
Translations of Minetest are performed using Weblate. You can access the project page with a list of current languages [here](https://hosted.weblate.org/projects/minetest/minetest/).
### Donations
If you'd like to monetarily support Minetest development, you can find donation methods on [our website](http://www.minetest.net/development/#donate).
# Maintaining
*This is a concise version of the [Rules & Guidelines](http://dev.minetest.net/Category:Rules_and_Guidelines) on the developer wiki.*
These notes are for those who have push access Minetest (core developers / maintainers).
- See the [project organisation](http://dev.minetest.net/Organisation) for the people involved.
## Reviewing pull requests
Pull requests should be reviewed and, if appropriate, checked if they achieve their intended purpose. You can show that you are in the process of, or will review the pull request by commenting *"Looks good"* or something similar.
**If the pull-request is not [merge-able](#a-pull-request-is-considered-merge-able-when):**
Submit a comment explaining to the author what they need to change to make the pull-request merge-able.
- If the author comments or makes changes to the pull-request, it can be reviewed again.
- If no response is made from the author within 1 month (when improvements are suggested or a question is asked), it can be closed.
**If the pull-request is [merge-able](#a-pull-request-is-considered-merge-able-when):**
Submit a :+1: (+1) or "Looks good" comment to show you believe the pull-request should be merged. "Looks good" comments often signify that the patch might require (more) testing.
- Two core developers must agree to the merge before it is carried out and both should +1 the pull request.
- Who intends to merge the pull-request should follow the commit rules:
- The title should follow the commit guidelines (title starts with a capital letter, present tense, descriptive).
- Don't modify history older than 10 minutes.
- Use rebase, not merge to get linear history:
- `curl https://github.com/minetest/minetest/pull/1.patch | git am`
## Reviewing issues and feature requests
- If an issue does not get a response from its author within 1 month (when requiring more details), it can be closed.
- When an issue is a duplicate, refer to the first ones and close the later ones.
- Tag issues with the appropriate [labels](https://github.com/minetest/minetest/labels) for devices, platforms etc.
## Releasing a new version
*Refer to [dev.minetest.net/Releasing_Minetest](http://dev.minetest.net/Releasing_Minetest)*

562
minetest/README.txt Normal file
View File

@ -0,0 +1,562 @@
Minetest
========
An InfiniMiner/Minecraft inspired game.
Copyright (c) 2010-2017 Perttu Ahola <celeron55@gmail.com>
and contributors (see source file comments and the version control log)
In case you downloaded the source code:
---------------------------------------
If you downloaded the Minetest Engine source code in which this file is
contained, you probably want to download the minetest_game project too:
https://github.com/minetest/minetest_game/
See the README.txt in it.
Further documentation
----------------------
- Website: http://minetest.net/
- Wiki: http://wiki.minetest.net/
- Developer wiki: http://dev.minetest.net/
- Forum: http://forum.minetest.net/
- Github: https://github.com/minetest/minetest/
- doc/ directory of source distribution
This game is not finished
--------------------------
- Don't expect it to work as well as a finished game will.
- Please report any bugs. When doing that, debug.txt is useful.
Default controls
-----------------
- Move mouse: Look around
- W, A, S, D: Move
- Space: Jump/move up
- Shift: Sneak/move down
- Q: Drop itemstack
- Shift + Q: Drop single item
- Left mouse button: Dig/punch/take item
- Right mouse button: Place/use
- Shift + right mouse button: Build (without using)
- I: Inventory menu
- Mouse wheel: Select item
- 0-9: Select item
- Z: Zoom (needs zoom privilege)
- T: Chat
- /: Commad
- Esc: Pause menu/abort/exit (pauses only singleplayer game)
- R: Enable/disable full range view
- +: Increase view range
- -: Decrease view range
- K: Enable/disable fly mode (needs fly privilege)
- J: Enable/disable fast mode (needs fast privilege)
- H: Enable/disable noclip mode (needs noclip privilege)
- F1: Hide/show HUD
- F2: Hide/show chat
- F3: Disable/enable fog
- F4: Disable/enable camera update (Mapblocks are not updated anymore when disabled, disabled in release builds)
- F5: Cycle through debug info screens
- F6: Cycle through profiler info screens
- F7: Cycle through camera modes
- F8: Toggle cinematic mode
- F9: Cycle through minimap modes
- Shift + F9: Change minimap orientation
- F10: Show/hide console
- F12: Take screenshot
- P: Write stack traces into debug.txt
Most controls are settable in the configuration file, see the section below.
Paths
------
$bin - Compiled binaries
$share - Distributed read-only data
$user - User-created modifiable data
Windows .zip / RUN_IN_PLACE source:
$bin = bin
$share = .
$user = .
Linux installed:
$bin = /usr/bin
$share = /usr/share/minetest
$user = ~/.minetest
OS X:
$bin = Contents/MacOS
$share = Contents/Resources
$user = Contents/User OR ~/Library/Application Support/minetest
World directory
----------------
- Worlds can be found as separate folders in:
$user/worlds/
Configuration file:
-------------------
- Default location:
$user/minetest.conf
- It is created by Minetest when it is ran the first time.
- A specific file can be specified on the command line:
--config <path-to-file>
Command-line options:
---------------------
- Use --help
Compiling on GNU/Linux:
-----------------------
Install dependencies. Here's an example for Debian/Ubuntu:
$ sudo apt-get 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
For Fedora users:
$ sudo dnf 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
You can install git for easily keeping your copy up to date.
If you dont want git, read below on how to get the source without git.
This is an example for installing git on Debian/Ubuntu:
$ sudo apt-get install git
For Fedora users:
$ sudo dnf install git
Download source (this is the URL to the latest of source repository, which might not work at all times) using git:
$ git clone --depth 1 https://github.com/minetest/minetest.git
$ cd minetest
Download minetest_game (otherwise only the "Minimal development test" game is available) using git:
$ git clone --depth 1 https://github.com/minetest/minetest_game.git games/minetest_game
Download source, without using git:
$ wget https://github.com/minetest/minetest/archive/master.tar.gz
$ tar xf master.tar.gz
$ cd minetest-master
Download minetest_game, without using git:
$ cd games/
$ wget https://github.com/minetest/minetest_game/archive/master.tar.gz
$ tar xf master.tar.gz
$ mv minetest_game-master minetest_game
$ cd ..
Build a version that runs directly from the source directory:
$ cmake . -DRUN_IN_PLACE=TRUE
$ make -j <number of processors>
Run it:
$ ./bin/minetest
- Use cmake . -LH to see all CMake options and their current state
- If you want to install it system-wide (or are making a distribution package),
you will want to use -DRUN_IN_PLACE=FALSE
- You can build a bare server by specifying -DBUILD_SERVER=TRUE
- You can disable the client build by specifying -DBUILD_CLIENT=FALSE
- You can select between Release and Debug build by -DCMAKE_BUILD_TYPE=<Debug or Release>
- Debug build is slower, but gives much more useful output in a debugger
- If you build a bare server, you don't need to have Irrlicht installed.
In that case use -DIRRLICHT_SOURCE_DIR=/the/irrlicht/source
CMake options
-------------
General options:
BUILD_CLIENT - Build Minetest client
BUILD_SERVER - Build Minetest server
CMAKE_BUILD_TYPE - Type of build (Release vs. Debug)
Release - Release build
Debug - Debug build
SemiDebug - Partially optimized debug build
RelWithDebInfo - Release build with Debug information
MinSizeRel - Release build with -Os passed to compiler to make executable as small as possible
ENABLE_CURL - Build with cURL; Enables use of online mod repo, public serverlist and remote media fetching via http
ENABLE_CURSES - Build with (n)curses; Enables a server side terminal (command line option: --terminal)
ENABLE_FREETYPE - Build with FreeType2; Allows using TTF fonts
ENABLE_GETTEXT - Build with Gettext; Allows using translations
ENABLE_GLES - Search for Open GLES headers & libraries and use them
ENABLE_LEVELDB - Build with LevelDB; Enables use of LevelDB map backend
ENABLE_POSTGRESQL - Build with libpq; Enables use of PostgreSQL map backend (PostgreSQL 9.5 or greater recommended)
ENABLE_REDIS - Build with libhiredis; Enables use of Redis map backend
ENABLE_SPATIAL - Build with LibSpatial; Speeds up AreaStores
ENABLE_SOUND - Build with OpenAL, libogg & libvorbis; in-game Sounds
ENABLE_LUAJIT - Build with LuaJIT (much faster than non-JIT Lua)
ENABLE_SYSTEM_GMP - Use GMP from system (much faster than bundled mini-gmp)
RUN_IN_PLACE - Create a portable install (worlds, settings etc. in current directory)
USE_GPROF - Enable profiling using GProf
VERSION_EXTRA - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar)
Library specific options:
BZIP2_INCLUDE_DIR - Linux only; directory where bzlib.h is located
BZIP2_LIBRARY - Linux only; path to libbz2.a/libbz2.so
CURL_DLL - Only if building with cURL on Windows; path to libcurl.dll
CURL_INCLUDE_DIR - Only if building with cURL; directory where curl.h is located
CURL_LIBRARY - Only if building with cURL; path to libcurl.a/libcurl.so/libcurl.lib
EGL_INCLUDE_DIR - Only if building with GLES; directory that contains egl.h
EGL_LIBRARY - Only if building with GLES; path to libEGL.a/libEGL.so
FREETYPE_INCLUDE_DIR_freetype2 - Only if building with Freetype2; directory that contains an freetype directory with files such as ftimage.h in it
FREETYPE_INCLUDE_DIR_ft2build - Only if building with Freetype2; directory that contains ft2build.h
FREETYPE_LIBRARY - Only if building with Freetype2; path to libfreetype.a/libfreetype.so/freetype.lib
FREETYPE_DLL - Only if building with Freetype2 on Windows; path to libfreetype.dll
GETTEXT_DLL - Only when building with Gettext on Windows; path to libintl3.dll
GETTEXT_ICONV_DLL - Only when building with Gettext on Windows; path to libiconv2.dll
GETTEXT_INCLUDE_DIR - Only when building with Gettext; directory that contains iconv.h
GETTEXT_LIBRARY - Only when building with Gettext on Windows; path to libintl.dll.a
GETTEXT_MSGFMT - Only when building with Gettext; path to msgfmt/msgfmt.exe
IRRLICHT_DLL - Only on Windows; path to Irrlicht.dll
IRRLICHT_INCLUDE_DIR - Directory that contains IrrCompileConfig.h
IRRLICHT_LIBRARY - Path to libIrrlicht.a/libIrrlicht.so/libIrrlicht.dll.a/Irrlicht.lib
LEVELDB_INCLUDE_DIR - Only when building with LevelDB; directory that contains db.h
LEVELDB_LIBRARY - Only when building with LevelDB; path to libleveldb.a/libleveldb.so/libleveldb.dll.a
LEVELDB_DLL - Only when building with LevelDB on Windows; path to libleveldb.dll
POSTGRESQL_INCLUDE_DIR - Only when building with PostgreSQL; directory that contains libpq-fe.h
POSTGRESQL_LIBRARY - Only when building with PostgreSQL; path to libpq.a/libpq.so
REDIS_INCLUDE_DIR - Only when building with Redis; directory that contains hiredis.h
REDIS_LIBRARY - Only when building with Redis; path to libhiredis.a/libhiredis.so
SPATIAL_INCLUDE_DIR - Only when building with LibSpatial; directory that contains spatialindex/SpatialIndex.h
SPATIAL_LIBRARY - Only when building with LibSpatial; path to libspatialindex_c.so/spatialindex-32.lib
LUA_INCLUDE_DIR - Only if you want to use LuaJIT; directory where luajit.h is located
LUA_LIBRARY - Only if you want to use LuaJIT; path to libluajit.a/libluajit.so
MINGWM10_DLL - Only if compiling with MinGW; path to mingwm10.dll
OGG_DLL - Only if building with sound on Windows; path to libogg.dll
OGG_INCLUDE_DIR - Only if building with sound; directory that contains an ogg directory which contains ogg.h
OGG_LIBRARY - Only if building with sound; path to libogg.a/libogg.so/libogg.dll.a
OPENAL_DLL - Only if building with sound on Windows; path to OpenAL32.dll
OPENAL_INCLUDE_DIR - Only if building with sound; directory where al.h is located
OPENAL_LIBRARY - Only if building with sound; path to libopenal.a/libopenal.so/OpenAL32.lib
OPENGLES2_INCLUDE_DIR - Only if building with GLES; directory that contains gl2.h
OPENGLES2_LIBRARY - Only if building with GLES; path to libGLESv2.a/libGLESv2.so
SQLITE3_INCLUDE_DIR - Directory that contains sqlite3.h
SQLITE3_LIBRARY - Path to libsqlite3.a/libsqlite3.so/sqlite3.lib
VORBISFILE_DLL - Only if building with sound on Windows; path to libvorbisfile-3.dll
VORBISFILE_LIBRARY - Only if building with sound; path to libvorbisfile.a/libvorbisfile.so/libvorbisfile.dll.a
VORBIS_DLL - Only if building with sound on Windows; path to libvorbis-0.dll
VORBIS_INCLUDE_DIR - Only if building with sound; directory that contains a directory vorbis with vorbisenc.h inside
VORBIS_LIBRARY - Only if building with sound; path to libvorbis.a/libvorbis.so/libvorbis.dll.a
XXF86VM_LIBRARY - Only on Linux; path to libXXf86vm.a/libXXf86vm.so
ZLIB_DLL - Only on Windows; path to zlib1.dll
ZLIBWAPI_DLL - Only on Windows; path to zlibwapi.dll
ZLIB_INCLUDE_DIR - Directory that contains zlib.h
ZLIB_LIBRARY - Path to libz.a/libz.so/zlibwapi.lib
Compiling on Windows:
---------------------
- This section is outdated. In addition to what is described here:
- In addition to minetest, you need to download minetest_game.
- If you wish to have sound support, you need libogg, libvorbis and libopenal
- You need:
* CMake:
http://www.cmake.org/cmake/resources/software.html
* MinGW or Visual Studio
http://www.mingw.org/
http://msdn.microsoft.com/en-us/vstudio/default
* Irrlicht SDK 1.7:
http://irrlicht.sourceforge.net/downloads.html
* Zlib headers (zlib125.zip)
http://www.winimage.com/zLibDll/index.html
* Zlib library (zlibwapi.lib and zlibwapi.dll from zlib125dll.zip):
http://www.winimage.com/zLibDll/index.html
* SQLite3 headers and library
https://www.sqlite.org/download.html
* Optional: gettext library and tools:
http://gnuwin32.sourceforge.net/downlinks/gettext.php
- This is used for other UI languages. Feel free to leave it out.
* And, of course, Minetest:
http://minetest.net/download
- Steps:
- Select a directory called DIR hereafter in which you will operate.
- Make sure you have CMake and a compiler installed.
- Download all the other stuff to DIR and extract them into there.
("extract here", not "extract to packagename/")
NOTE: zlib125dll.zip needs to be extracted into zlib125dll
NOTE: You need to extract sqlite3.h & sqlite3ext.h from sqlite3 source
and sqlite3.dll & sqlite3.def from sqlite3 precompiled binaries
into "sqlite3" directory, and generate sqlite3.lib using command
"LIB /DEF:sqlite3.def /OUT:sqlite3.lib"
- All those packages contain a nice base directory in them, which
should end up being the direct subdirectories of DIR.
- You will end up with a directory structure like this (+=dir, -=file):
-----------------
+ DIR
- zlib-1.2.5.tar.gz
- zlib125dll.zip
- irrlicht-1.8.3.zip
- sqlite-amalgamation-3130000.zip (SQLite3 headers)
- sqlite-dll-win32-x86-3130000.zip (SQLite3 library for 32bit system)
- 110214175330.zip (or whatever, this is the minetest source)
+ zlib-1.2.5
- zlib.h
+ win32
...
+ zlib125dll
- readme.txt
+ dll32
...
+ irrlicht-1.8.3
+ lib
+ include
...
+ sqlite3
sqlite3.h
sqlite3ext.h
sqlite3.lib
sqlite3.dll
+ gettext (optional)
+bin
+include
+lib
+ minetest
+ src
+ doc
- CMakeLists.txt
...
-----------------
- Start up the CMake GUI
- Select "Browse Source..." and select DIR/minetest
- Now, if using MSVC:
- Select "Browse Build..." and select DIR/minetest-build
- Else if using MinGW:
- Select "Browse Build..." and select DIR/minetest
- Select "Configure"
- Select your compiler
- It will warn about missing stuff, ignore that at this point. (later don't)
- Make sure the configuration is as follows
(note that the versions may differ for you):
-----------------
BUILD_CLIENT [X]
BUILD_SERVER [ ]
CMAKE_BUILD_TYPE Release
CMAKE_INSTALL_PREFIX DIR/minetest-install
IRRLICHT_SOURCE_DIR DIR/irrlicht-1.8.3
RUN_IN_PLACE [X]
WARN_ALL [ ]
ZLIB_DLL DIR/zlib125dll/dll32/zlibwapi.dll
ZLIB_INCLUDE_DIR DIR/zlib-1.2.5
ZLIB_LIBRARIES DIR/zlib125dll/dll32/zlibwapi.lib
GETTEXT_BIN_DIR DIR/gettext/bin
GETTEXT_INCLUDE_DIR DIR/gettext/include
GETTEXT_LIBRARIES DIR/gettext/lib/intl.lib
GETTEXT_MSGFMT DIR/gettext/bin/msgfmt
-----------------
- If CMake complains it couldn't find SQLITE3, choose "Advanced" box on the
right top corner, then specify the location of SQLITE3_INCLUDE_DIR and
SQLITE3_LIBRARY manually.
- If you want to build 64-bit minetest, you will need to build 64-bit version
of irrlicht engine manually, as only 32-bit pre-built library is provided.
- Hit "Configure"
- Hit "Configure" once again 8)
- If something is still coloured red, you have a problem.
- Hit "Generate"
If using MSVC:
- Open the generated minetest.sln
- The project defaults to the "Debug" configuration. Make very sure to
select "Release", unless you want to debug some stuff (it's slower
and might not even work at all)
- Build the ALL_BUILD project
- Build the INSTALL project
- You should now have a working game with the executable in
DIR/minetest-install/bin/minetest.exe
- Additionally you may create a zip package by building the PACKAGE
project.
If using MinGW:
- Using the command line, browse to the build directory and run 'make'
(or mingw32-make or whatever it happens to be)
- You may need to copy some of the downloaded DLLs into bin/, see what
running the produced executable tells you it doesn't have.
- You should now have a working game with the executable in
DIR/minetest/bin/minetest.exe
Windows releases of minetest are built using a bat script like this:
--------------------------------------------------------------------
set sourcedir=%CD%
set installpath="C:\tmp\minetest_install"
set irrlichtpath="C:\tmp\irrlicht-1.7.2"
set builddir=%sourcedir%\bvc10
mkdir %builddir%
pushd %builddir%
cmake %sourcedir% -G "Visual Studio 10" -DIRRLICHT_SOURCE_DIR=%irrlichtpath% -DRUN_IN_PLACE=TRUE -DCMAKE_INSTALL_PREFIX=%installpath%
if %errorlevel% neq 0 goto fail
"C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" ALL_BUILD.vcxproj /p:Configuration=Release
if %errorlevel% neq 0 goto fail
"C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" INSTALL.vcxproj /p:Configuration=Release
if %errorlevel% neq 0 goto fail
"C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" PACKAGE.vcxproj /p:Configuration=Release
if %errorlevel% neq 0 goto fail
popd
echo Finished.
exit /b 0
:fail
popd
echo Failed.
exit /b 1
License of Minetest textures and sounds
---------------------------------------
This applies to textures and sounds contained in the main Minetest
distribution.
Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
http://creativecommons.org/licenses/by-sa/3.0/
Authors of media files
-----------------------
Everything not listed in here:
Copyright (C) 2010-2012 celeron55, Perttu Ahola <celeron55@gmail.com>
ShadowNinja:
textures/base/pack/smoke_puff.png
Paramat:
textures/base/pack/menu_header.png
erlehmann:
misc/minetest-icon-24x24.png
misc/minetest-icon.ico
misc/minetest.svg
textures/base/pack/logo.png
License of Minetest source code
-------------------------------
Minetest
Copyright (C) 2010-2017 celeron55, Perttu Ahola <celeron55@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.
Irrlicht
---------------
This program uses the Irrlicht Engine. http://irrlicht.sourceforge.net/
The Irrlicht Engine License
Copyright © 2002-2005 Nikolaus Gebhardt
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you
must not claim that you wrote the original software. If you use
this software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must
not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
JThread
---------------
This program uses the JThread library. License for JThread follows:
Copyright (c) 2000-2006 Jori Liesenborgs (jori.liesenborgs@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
Lua
---------------
Lua is licensed under the terms of the MIT license reproduced below.
This means that Lua is free software and can be used for both academic
and commercial purposes at absolutely no cost.
For details and rationale, see http://www.lua.org/license.html .
Copyright (C) 1994-2008 Lua.org, PUC-Rio.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Fonts
---------------
DejaVu Sans Mono:
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
Bitstream Vera Fonts Copyright:
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
a trademark of Bitstream, Inc.
Arev Fonts Copyright:
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
Liberation Fonts Copyright:
Copyright (c) 2007 Red Hat, Inc. All rights reserved. LIBERATION is a trademark of Red Hat, Inc.
DroidSansFallback:
Copyright (C) 2008 The Android Open Source Project
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
http://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.

View File

@ -0,0 +1,850 @@
# build options
OS := $(shell uname)
# compile with GPROF
# GPROF = 1
# build for build platform
APP_PLATFORM = android-9
ANDR_ROOT = $(shell pwd)
PROJ_ROOT = $(shell realpath $(ANDR_ROOT)/../..)
APP_ROOT = $(ANDR_ROOT)/src/main
GAMES_TO_COPY = minetest_game
MODS_TO_COPY =
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_PREFIX = arm-linux-androideabi-
COMPILER_VERSION = 4.9
HAVE_LEVELDB = 1
################################################################################
# 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_PREFIX = mipsel-linux-android-
#COMPILER_VERSION = 4.9
#HAVE_LEVELDB = 0
################################################################################
# toolchain config for x86
################################################################################
#TARGET_HOST = x86-linux
#TARGET_ABI = x86
#TARGET_LIBDIR = x86
#TARGET_TOOLCHAIN = x86-
#CROSS_PREFIX = i686-linux-android-
#TARGET_ARCH = x86
#COMPILER_VERSION = 4.9
#HAVE_LEVELDB = 1
################################################################################
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://github.com/vincentjames501/libvorbis-libogg-android
IRRLICHT_REVISION = 5122
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.2j
OPENSSL_BASEDIR = openssl-$(OPENSSL_VERSION)
OPENSSL_DIR = $(ANDR_ROOT)/deps/$(OPENSSL_BASEDIR)/
OPENSSL_LIB = $(OPENSSL_DIR)/libssl.so.1.0.0
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.52.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
GMP_VERSION = 6.1.2
GMP_DIR = $(ANDR_ROOT)/deps/gmp-$(GMP_VERSION)
GMP_LIB = $(GMP_DIR)/usr/lib/libgmp.so
GMP_TIMESTAMP = $(GMP_DIR)/timestamp
GMP_TIMESTAMP_INT = $(ANDR_ROOT)/deps/gmp_timestamp
GMP_URL_HTTP = https://gmplib.org/download/gmp/gmp-$(GMP_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-3150200
SQLITE3_URL = https://www.sqlite.org/2016/$(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:]]*//')
NDK_MODULE_PATH = $(ANDROID_NDK)/toolchains
#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/ndk-r11c/"; \
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}; \
${ANDROID_NDK}/ndk-build NDEBUG=${NDEBUG} \
NDK_MODULE_PATH=${NDK_MODULE_PATH} APP_ABI=${TARGET_ABI} \
TARGET_ARCH_ABI=${TARGET_ABI} APP_PLATFORM=${APP_PLATFORM} \
PRIVATE_CC=${NDK_MODULE_PATH}/${TARGET_TOOLCHAIN}${COMPILER_VERSION}/prebuilt/linux-x86_64/bin/${TARGET_TOOLCHAIN}gcc \
PRIVATE_CXX=${NDK_MODULE_PATH}/${TARGET_TOOLCHAIN}${COMPILER_VERSION}/prebuilt/linux-x86_64/bin/${TARGET_TOOLCHAIN}g++ \
TARGET_CFLAGS+="${TARGET_CFLAGS_ADDON}" \
TARGET_LDFLAGS+="${TARGET_LDFLAGS_ADDON}" \
TARGET_CXXFLAGS+="${TARGET_CXXFLAGS_ADDON}" || 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; \
sed -i 's-:-?-' jni/Application.mk; \
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}; \
${ANDROID_NDK}/ndk-build NDEBUG=${NDEBUG} \
NDK_MODULE_PATH=${NDK_MODULE_PATH} \
APP_ABI=${TARGET_ABI} APP_PLATFORM=${APP_PLATFORM} \
PRIVATE_CC=${NDK_MODULE_PATH}/${TARGET_TOOLCHAIN}${COMPILER_VERSION}/prebuilt/linux-x86_64/bin/${TARGET_TOOLCHAIN}gcc \
PRIVATE_CXX=${NDK_MODULE_PATH}/${TARGET_TOOLCHAIN}${COMPILER_VERSION}/prebuilt/linux-x86_64/bin/${TARGET_TOOLCHAIN}g++ \
TARGET_CFLAGS+="${TARGET_CFLAGS_ADDON}" \
TARGET_LDFLAGS+="${TARGET_LDFLAGS_ADDON}" \
TARGET_CXXFLAGS+="${TARGET_CXXFLAGS_ADDON}" || exit 1; \
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; \
fi
openssl : $(OPENSSL_LIB)
$(OPENSSL_LIB): $(OPENSSL_TIMESTAMP) $(GMP_LIB)
@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=android-9 \
--install-dir=$${TOOLCHAIN}; \
export PATH="$${TOOLCHAIN}/bin:$${PATH}"; \
CC=${CROSS_PREFIX}gcc ./Configure enable-gmp -DL_ENDIAN -I${GMP_DIR} -L${GMP_DIR}/usr/lib android-${TARGET_ARCH};\
CC=${CROSS_PREFIX}gcc ANDROID_DEV=/tmp/ndk-${TARGET_HOST} make depend; \
CC=${CROSS_PREFIX}gcc 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)
$(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=${CROSS_PREFIX}; \
export TOOLCHAIN=/tmp/ndk-${TARGET_HOST}-leveldb; \
${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh \
--toolchain=${TARGET_TOOLCHAIN}${COMPILER_VERSION} \
--platform=android-9 \
--install-dir=$${TOOLCHAIN}; \
export PATH="$${TOOLCHAIN}/bin:$${PATH}"; \
export CC=${CROSS_PREFIX}gcc; \
export CXX=${CROSS_PREFIX}g++; \
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
clean_leveldb :
$(RM) -rf deps/leveldb
$(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; \
${ANDROID_NDK}/ndk-build NDEBUG=${NDEBUG} \
NDK_MODULE_PATH=${NDK_MODULE_PATH} \
APP_PLATFORM=${APP_PLATFORM} APP_ABI=${TARGET_ABI} \
PRIVATE_CC=${NDK_MODULE_PATH}/${TARGET_TOOLCHAIN}${COMPILER_VERSION}/prebuilt/linux-x86_64/bin/${TARGET_TOOLCHAIN}gcc \
PRIVATE_CXX=${NDK_MODULE_PATH}/${TARGET_TOOLCHAIN}${COMPILER_VERSION}/prebuilt/linux-x86_64/bin/${TARGET_TOOLCHAIN}g++ \
TARGET_CFLAGS+="${TARGET_CFLAGS_ADDON}" \
TARGET_LDFLAGS+="${TARGET_LDFLAGS_ADDON}" \
TARGET_CXXFLAGS+="${TARGET_CXXFLAGS_ADDON}" || exit 1; \
touch ${FREETYPE_TIMESTAMP}; \
touch ${FREETYPE_TIMESTAMP_INT}; \
else \
echo "nothing to be done for freetype"; \
fi
clean_freetype :
$(RM) -rf ${FREETYPE_DIR}
$(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=android-9 \
--install-dir=$${TOOLCHAIN}; \
export PATH="$${TOOLCHAIN}/bin:$${PATH}"; \
export CC=${CROSS_PREFIX}gcc; \
export CXX=${CROSS_PREFIX}g++; \
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 :
$(RM) -rf ${ICONV_DIR}
#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; \
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; \
${ANDROID_NDK}/ndk-build NDEBUG=${NDEBUG} \
NDK_MODULE_PATH=${NDK_MODULE_PATH} \
APP_ABI=${TARGET_ABI} APP_PLATFORM=${APP_PLATFORM} \
PRIVATE_CC=${NDK_MODULE_PATH}/${TARGET_TOOLCHAIN}${COMPILER_VERSION}/prebuilt/linux-x86_64/bin/${TARGET_TOOLCHAIN}gcc \
PRIVATE_CXX=${NDK_MODULE_PATH}/${TARGET_TOOLCHAIN}${COMPILER_VERSION}/prebuilt/linux-x86_64/bin/${TARGET_TOOLCHAIN}g++ \
TARGET_CFLAGS+="${TARGET_CFLAGS_ADDON}" \
TARGET_LDFLAGS+="${TARGET_LDFLAGS_ADDON}" \
TARGET_CXXFLAGS+="${TARGET_CXXFLAGS_ADDON}" || exit 1; \
touch ${IRRLICHT_TIMESTAMP}; \
touch ${IRRLICHT_TIMESTAMP_INT}; \
else \
echo "nothing to be done for irrlicht"; \
fi
clean_irrlicht :
$(RM) -rf deps/irrlicht
$(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=${CROSS_PREFIX}; \
export TOOLCHAIN=/tmp/ndk-${TARGET_HOST}-curl; \
${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh \
--toolchain=${TARGET_TOOLCHAIN}${COMPILER_VERSION} \
--platform=android-9 \
--install-dir=$${TOOLCHAIN}; \
export PATH="$${TOOLCHAIN}/bin:$${PATH}"; \
export CC=${CROSS_PREFIX}gcc; \
export CXX=${CROSS_PREFIX}g++; \
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 :
$(RM) -rf deps/curl-${CURL_VERSION} \
$(RM) -f deps/curl
$(GMP_TIMESTAMP) : gmp_download
@LAST_MODIF=$$(find ${GMP_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
touch ${GMP_TIMESTAMP}; \
fi
gmp_download :
@if [ ! -d "${GMP_DIR}" ] ; then \
echo "gmp sources missing, downloading..."; \
mkdir -p ${ANDR_ROOT}/deps; \
cd deps; \
wget ${GMP_URL_HTTP} || exit 1; \
tar -xjf gmp-${GMP_VERSION}.tar.bz2 || exit 1; \
rm gmp-${GMP_VERSION}.tar.bz2; \
ln -s gmp-${GMP_VERSION} gmp; \
fi
gmp : $(GMP_LIB)
$(GMP_LIB): $(GMP_TIMESTAMP)
@REFRESH=0; \
if [ ! -e ${GMP_TIMESTAMP_INT} ] ; then \
REFRESH=1; \
fi; \
if [ ! -e ${GMP_LIB} ] ; then \
REFRESH=1; \
fi; \
if [ ${GMP_TIMESTAMP} -nt ${GMP_TIMESTAMP_INT} ] ; then \
REFRESH=1; \
fi; \
if [ $$REFRESH -ne 0 ] ; then \
mkdir -p ${GMP_DIR}; \
echo "changed timestamp for gmp detected building..."; \
cd deps/gmp-${GMP_VERSION}; \
export CROSS_PREFIX=${CROSS_PREFIX}; \
export TOOLCHAIN=/tmp/ndk-${TARGET_HOST}-gmp; \
${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh \
--toolchain=${TARGET_TOOLCHAIN}${COMPILER_VERSION} \
--platform=android-9 \
--install-dir=$${TOOLCHAIN}; \
export PATH="$${TOOLCHAIN}/bin:$${PATH}"; \
export CC=${CROSS_PREFIX}gcc; \
export CXX=${CROSS_PREFIX}g++; \
export LIBGMP_LDFLAGS="-avoid-version"; \
export LIBGMPXX_LDFLAGS="-avoid-version"; \
./configure --disable-static --host=${TARGET_HOST} --prefix=/usr; \
$(MAKE) install DESTDIR=/${GMP_DIR} || exit 1; \
touch ${GMP_TIMESTAMP}; \
touch ${GMP_TIMESTAMP_INT}; \
$(RM) -rf $${TOOLCHAIN}; \
else \
echo "nothing to be done for gmp"; \
fi
clean_gmp:
$(RM) -rf deps/gmp-${GMP_VERSION} \
$(RM) -f deps/gmp
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}; \
patch sqlite3.c < ${ANDR_ROOT}/patches/sqlite3-readonly-fix.patch
clean_sqlite3:
cd deps && $(RM) -rf ${SQLITE3_FOLDER} && $(RM) -f ${SQLITE3_FOLDER}.zip && \
$(RM) -f sqlite
$(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; \
mkdir -p ${APP_ROOT}/assets/Minetest; \
cp ${PROJ_ROOT}/minetest.conf.example ${APP_ROOT}/assets/Minetest; \
cp ${PROJ_ROOT}/README.txt ${APP_ROOT}/assets/Minetest; \
cp -r ${PROJ_ROOT}/builtin ${APP_ROOT}/assets/Minetest; \
mkdir -p ${APP_ROOT}/assets/Minetest/client; \
cp -r ${PROJ_ROOT}/client/shaders ${APP_ROOT}/assets/Minetest/client; \
cp ${PROJ_ROOT}/doc/lgpl-2.1.txt ${APP_ROOT}/assets/Minetest/LICENSE.txt; \
mkdir -p ${APP_ROOT}/assets/Minetest/fonts; \
cp -r ${PROJ_ROOT}/fonts/*.ttf ${APP_ROOT}/assets/Minetest/fonts/; \
mkdir -p ${APP_ROOT}/assets/Minetest/games; \
for game in ${GAMES_TO_COPY}; do \
cp -r ${PROJ_ROOT}/games/$$game ${APP_ROOT}/assets/Minetest/games/; \
done; \
mkdir -p ${APP_ROOT}/assets/Minetest/mods; \
for mod in ${MODS_TO_COPY}; do \
cp -r ${PROJ_ROOT}/mods/$$mod ${APP_ROOT}/assets/Minetest/mods/; \
done; \
cp -r ${PROJ_ROOT}/po ${APP_ROOT}/assets/Minetest; \
cp -r ${PROJ_ROOT}/textures ${APP_ROOT}/assets/Minetest; \
mkdir -p ${APP_ROOT}/assets/Minetest/media; \
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 :
@$(RM) -r assets
apk: local.properties assets $(ICONV_LIB) $(IRRLICHT_LIB) $(CURL_LIB) $(GMP_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
+ @${ANDROID_NDK}/ndk-build NDK_MODULE_PATH=${NDK_MODULE_PATH} \
GPROF=${GPROF} APP_ABI=${TARGET_ABI} HAVE_LEVELDB=${HAVE_LEVELDB} \
APP_PLATFORM=${APP_PLATFORM} \
PRIVATE_CC=${NDK_MODULE_PATH}/${TARGET_TOOLCHAIN}${COMPILER_VERSION}/prebuilt/linux-x86_64/bin/${TARGET_TOOLCHAIN}gcc \
PRIVATE_CXX=${NDK_MODULE_PATH}/${TARGET_TOOLCHAIN}${COMPILER_VERSION}/prebuilt/linux-x86_64/bin/${TARGET_TOOLCHAIN}g++ \
TARGET_LIBDIR=${TARGET_LIBDIR} \
TARGET_CFLAGS+="${TARGET_CFLAGS_ADDON}" \
TARGET_LDFLAGS+="${TARGET_LDFLAGS_ADDON}" \
TARGET_CXXFLAGS+="${TARGET_CXXFLAGS_ADDON}" || 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/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/Minetest-debug.apk
install_release:
${ANDROID_SDK}/platform-tools/adb install -r build/outputs/apk/Minetest-release.apk
prep_srcdir :
@if [ ! -e ${ANDR_ROOT}/jni/src ]; then \
ln -s ${PROJ_ROOT}/src ${ANDR_ROOT}/jni/src; \
fi
clean_apk :
./gradlew clean
clean_all :
@$(MAKE) clean_apk; \
$(MAKE) clean_assets clean_iconv clean_irrlicht clean_leveldb clean_curl \
clean_openssl clean_openal clean_ogg clean_gmp; \
sleep 1; \
$(RM) -r gen libs obj deps bin Debug and_env
$(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,48 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:1.5.0"
}
}
apply plugin: "com.android.application"
android {
compileSdkVersion 25
buildToolsVersion "25.0.1"
defaultConfig {
versionCode 16
versionName "${System.env.VERSION_STR}.${versionCode}"
minSdkVersion 9
targetSdkVersion 9
applicationId "net.minetest.minetest"
manifestPlaceholders = [ package: "net.minetest.minetest", project: project.name ]
}
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
}
}
}
}

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Sat Aug 27 20:10:09 CEST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

164
minetest/build/android/gradlew vendored Executable file
View File

@ -0,0 +1,164 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
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
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
minetest/build/android/gradlew.bat vendored Normal file
View File

@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@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 DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,391 @@
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 := gmp
LOCAL_SRC_FILES := deps/gmp/usr/lib/libgmp.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 += -O3
endif
ifdef GPROF
PROFILER_LIBS := android-ndk-profiler
LOCAL_CFLAGS += -pg
endif
# LOCAL_CFLAGS += -fsanitize=address
# LOCAL_LDFLAGS += -fsanitize=address
ifeq ($(TARGET_ARCH_ABI),x86)
LOCAL_CFLAGS += -fno-stack-protector
endif
LOCAL_C_INCLUDES := \
jni/src \
jni/src/script \
jni/src/lua/src \
jni/src/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/gmp/usr/include \
deps/leveldb/include \
deps/sqlite/
LOCAL_SRC_FILES := \
jni/src/ban.cpp \
jni/src/camera.cpp \
jni/src/cavegen.cpp \
jni/src/chat.cpp \
jni/src/client.cpp \
jni/src/clientenvironment.cpp \
jni/src/clientiface.cpp \
jni/src/clientmap.cpp \
jni/src/clientmedia.cpp \
jni/src/clientobject.cpp \
jni/src/clouds.cpp \
jni/src/collision.cpp \
jni/src/content_abm.cpp \
jni/src/content_cao.cpp \
jni/src/content_cso.cpp \
jni/src/content_mapblock.cpp \
jni/src/content_mapnode.cpp \
jni/src/content_nodemeta.cpp \
jni/src/content_sao.cpp \
jni/src/convert_json.cpp \
jni/src/craftdef.cpp \
jni/src/database-dummy.cpp \
jni/src/database-sqlite3.cpp \
jni/src/database.cpp \
jni/src/debug.cpp \
jni/src/defaultsettings.cpp \
jni/src/drawscene.cpp \
jni/src/dungeongen.cpp \
jni/src/emerge.cpp \
jni/src/environment.cpp \
jni/src/filecache.cpp \
jni/src/filesys.cpp \
jni/src/fontengine.cpp \
jni/src/game.cpp \
jni/src/genericobject.cpp \
jni/src/gettext.cpp \
jni/src/guiChatConsole.cpp \
jni/src/guiEngine.cpp \
jni/src/guiFileSelectMenu.cpp \
jni/src/guiFormSpecMenu.cpp \
jni/src/guiKeyChangeMenu.cpp \
jni/src/guiPasswordChange.cpp \
jni/src/guiTable.cpp \
jni/src/guiscalingfilter.cpp \
jni/src/guiVolumeChange.cpp \
jni/src/httpfetch.cpp \
jni/src/hud.cpp \
jni/src/imagefilters.cpp \
jni/src/intlGUIEditBox.cpp \
jni/src/inventory.cpp \
jni/src/inventorymanager.cpp \
jni/src/itemdef.cpp \
jni/src/itemstackmetadata.cpp \
jni/src/keycode.cpp \
jni/src/light.cpp \
jni/src/localplayer.cpp \
jni/src/log.cpp \
jni/src/main.cpp \
jni/src/map.cpp \
jni/src/map_settings_manager.cpp \
jni/src/mapblock.cpp \
jni/src/mapblock_mesh.cpp \
jni/src/mapgen.cpp \
jni/src/mapgen_flat.cpp \
jni/src/mapgen_fractal.cpp \
jni/src/mapgen_singlenode.cpp \
jni/src/mapgen_v5.cpp \
jni/src/mapgen_v6.cpp \
jni/src/mapgen_v7.cpp \
jni/src/mapgen_valleys.cpp \
jni/src/mapnode.cpp \
jni/src/mapsector.cpp \
jni/src/mesh.cpp \
jni/src/metadata.cpp \
jni/src/mg_biome.cpp \
jni/src/mg_decoration.cpp \
jni/src/mg_ore.cpp \
jni/src/mg_schematic.cpp \
jni/src/minimap.cpp \
jni/src/mods.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/particles.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.cpp \
jni/src/serverenvironment.cpp \
jni/src/serverlist.cpp \
jni/src/serverobject.cpp \
jni/src/shader.cpp \
jni/src/sky.cpp \
jni/src/socket.cpp \
jni/src/sound.cpp \
jni/src/sound_openal.cpp \
jni/src/staticobject.cpp \
jni/src/subgame.cpp \
jni/src/tileanimation.cpp \
jni/src/tool.cpp \
jni/src/treegen.cpp \
jni/src/version.cpp \
jni/src/voxel.cpp \
jni/src/voxelalgorithms.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/numeric.cpp \
jni/src/util/pointedthing.cpp \
jni/src/util/serialize.cpp \
jni/src/util/sha1.cpp \
jni/src/util/string.cpp \
jni/src/util/srp.cpp \
jni/src/util/timetaker.cpp \
jni/src/unittest/test.cpp \
jni/src/unittest/test_collision.cpp \
jni/src/unittest/test_compression.cpp \
jni/src/unittest/test_connection.cpp \
jni/src/unittest/test_filepath.cpp \
jni/src/unittest/test_inventory.cpp \
jni/src/unittest/test_map_settings_manager.cpp \
jni/src/unittest/test_mapnode.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/touchscreengui.cpp \
jni/src/database-leveldb.cpp \
jni/src/settings.cpp \
jni/src/wieldmesh.cpp \
jni/src/client/clientlauncher.cpp \
jni/src/client/tile.cpp \
jni/src/client/joystick_controller.cpp \
jni/src/irrlicht_changes/static_text.cpp
# intentionally kept out (we already build openssl itself): jni/src/util/sha256.c
# Network
LOCAL_SRC_FILES += \
jni/src/network/connection.cpp \
jni/src/network/networkpacket.cpp \
jni/src/network/clientopcodes.cpp \
jni/src/network/clientpackethandler.cpp \
jni/src/network/serveropcodes.cpp \
jni/src/network/serverpackethandler.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/cpp_api/s_async.cpp \
jni/src/script/cpp_api/s_base.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_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_base.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_mainmenu.cpp \
jni/src/script/lua_api/l_mapgen.cpp \
jni/src/script/lua_api/l_metadata.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_particles.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_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_game.cpp \
jni/src/script/scripting_mainmenu.cpp
#freetype2 support
LOCAL_SRC_FILES += jni/src/cguittfont/xCGUITTFont.cpp
# Lua
LOCAL_SRC_FILES += \
jni/src/lua/src/lapi.c \
jni/src/lua/src/lauxlib.c \
jni/src/lua/src/lbaselib.c \
jni/src/lua/src/lcode.c \
jni/src/lua/src/ldblib.c \
jni/src/lua/src/ldebug.c \
jni/src/lua/src/ldo.c \
jni/src/lua/src/ldump.c \
jni/src/lua/src/lfunc.c \
jni/src/lua/src/lgc.c \
jni/src/lua/src/linit.c \
jni/src/lua/src/liolib.c \
jni/src/lua/src/llex.c \
jni/src/lua/src/lmathlib.c \
jni/src/lua/src/lmem.c \
jni/src/lua/src/loadlib.c \
jni/src/lua/src/lobject.c \
jni/src/lua/src/lopcodes.c \
jni/src/lua/src/loslib.c \
jni/src/lua/src/lparser.c \
jni/src/lua/src/lstate.c \
jni/src/lua/src/lstring.c \
jni/src/lua/src/lstrlib.c \
jni/src/lua/src/ltable.c \
jni/src/lua/src/ltablib.c \
jni/src/lua/src/ltm.c \
jni/src/lua/src/lundump.c \
jni/src/lua/src/lvm.c \
jni/src/lua/src/lzio.c \
jni/src/lua/src/print.c
# SQLite3
LOCAL_SRC_FILES += deps/sqlite/sqlite3.c
# Threading
LOCAL_SRC_FILES += \
jni/src/threading/event.cpp \
jni/src/threading/mutex.cpp \
jni/src/threading/semaphore.cpp \
jni/src/threading/thread.cpp
# JSONCPP
LOCAL_SRC_FILES += jni/src/jsoncpp/json/jsoncpp.cpp
LOCAL_SHARED_LIBRARIES := iconv openal ogg vorbis gmp
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

@ -0,0 +1,9 @@
# NDK_TOOLCHAIN_VERSION := clang3.8
APP_PLATFORM := android-9
APP_MODULES := minetest
APP_STL := gnustl_static
APP_CPPFLAGS += -fexceptions
APP_GNUSTL_FORCE_CPP_FEATURES := rtti

View File

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

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

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

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

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

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

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

@ -0,0 +1,17 @@
--- sqlite3.c 2016-11-29 02:29:24.000000000 +0000
+++ sqlite3.c 2016-12-08 22:54:54.206465377 +0000
@@ -30445,7 +30445,14 @@
#if OS_VXWORKS
struct vxworksFileId *pId; /* Unique file ID for vxworks. */
#else
- ino_t ino; /* Inode number */
+ #ifdef ANDROID
+ // Bionic's struct stat has a 64 bit st_ino on both 32 and
+ // 64 bit architectures. ino_t remains 32 bits wide on 32 bit
+ // architectures and can lead to inode truncation.
+ unsigned long long ino; /* Inode number */
+ #else
+ ino_t ino; /* Inode number */
+ #endif
#endif
};

View File

@ -0,0 +1,2 @@
rootProject.name = "Minetest"

View File

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

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
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.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:icon="@drawable/irr_icon"
android:label="${project}"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:allowBackup="true">
<activity android:name=".MtNativeActivity"
android:label="${project}"
android:launchMode="singleTask"
android:configChanges="orientation|keyboard|keyboardHidden|navigation"
android:screenOrientation="sensorLandscape"
android:clearTaskOnLaunch="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="minetest" />
</activity>
<activity android:name=".MinetestTextEntry"
android:theme="@style/Theme.Transparent"
android:excludeFromRecents="true">
</activity>
<activity android:name=".MinetestAssetCopy"
android:theme="@style/Theme.Transparent"
android:excludeFromRecents="true">
</activity>
</application>
</manifest>

View File

@ -0,0 +1,416 @@
package net.minetest.minetest;
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.Vector;
import java.util.Iterator;
import java.lang.Object;
import android.app.Activity;
import android.content.res.AssetFileDescriptor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.Display;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.graphics.Rect;
import android.graphics.Paint;
import android.text.TextPaint;
public class MinetestAssetCopy extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.assetcopy);
m_ProgressBar = (ProgressBar) findViewById(R.id.progressBar1);
m_Filename = (TextView) 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();
}
}
/* 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;
}
ProgressBar m_ProgressBar;
TextView m_Filename;
copyAssetTask m_AssetCopy;
private class copyAssetTask extends AsyncTask<String, Integer, String>
{
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[1*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 al 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);
}
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;
}
}

View File

@ -0,0 +1,91 @@
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.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.widget.EditText;
public class MinetestTextEntry extends Activity {
public AlertDialog mTextInputDialog;
public EditText mTextInputWidget;
private final int MultiLineTextInput = 1;
private final int SingleLineTextInput = 2;
private final int SingleLinePasswordInput = 3;
@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

@ -0,0 +1,99 @@
package net.minetest.minetest;
import android.app.NativeActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.WindowManager;
public class MtNativeActivity extends NativeActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
m_MessagReturnCode = -1;
m_MessageReturnValue = "";
}
@Override
public void onDestroy() {
super.onDestroy();
}
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;
}
public static native void putMessageBoxResult(String text);
/* 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;
}
}
}
static {
System.loadLibrary("openal");
System.loadLibrary("ogg");
System.loadLibrary("vorbis");
System.loadLibrary("ssl");
System.loadLibrary("crypto");
System.loadLibrary("gmp");
System.loadLibrary("iconv");
// We don't have to load libminetest.so ourselves,
// but if we do, we get nicer logcat errors when
// loading fails.
System.loadLibrary("minetest");
}
private int m_MessagReturnCode;
private String m_MessageReturnValue;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ProgressBar
android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical" />
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="middle"
android:singleLine="true"
android:layout_gravity="center_horizontal"
android:text="@string/preparing_media"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="preparing_media">Preparing media...</string>
</resources>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Transparent" parent="android:Theme">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
</resources>

View File

@ -0,0 +1,17 @@
core.log("info", "Initializing Asynchronous environment")
function core.job_processor(serialized_func, serialized_param)
local func = loadstring(serialized_func)
local param = core.deserialize(serialized_param)
local retval = nil
if type(func) == "function" then
retval = core.serialize(func(param))
else
core.log("error", "ASYNC WORKER: Unable to deserialize function")
end
return retval or core.serialize(nil)
end

View File

@ -0,0 +1,40 @@
core.async_jobs = {}
local function handle_job(jobid, serialized_retval)
local retval = core.deserialize(serialized_retval)
assert(type(core.async_jobs[jobid]) == "function")
core.async_jobs[jobid](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
function core.handle_async(func, parameter, callback)
-- Serialize function
local serialized_func = string.dump(func)
assert(serialized_func ~= nil)
-- Serialize parameters
local serialized_param = core.serialize(parameter)
if serialized_param == nil then
return false
end
local jobid = core.do_async_callback(serialized_func, serialized_param)
core.async_jobs[jobid] = callback
return true
end

View File

@ -0,0 +1,317 @@
--Minetest
--Copyright (C) 2013 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--------------------------------------------------------------------------------
-- TODO improve doc --
-- TODO code cleanup --
-- Generic implementation of a filter/sortable list --
-- Usage: --
-- Filterlist needs to be initialized on creation. To achieve this you need to --
-- pass following functions: --
-- raw_fct() (mandatory): --
-- function returning a table containing the elements to be filtered --
-- compare_fct(element1,element2) (mandatory): --
-- function returning true/false if element1 is same element as element2 --
-- uid_match_fct(element1,uid) (optional) --
-- function telling if uid is attached to element1 --
-- filter_fct(element,filtercriteria) (optional) --
-- function returning true/false if filtercriteria met to element --
-- fetch_param (optional) --
-- parameter passed to raw_fct to aquire correct raw data --
-- --
--------------------------------------------------------------------------------
filterlist = {}
--------------------------------------------------------------------------------
function filterlist.refresh(self)
self.m_raw_list = self.m_raw_list_fct(self.m_fetch_param)
filterlist.process(self)
end
--------------------------------------------------------------------------------
function filterlist.create(raw_fct,compare_fct,uid_match_fct,filter_fct,fetch_param)
assert((raw_fct ~= nil) and (type(raw_fct) == "function"))
assert((compare_fct ~= nil) and (type(compare_fct) == "function"))
local self = {}
self.m_raw_list_fct = raw_fct
self.m_compare_fct = compare_fct
self.m_filter_fct = filter_fct
self.m_uid_match_fct = uid_match_fct
self.m_filtercriteria = nil
self.m_fetch_param = fetch_param
self.m_sortmode = "none"
self.m_sort_list = {}
self.m_processed_list = nil
self.m_raw_list = self.m_raw_list_fct(self.m_fetch_param)
self.add_sort_mechanism = filterlist.add_sort_mechanism
self.set_filtercriteria = filterlist.set_filtercriteria
self.get_filtercriteria = filterlist.get_filtercriteria
self.set_sortmode = filterlist.set_sortmode
self.get_list = filterlist.get_list
self.get_raw_list = filterlist.get_raw_list
self.get_raw_element = filterlist.get_raw_element
self.get_raw_index = filterlist.get_raw_index
self.get_current_index = filterlist.get_current_index
self.size = filterlist.size
self.uid_exists_raw = filterlist.uid_exists_raw
self.raw_index_by_uid = filterlist.raw_index_by_uid
self.refresh = filterlist.refresh
filterlist.process(self)
return self
end
--------------------------------------------------------------------------------
function filterlist.add_sort_mechanism(self,name,fct)
self.m_sort_list[name] = fct
end
--------------------------------------------------------------------------------
function filterlist.set_filtercriteria(self,criteria)
if criteria == self.m_filtercriteria and
type(criteria) ~= "table" then
return
end
self.m_filtercriteria = criteria
filterlist.process(self)
end
--------------------------------------------------------------------------------
function filterlist.get_filtercriteria(self)
return self.m_filtercriteria
end
--------------------------------------------------------------------------------
--supported sort mode "alphabetic|none"
function filterlist.set_sortmode(self,mode)
if (mode == self.m_sortmode) then
return
end
self.m_sortmode = mode
filterlist.process(self)
end
--------------------------------------------------------------------------------
function filterlist.get_list(self)
return self.m_processed_list
end
--------------------------------------------------------------------------------
function filterlist.get_raw_list(self)
return self.m_raw_list
end
--------------------------------------------------------------------------------
function filterlist.get_raw_element(self,idx)
if type(idx) ~= "number" then
idx = tonumber(idx)
end
if idx ~= nil and idx > 0 and idx <= #self.m_raw_list then
return self.m_raw_list[idx]
end
return nil
end
--------------------------------------------------------------------------------
function filterlist.get_raw_index(self,listindex)
assert(self.m_processed_list ~= nil)
if listindex ~= nil and listindex > 0 and
listindex <= #self.m_processed_list then
local entry = self.m_processed_list[listindex]
for i,v in ipairs(self.m_raw_list) do
if self.m_compare_fct(v,entry) then
return i
end
end
end
return 0
end
--------------------------------------------------------------------------------
function filterlist.get_current_index(self,listindex)
assert(self.m_processed_list ~= nil)
if listindex ~= nil and listindex > 0 and
listindex <= #self.m_raw_list then
local entry = self.m_raw_list[listindex]
for i,v in ipairs(self.m_processed_list) do
if self.m_compare_fct(v,entry) then
return i
end
end
end
return 0
end
--------------------------------------------------------------------------------
function filterlist.process(self)
assert(self.m_raw_list ~= nil)
if self.m_sortmode == "none" and
self.m_filtercriteria == nil then
self.m_processed_list = self.m_raw_list
return
end
self.m_processed_list = {}
for k,v in pairs(self.m_raw_list) do
if self.m_filtercriteria == nil or
self.m_filter_fct(v,self.m_filtercriteria) then
self.m_processed_list[#self.m_processed_list + 1] = v
end
end
if self.m_sortmode == "none" then
return
end
if self.m_sort_list[self.m_sortmode] ~= nil and
type(self.m_sort_list[self.m_sortmode]) == "function" then
self.m_sort_list[self.m_sortmode](self)
end
end
--------------------------------------------------------------------------------
function filterlist.size(self)
if self.m_processed_list == nil then
return 0
end
return #self.m_processed_list
end
--------------------------------------------------------------------------------
function filterlist.uid_exists_raw(self,uid)
for i,v in ipairs(self.m_raw_list) do
if self.m_uid_match_fct(v,uid) then
return true
end
end
return false
end
--------------------------------------------------------------------------------
function filterlist.raw_index_by_uid(self, uid)
local elementcount = 0
local elementidx = 0
for i,v in ipairs(self.m_raw_list) do
if self.m_uid_match_fct(v,uid) then
elementcount = elementcount +1
elementidx = i
end
end
-- If there are more elements than one with same name uid can't decide which
-- one is meant. self shouldn't be possible but just for sure.
if elementcount > 1 then
elementidx=0
end
return elementidx
end
--------------------------------------------------------------------------------
-- COMMON helper functions --
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function compare_worlds(world1,world2)
if world1.path ~= world2.path then
return false
end
if world1.name ~= world2.name then
return false
end
if world1.gameid ~= world2.gameid then
return false
end
return true
end
--------------------------------------------------------------------------------
function sort_worlds_alphabetic(self)
table.sort(self.m_processed_list, function(a, b)
--fixes issue #857 (crash due to sorting nil in worldlist)
if a == nil or b == nil then
if a == nil and b ~= nil then return false end
if b == nil and a ~= nil then return true end
return false
end
if a.name:lower() == b.name:lower() then
return a.name < b.name
end
return a.name:lower() < b.name:lower()
end)
end
--------------------------------------------------------------------------------
function sort_mod_list(self)
table.sort(self.m_processed_list, function(a, b)
-- Show game mods at bottom
if a.typ ~= b.typ then
return b.typ == "game_mod"
end
-- If in same or no modpack, sort by name
if a.modpack == b.modpack then
if a.name:lower() == b.name:lower() then
return a.name < b.name
end
return a.name:lower() < b.name:lower()
-- Else compare name to modpack name
else
-- Always show modpack pseudo-mod on top of modpack mod list
if a.name == b.modpack then
return true
elseif b.name == a.modpack then
return false
end
local name_a = a.modpack or a.name
local name_b = b.modpack or b.name
if name_a:lower() == name_b:lower() then
return name_a < name_b
end
return name_a:lower() < name_b:lower()
end
end)
end

View File

@ -0,0 +1,639 @@
-- Minetest: builtin/misc_helpers.lua
--------------------------------------------------------------------------------
-- Localize functions to avoid table lookups (better performance).
local string_sub, string_find = string.sub, string.find
--------------------------------------------------------------------------------
function basic_dump(o)
local tp = type(o)
if tp == "number" then
return tostring(o)
elseif tp == "string" then
return string.format("%q", o)
elseif tp == "boolean" then
return tostring(o)
elseif tp == "nil" then
return "nil"
-- Uncomment for full function dumping support.
-- Not currently enabled because bytecode isn't very human-readable and
-- dump's output is intended for humans.
--elseif tp == "function" then
-- return string.format("loadstring(%q)", string.dump(o))
else
return string.format("<%s>", tp)
end
end
local keywords = {
["and"] = true,
["break"] = true,
["do"] = true,
["else"] = true,
["elseif"] = true,
["end"] = true,
["false"] = true,
["for"] = true,
["function"] = true,
["goto"] = true, -- Lua 5.2
["if"] = true,
["in"] = true,
["local"] = true,
["nil"] = true,
["not"] = true,
["or"] = true,
["repeat"] = true,
["return"] = true,
["then"] = true,
["true"] = true,
["until"] = true,
["while"] = true,
}
local function is_valid_identifier(str)
if not str:find("^[a-zA-Z_][a-zA-Z0-9_]*$") or keywords[str] then
return false
end
return true
end
--------------------------------------------------------------------------------
-- Dumps values in a line-per-value format.
-- For example, {test = {"Testing..."}} becomes:
-- _["test"] = {}
-- _["test"][1] = "Testing..."
-- This handles tables as keys and circular references properly.
-- It also handles multiple references well, writing the table only once.
-- The dumped argument is internal-only.
function dump2(o, name, dumped)
name = name or "_"
-- "dumped" is used to keep track of serialized tables to handle
-- multiple references and circular tables properly.
-- It only contains tables as keys. The value is the name that
-- the table has in the dump, eg:
-- {x = {"y"}} -> dumped[{"y"}] = '_["x"]'
dumped = dumped or {}
if type(o) ~= "table" then
return string.format("%s = %s\n", name, basic_dump(o))
end
if dumped[o] then
return string.format("%s = %s\n", name, dumped[o])
end
dumped[o] = name
-- This contains a list of strings to be concatenated later (because
-- Lua is slow at individual concatenation).
local t = {}
for k, v in pairs(o) do
local keyStr
if type(k) == "table" then
if dumped[k] then
keyStr = dumped[k]
else
-- Key tables don't have a name, so use one of
-- the form _G["table: 0xFFFFFFF"]
keyStr = string.format("_G[%q]", tostring(k))
-- Dump key table
t[#t + 1] = dump2(k, keyStr, dumped)
end
else
keyStr = basic_dump(k)
end
local vname = string.format("%s[%s]", name, keyStr)
t[#t + 1] = dump2(v, vname, dumped)
end
return string.format("%s = {}\n%s", name, table.concat(t))
end
--------------------------------------------------------------------------------
-- This dumps values in a one-statement format.
-- For example, {test = {"Testing..."}} becomes:
-- [[{
-- test = {
-- "Testing..."
-- }
-- }]]
-- This supports tables as keys, but not circular references.
-- It performs poorly with multiple references as it writes out the full
-- table each time.
-- The indent field specifies a indentation string, it defaults to a tab.
-- Use the empty string to disable indentation.
-- The dumped and level arguments are internal-only.
function dump(o, indent, nested, level)
if type(o) ~= "table" then
return basic_dump(o)
end
-- Contains table -> true/nil of currently nested tables
nested = nested or {}
if nested[o] then
return "<circular reference>"
end
nested[o] = true
indent = indent or "\t"
level = level or 1
local t = {}
local dumped_indexes = {}
for i, v in ipairs(o) do
t[#t + 1] = dump(v, indent, nested, level + 1)
dumped_indexes[i] = true
end
for k, v in pairs(o) do
if not dumped_indexes[k] then
if type(k) ~= "string" or not is_valid_identifier(k) then
k = "["..dump(k, indent, nested, level + 1).."]"
end
v = dump(v, indent, nested, level + 1)
t[#t + 1] = k.." = "..v
end
end
nested[o] = nil
if indent ~= "" then
local indent_str = "\n"..string.rep(indent, level)
local end_indent_str = "\n"..string.rep(indent, level - 1)
return string.format("{%s%s%s}",
indent_str,
table.concat(t, ","..indent_str),
end_indent_str)
end
return "{"..table.concat(t, ", ").."}"
end
--------------------------------------------------------------------------------
function string.split(str, delim, include_empty, max_splits, sep_is_pattern)
delim = delim or ","
max_splits = max_splits or -1
local items = {}
local pos, len, seplen = 1, #str, #delim
local plain = not sep_is_pattern
max_splits = max_splits + 1
repeat
local np, npe = string_find(str, delim, pos, plain)
np, npe = (np or (len+1)), (npe or (len+1))
if (not np) or (max_splits == 1) then
np = len + 1
npe = np
end
local s = string_sub(str, pos, np - 1)
if include_empty or (s ~= "") then
max_splits = max_splits - 1
items[#items + 1] = s
end
pos = npe + 1
until (max_splits == 0) or (pos > (len + 1))
return items
end
--------------------------------------------------------------------------------
function table.indexof(list, val)
for i, v in ipairs(list) do
if v == val then
return i
end
end
return -1
end
assert(table.indexof({"foo", "bar"}, "foo") == 1)
assert(table.indexof({"foo", "bar"}, "baz") == -1)
--------------------------------------------------------------------------------
function file_exists(filename)
local f = io.open(filename, "r")
if f == nil then
return false
else
f:close()
return true
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
x = math.abs(x)
y = math.abs(y)
t = math.min(x, y)
x = math.max(x, y)
if x == 0 then return 0 end
t = t / x
return x * math.sqrt(1 + t * t)
end
--------------------------------------------------------------------------------
function math.sign(x, tolerance)
tolerance = tolerance or 0
if x > tolerance then
return 1
elseif x < -tolerance then
return -1
end
return 0
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,"\\","\\\\")
text = string.gsub(text,"%]","\\]")
text = string.gsub(text,"%[","\\[")
text = string.gsub(text,";","\\;")
text = string.gsub(text,",","\\,")
end
return text
end
function core.splittext(text,charlimit)
local retval = {}
local current_idx = 1
local start,stop = string_find(text, " ", current_idx)
local nl_start,nl_stop = string_find(text, "\n", current_idx)
local gotnewline = false
if nl_start ~= nil and (start == nil or nl_start < start) then
start = nl_start
stop = nl_stop
gotnewline = true
end
local last_line = ""
while start ~= nil do
if string.len(last_line) + (stop-start) > charlimit then
retval[#retval + 1] = last_line
last_line = ""
end
if last_line ~= "" then
last_line = last_line .. " "
end
last_line = last_line .. string_sub(text, current_idx, stop - 1)
if gotnewline then
retval[#retval + 1] = last_line
last_line = ""
gotnewline = false
end
current_idx = stop+1
start,stop = string_find(text, " ", current_idx)
nl_start,nl_stop = string_find(text, "\n", current_idx)
if nl_start ~= nil and (start == nil or nl_start < start) then
start = nl_start
stop = nl_stop
gotnewline = true
end
end
--add last part of text
if string.len(last_line) + (string.len(text) - current_idx) > charlimit then
retval[#retval + 1] = last_line
retval[#retval + 1] = string_sub(text, current_idx)
else
last_line = last_line .. " " .. string_sub(text, current_idx)
retval[#retval + 1] = last_line
end
return retval
end
--------------------------------------------------------------------------------
if INIT == "game" then
local dirs1 = {9, 18, 7, 12}
local dirs2 = {20, 23, 22, 21}
function core.rotate_and_place(itemstack, placer, pointed_thing,
infinitestacks, orient_flags)
orient_flags = orient_flags or {}
local unode = core.get_node_or_nil(pointed_thing.under)
if not unode then
return
end
local undef = core.registered_nodes[unode.name]
if undef and undef.on_rightclick then
undef.on_rightclick(pointed_thing.under, unode, placer,
itemstack, pointed_thing)
return
end
local fdir = core.dir_to_facedir(placer:get_look_dir())
local wield_name = itemstack:get_name()
local above = pointed_thing.above
local under = pointed_thing.under
local iswall = (above.y == under.y)
local isceiling = not iswall and (above.y < under.y)
local anode = core.get_node_or_nil(above)
if not anode then
return
end
local pos = pointed_thing.above
local node = anode
if undef and undef.buildable_to then
pos = pointed_thing.under
node = unode
iswall = false
end
if core.is_protected(pos, placer:get_player_name()) then
core.record_protection_violation(pos,
placer:get_player_name())
return
end
local ndef = core.registered_nodes[node.name]
if not ndef or not ndef.buildable_to then
return
end
if orient_flags.force_floor then
iswall = false
isceiling = false
elseif orient_flags.force_ceiling then
iswall = false
isceiling = true
elseif orient_flags.force_wall then
iswall = true
isceiling = false
elseif orient_flags.invert_wall then
iswall = not iswall
end
if iswall then
core.set_node(pos, {name = wield_name,
param2 = dirs1[fdir + 1]})
elseif isceiling then
if orient_flags.force_facedir then
core.set_node(pos, {name = wield_name,
param2 = 20})
else
core.set_node(pos, {name = wield_name,
param2 = dirs2[fdir + 1]})
end
else -- place right side up
if orient_flags.force_facedir then
core.set_node(pos, {name = wield_name,
param2 = 0})
else
core.set_node(pos, {name = wield_name,
param2 = fdir})
end
end
if not infinitestacks then
itemstack:take_item()
return itemstack
end
end
--------------------------------------------------------------------------------
--Wrapper for rotate_and_place() to check for sneak and assume Creative mode
--implies infinite stacks when performing a 6d rotation.
--------------------------------------------------------------------------------
core.rotate_node = function(itemstack, placer, pointed_thing)
core.rotate_and_place(itemstack, placer, pointed_thing,
core.setting_getbool("creative_mode"),
{invert_wall = placer:get_player_control().sneak})
return itemstack
end
end
--------------------------------------------------------------------------------
function core.explode_table_event(evt)
if evt ~= nil then
local parts = evt:split(":")
if #parts == 3 then
local t = parts[1]:trim()
local r = tonumber(parts[2]:trim())
local c = tonumber(parts[3]:trim())
if type(r) == "number" and type(c) == "number"
and t ~= "INV" then
return {type=t, row=r, column=c}
end
end
end
return {type="INV", row=0, column=0}
end
--------------------------------------------------------------------------------
function core.explode_textlist_event(evt)
if evt ~= nil then
local parts = evt:split(":")
if #parts == 2 then
local t = parts[1]:trim()
local r = tonumber(parts[2]:trim())
if type(r) == "number" and t ~= "INV" then
return {type=t, index=r}
end
end
end
return {type="INV", index=0}
end
--------------------------------------------------------------------------------
function core.explode_scrollbar_event(evt)
local retval = core.explode_textlist_event(evt)
retval.value = retval.index
retval.index = nil
return retval
end
--------------------------------------------------------------------------------
function core.pos_to_string(pos, decimal_places)
local x = pos.x
local y = pos.y
local z = pos.z
if decimal_places ~= nil then
x = string.format("%." .. decimal_places .. "f", x)
y = string.format("%." .. decimal_places .. "f", y)
z = string.format("%." .. decimal_places .. "f", z)
end
return "(" .. x .. "," .. y .. "," .. z .. ")"
end
--------------------------------------------------------------------------------
function core.string_to_pos(value)
if value == nil then
return nil
end
local p = {}
p.x, p.y, p.z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
if p.x and p.y and p.z then
p.x = tonumber(p.x)
p.y = tonumber(p.y)
p.z = tonumber(p.z)
return p
end
local p = {}
p.x, p.y, p.z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$")
if p.x and p.y and p.z then
p.x = tonumber(p.x)
p.y = tonumber(p.y)
p.z = tonumber(p.z)
return p
end
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)
local p1, p2 = unpack(value:split(") ("))
if p1 == nil or p2 == nil then
return nil
end
p1 = core.string_to_pos(p1 .. ")")
p2 = core.string_to_pos("(" .. p2)
if p1 == nil or p2 == nil then
return nil
end
return p1, p2
end
local function test_string_to_area()
local p1, p2 = core.string_to_area("(10.0, 5, -2) ( 30.2, 4, -12.53)")
assert(p1.x == 10.0 and p1.y == 5 and p1.z == -2)
assert(p2.x == 30.2 and p2.y == 4 and p2.z == -12.53)
p1, p2 = core.string_to_area("(10.0, 5, -2 30.2, 4, -12.53")
assert(p1 == nil and p2 == nil)
p1, p2 = core.string_to_area("(10.0, 5,) -2 fgdf2, 4, -12.53")
assert(p1 == nil and p2 == nil)
end
test_string_to_area()
--------------------------------------------------------------------------------
function table.copy(t, seen)
local n = {}
seen = seen or {}
seen[t] = n
for k, v in pairs(t) do
n[(type(k) == "table" and (seen[k] or table.copy(k, seen))) or k] =
(type(v) == "table" and (seen[v] or table.copy(v, seen))) or v
end
return n
end
--------------------------------------------------------------------------------
-- mainmenu only functions
--------------------------------------------------------------------------------
if INIT == "mainmenu" then
function core.get_game(index)
local games = game.get_games()
if index > 0 and index <= #games then
return games[index]
end
return nil
end
function fgettext_ne(text, ...)
text = core.gettext(text)
local arg = {n=select('#', ...), ...}
if arg.n >= 1 then
-- Insert positional parameters ($1, $2, ...)
local result = ''
local pos = 1
while pos <= text:len() do
local newpos = text:find('[$]', pos)
if newpos == nil then
result = result .. text:sub(pos)
pos = text:len() + 1
else
local paramindex =
tonumber(text:sub(newpos+1, newpos+1))
result = result .. text:sub(pos, newpos-1)
.. tostring(arg[paramindex])
pos = newpos + 2
end
end
text = result
end
return text
end
function fgettext(text, ...)
return core.formspec_escape(fgettext_ne(text, ...))
end
end

View File

@ -0,0 +1,217 @@
--- Lua module to serialize values as Lua code.
-- From: https://github.com/fab13n/metalua/blob/no-dll/src/lib/serialize.lua
-- License: MIT
-- @copyright 2006-2997 Fabien Fleutot <metalua@gmail.com>
-- @author Fabien Fleutot <metalua@gmail.com>
-- @author ShadowNinja <shadowninja@minetest.net>
--------------------------------------------------------------------------------
--- Serialize an object into a source code string. This string, when passed as
-- an argument to deserialize(), returns an object structurally identical to
-- the original one. The following are currently supported:
-- * Booleans, numbers, strings, and nil.
-- * Functions; uses interpreter-dependent (and sometimes platform-dependent) bytecode!
-- * Tables; they can cantain multiple references and can be recursive, but metatables aren't saved.
-- This works in two phases:
-- 1. Recursively find and record multiple references and recursion.
-- 2. Recursively dump the value into a string.
-- @param x Value to serialize (nil is allowed).
-- @return load()able string containing the value.
function core.serialize(x)
local local_index = 1 -- Top index of the "_" local table in the dump
-- table->nil/1/2 set of tables seen.
-- nil = not seen, 1 = seen once, 2 = seen multiple times.
local seen = {}
-- nest_points are places where a table appears within itself, directly
-- or not. For instance, all of these chunks create nest points in
-- table x: "x = {}; x[x] = 1", "x = {}; x[1] = x",
-- "x = {}; x[1] = {y = {x}}".
-- To handle those, two tables are used by mark_nest_point:
-- * nested - Transient set of tables being currently traversed.
-- Used for detecting nested tables.
-- * nest_points - parent->{key=value, ...} table cantaining the nested
-- keys and values in the parent. They're all dumped after all the
-- other table operations have been performed.
--
-- mark_nest_point(p, k, v) fills nest_points with information required
-- to remember that key/value (k, v) creates a nest point in table
-- parent. It also marks "parent" and the nested item(s) as occuring
-- multiple times, since several references to it will be required in
-- order to patch the nest points.
local nest_points = {}
local nested = {}
local function mark_nest_point(parent, k, v)
local nk, nv = nested[k], nested[v]
local np = nest_points[parent]
if not np then
np = {}
nest_points[parent] = np
end
np[k] = v
seen[parent] = 2
if nk then seen[k] = 2 end
if nv then seen[v] = 2 end
end
-- First phase, list the tables and functions which appear more than
-- once in x.
local function mark_multiple_occurences(x)
local tp = type(x)
if tp ~= "table" and tp ~= "function" then
-- No identity (comparison is done by value, not by instance)
return
end
if seen[x] == 1 then
seen[x] = 2
elseif seen[x] ~= 2 then
seen[x] = 1
end
if tp == "table" then
nested[x] = true
for k, v in pairs(x) do
if nested[k] or nested[v] then
mark_nest_point(x, k, v)
else
mark_multiple_occurences(k)
mark_multiple_occurences(v)
end
end
nested[x] = nil
end
end
local dumped = {} -- object->varname set
local local_defs = {} -- Dumped local definitions as source code lines
-- Mutually recursive local functions:
local dump_val, dump_or_ref_val
-- If x occurs multiple times, dump the local variable rather than
-- the value. If it's the first time it's dumped, also dump the
-- content in local_defs.
function dump_or_ref_val(x)
if seen[x] ~= 2 then
return dump_val(x)
end
local var = dumped[x]
if var then -- Already referenced
return var
end
-- First occurence, create and register reference
local val = dump_val(x)
local i = local_index
local_index = local_index + 1
var = "_["..i.."]"
local_defs[#local_defs + 1] = var.." = "..val
dumped[x] = var
return var
end
-- Second phase. Dump the object; subparts occuring multiple times
-- are dumped in local variables which can be referenced multiple
-- times. Care is taken to dump local vars in a sensible order.
function dump_val(x)
local tp = type(x)
if x == nil then return "nil"
elseif tp == "string" then return string.format("%q", x)
elseif tp == "boolean" then return x and "true" or "false"
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
elseif tp == "table" then
local vals = {}
local idx_dumped = {}
local np = nest_points[x]
for i, v in ipairs(x) do
if not np or not np[i] then
vals[#vals + 1] = dump_or_ref_val(v)
end
idx_dumped[i] = true
end
for k, v in pairs(x) do
if (not np or not np[k]) and
not idx_dumped[k] then
vals[#vals + 1] = "["..dump_or_ref_val(k).."] = "
..dump_or_ref_val(v)
end
end
return "{"..table.concat(vals, ", ").."}"
else
error("Can't serialize data of type "..tp)
end
end
local function dump_nest_points()
for parent, vals in pairs(nest_points) do
for k, v in pairs(vals) do
local_defs[#local_defs + 1] = dump_or_ref_val(parent)
.."["..dump_or_ref_val(k).."] = "
..dump_or_ref_val(v)
end
end
end
mark_multiple_occurences(x)
local top_level = dump_or_ref_val(x)
dump_nest_points()
if next(local_defs) then
return "local _ = {}\n"
..table.concat(local_defs, "\n")
.."\nreturn "..top_level
else
return "return "..top_level
end
end
-- Deserialization
local env = {
loadstring = loadstring,
}
local safe_env = {
loadstring = function() end,
}
function core.deserialize(str, safe)
if str:byte(1) == 0x1B then
return nil, "Bytecode prohibited"
end
local f, err = loadstring(str)
if not f then return nil, err end
setfenv(f, safe and safe_env or env)
local good, data = pcall(f)
if good then
return data
else
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,56 @@
-- Always warn when creating a global variable, even outside of a function.
-- This ignores mod namespaces (variables with the same name as the current mod).
local WARN_INIT = false
function core.global_exists(name)
if type(name) ~= "string" then
error("core.global_exists: " .. tostring(name) .. " is not a string")
end
return rawget(_G, name) ~= nil
end
local meta = {}
local declared = {}
-- Key is source file, line, and variable name; seperated by NULs
local warned = {}
function meta:__newindex(name, value)
local info = debug.getinfo(2, "Sl")
local desc = ("%s:%d"):format(info.short_src, info.currentline)
if not declared[name] then
local warn_key = ("%s\0%d\0%s"):format(info.source,
info.currentline, name)
if not warned[warn_key] and info.what ~= "main" and
info.what ~= "C" then
core.log("warning", ("Assignment to undeclared "..
"global %q inside a function at %s.")
:format(name, desc))
warned[warn_key] = true
end
declared[name] = true
end
-- Ignore mod namespaces
if WARN_INIT and name ~= core.get_current_modname() then
core.log("warning", ("Global variable %q created at %s.")
:format(name, desc))
end
rawset(self, name, value)
end
function meta:__index(name)
local info = debug.getinfo(2, "Sl")
local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
if not declared[name] and not warned[warn_key] and info.what ~= "C" then
core.log("warning", ("Undeclared global variable %q accessed at %s:%s")
:format(name, info.short_src, info.currentline))
warned[warn_key] = true
end
return rawget(self, name)
end
setmetatable(_G, meta)

View File

@ -0,0 +1,145 @@
vector = {}
function vector.new(a, b, c)
if type(a) == "table" then
assert(a.x and a.y and a.z, "Invalid vector passed to vector.new()")
return {x=a.x, y=a.y, z=a.z}
elseif a then
assert(b and c, "Invalid arguments for vector.new()")
return {x=a, y=b, z=c}
end
return {x=0, y=0, z=0}
end
function vector.equals(a, b)
return a.x == b.x and
a.y == b.y and
a.z == b.z
end
function vector.length(v)
return math.hypot(v.x, math.hypot(v.y, v.z))
end
function vector.normalize(v)
local len = vector.length(v)
if len == 0 then
return {x=0, y=0, z=0}
else
return vector.divide(v, len)
end
end
function vector.floor(v)
return {
x = math.floor(v.x),
y = math.floor(v.y),
z = math.floor(v.z)
}
end
function vector.round(v)
return {
x = math.floor(v.x + 0.5),
y = math.floor(v.y + 0.5),
z = math.floor(v.z + 0.5)
}
end
function vector.apply(v, func)
return {
x = func(v.x),
y = func(v.y),
z = func(v.z)
}
end
function vector.distance(a, b)
local x = a.x - b.x
local y = a.y - b.y
local z = a.z - b.z
return math.hypot(x, math.hypot(y, z))
end
function vector.direction(pos1, pos2)
local x_raw = pos2.x - pos1.x
local y_raw = pos2.y - pos1.y
local z_raw = pos2.z - pos1.z
local x_abs = math.abs(x_raw)
local y_abs = math.abs(y_raw)
local z_abs = math.abs(z_raw)
if x_abs >= y_abs and
x_abs >= z_abs then
y_raw = y_raw * (1 / x_abs)
z_raw = z_raw * (1 / x_abs)
x_raw = x_raw / x_abs
end
if y_abs >= x_abs and
y_abs >= z_abs then
x_raw = x_raw * (1 / y_abs)
z_raw = z_raw * (1 / y_abs)
y_raw = y_raw / y_abs
end
if z_abs >= y_abs and
z_abs >= x_abs then
x_raw = x_raw * (1 / z_abs)
y_raw = y_raw * (1 / z_abs)
z_raw = z_raw / z_abs
end
return {x=x_raw, y=y_raw, z=z_raw}
end
function vector.add(a, b)
if type(b) == "table" then
return {x = a.x + b.x,
y = a.y + b.y,
z = a.z + b.z}
else
return {x = a.x + b,
y = a.y + b,
z = a.z + b}
end
end
function vector.subtract(a, b)
if type(b) == "table" then
return {x = a.x - b.x,
y = a.y - b.y,
z = a.z - b.z}
else
return {x = a.x - b,
y = a.y - b,
z = a.z - b}
end
end
function vector.multiply(a, b)
if type(b) == "table" then
return {x = a.x * b.x,
y = a.y * b.y,
z = a.z * b.z}
else
return {x = a.x * b,
y = a.y * b,
z = a.z * b}
end
end
function vector.divide(a, b)
if type(b) == "table" then
return {x = a.x / b.x,
y = a.y / b.y,
z = a.z / b.z}
else
return {x = a.x / b,
y = a.y / b,
z = a.z / b}
end
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

View File

@ -0,0 +1,215 @@
--Minetest
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local function buttonbar_formspec(self)
if self.hidden then
return ""
end
local formspec = string.format("box[%f,%f;%f,%f;%s]",
self.pos.x,self.pos.y ,self.size.x,self.size.y,self.bgcolor)
for i=self.startbutton,#self.buttons,1 do
local btn_name = self.buttons[i].name
local btn_pos = {}
if self.orientation == "horizontal" then
btn_pos.x = self.pos.x + --base pos
(i - self.startbutton) * self.btn_size + --button offset
self.btn_initial_offset
else
btn_pos.x = self.pos.x + (self.btn_size * 0.05)
end
if self.orientation == "vertical" then
btn_pos.y = self.pos.y + --base pos
(i - self.startbutton) * self.btn_size + --button offset
self.btn_initial_offset
else
btn_pos.y = self.pos.y + (self.btn_size * 0.05)
end
if (self.orientation == "vertical" and
(btn_pos.y + self.btn_size <= self.pos.y + self.size.y)) or
(self.orientation == "horizontal" and
(btn_pos.x + self.btn_size <= self.pos.x + self.size.x)) then
local borders="true"
if self.buttons[i].image ~= nil then
borders="false"
end
formspec = formspec ..
string.format("image_button[%f,%f;%f,%f;%s;%s;%s;true;%s]tooltip[%s;%s]",
btn_pos.x, btn_pos.y, self.btn_size, self.btn_size,
self.buttons[i].image, btn_name, self.buttons[i].caption,
borders, btn_name, self.buttons[i].tooltip)
else
--print("end of displayable buttons: orientation: " .. self.orientation)
--print( "button_end: " .. (btn_pos.y + self.btn_size - (self.btn_size * 0.05)))
--print( "bar_end: " .. (self.pos.x + self.size.x))
break
end
end
if (self.have_move_buttons) then
local btn_dec_pos = {}
btn_dec_pos.x = self.pos.x + (self.btn_size * 0.05)
btn_dec_pos.y = self.pos.y + (self.btn_size * 0.05)
local btn_inc_pos = {}
local btn_size = {}
if self.orientation == "horizontal" then
btn_size.x = 0.5
btn_size.y = self.btn_size
btn_inc_pos.x = self.pos.x + self.size.x - 0.5
btn_inc_pos.y = self.pos.y + (self.btn_size * 0.05)
else
btn_size.x = self.btn_size
btn_size.y = 0.5
btn_inc_pos.x = self.pos.x + (self.btn_size * 0.05)
btn_inc_pos.y = self.pos.y + self.size.y - 0.5
end
local text_dec = "<"
local text_inc = ">"
if self.orientation == "vertical" then
text_dec = "^"
text_inc = "v"
end
formspec = formspec ..
string.format("image_button[%f,%f;%f,%f;;btnbar_dec_%s;%s;true;true]",
btn_dec_pos.x, btn_dec_pos.y, btn_size.x, btn_size.y,
self.name, text_dec)
formspec = formspec ..
string.format("image_button[%f,%f;%f,%f;;btnbar_inc_%s;%s;true;true]",
btn_inc_pos.x, btn_inc_pos.y, btn_size.x, btn_size.y,
self.name, text_inc)
end
return formspec
end
local function buttonbar_buttonhandler(self, fields)
if fields["btnbar_inc_" .. self.name] ~= nil and
self.startbutton < #self.buttons then
self.startbutton = self.startbutton + 1
return true
end
if fields["btnbar_dec_" .. self.name] ~= nil and self.startbutton > 1 then
self.startbutton = self.startbutton - 1
return true
end
for i=1,#self.buttons,1 do
if fields[self.buttons[i].name] ~= nil then
return self.userbuttonhandler(fields)
end
end
end
local buttonbar_metatable = {
handle_buttons = buttonbar_buttonhandler,
handle_events = function(self, event) end,
get_formspec = buttonbar_formspec,
hide = function(self) self.hidden = true end,
show = function(self) self.hidden = false end,
delete = function(self) ui.delete(self) end,
add_button = function(self, name, caption, image, tooltip)
if caption == nil then caption = "" end
if image == nil then image = "" end
if tooltip == nil then tooltip = "" end
self.buttons[#self.buttons + 1] = {
name = name,
caption = caption,
image = image,
tooltip = tooltip
}
if self.orientation == "horizontal" then
if ( (self.btn_size * #self.buttons) + (self.btn_size * 0.05 *2)
> self.size.x ) then
self.btn_initial_offset = self.btn_size * 0.05 + 0.5
self.have_move_buttons = true
end
else
if ((self.btn_size * #self.buttons) + (self.btn_size * 0.05 *2)
> self.size.y ) then
self.btn_initial_offset = self.btn_size * 0.05 + 0.5
self.have_move_buttons = true
end
end
end,
set_bgparams = function(self, bgcolor)
if (type(bgcolor) == "string") then
self.bgcolor = bgcolor
end
end,
}
buttonbar_metatable.__index = buttonbar_metatable
function buttonbar_create(name, cbf_buttonhandler, pos, orientation, size)
assert(name ~= nil)
assert(cbf_buttonhandler ~= nil)
assert(orientation == "vertical" or orientation == "horizontal")
assert(pos ~= nil and type(pos) == "table")
assert(size ~= nil and type(size) == "table")
local self = {}
self.name = name
self.type = "addon"
self.bgcolor = "#000000"
self.pos = pos
self.size = size
self.orientation = orientation
self.startbutton = 1
self.have_move_buttons = false
self.hidden = false
if self.orientation == "horizontal" then
self.btn_size = self.size.y
else
self.btn_size = self.size.x
end
if (self.btn_initial_offset == nil) then
self.btn_initial_offset = self.btn_size * 0.05
end
self.userbuttonhandler = cbf_buttonhandler
self.buttons = {}
setmetatable(self,buttonbar_metatable)
ui.add(self)
return self
end

View File

@ -0,0 +1,69 @@
--Minetest
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--this program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local function dialog_event_handler(self,event)
if self.user_eventhandler == nil or
self.user_eventhandler(event) == false then
--close dialog on esc
if event == "MenuQuit" then
self:delete()
return true
end
end
end
local dialog_metatable = {
eventhandler = dialog_event_handler,
get_formspec = function(self)
if not self.hidden then return self.formspec(self.data) end
end,
handle_buttons = function(self,fields)
if not self.hidden then return self.buttonhandler(self,fields) end
end,
handle_events = function(self,event)
if not self.hidden then return self.eventhandler(self,event) end
end,
hide = function(self) self.hidden = true end,
show = function(self) self.hidden = false end,
delete = function(self)
if self.parent ~= nil then
self.parent:show()
end
ui.delete(self)
end,
set_parent = function(self,parent) self.parent = parent end
}
dialog_metatable.__index = dialog_metatable
function dialog_create(name,get_formspec,buttonhandler,eventhandler)
local self = {}
self.name = name
self.type = "toplevel"
self.hidden = true
self.data = {}
self.formspec = get_formspec
self.buttonhandler = buttonhandler
self.user_eventhandler = eventhandler
setmetatable(self,dialog_metatable)
ui.add(self)
return self
end

View File

@ -0,0 +1,273 @@
--Minetest
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--------------------------------------------------------------------------------
-- A tabview implementation --
-- Usage: --
-- tabview.create: returns initialized tabview raw element --
-- element.add(tab): add a tab declaration --
-- element.handle_buttons() --
-- element.handle_events() --
-- element.getFormspec() returns formspec of tabview --
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local function add_tab(self,tab)
assert(tab.size == nil or (type(tab.size) == table and
tab.size.x ~= nil and tab.size.y ~= nil))
assert(tab.cbf_formspec ~= nil and type(tab.cbf_formspec) == "function")
assert(tab.cbf_button_handler == nil or
type(tab.cbf_button_handler) == "function")
assert(tab.cbf_events == nil or type(tab.cbf_events) == "function")
local newtab = {
name = tab.name,
caption = tab.caption,
button_handler = tab.cbf_button_handler,
event_handler = tab.cbf_events,
get_formspec = tab.cbf_formspec,
tabsize = tab.tabsize,
on_change = tab.on_change,
tabdata = {},
}
self.tablist[#self.tablist + 1] = newtab
if self.last_tab_index == #self.tablist then
self.current_tab = tab.name
if tab.on_activate ~= nil then
tab.on_activate(nil,tab.name)
end
end
end
--------------------------------------------------------------------------------
local function get_formspec(self)
local formspec = ""
if not self.hidden and (self.parent == nil or not self.parent.hidden) then
if self.parent == nil then
local tsize = self.tablist[self.last_tab_index].tabsize or
{width=self.width, height=self.height}
formspec = formspec ..
string.format("size[%f,%f,%s]",tsize.width,tsize.height,
dump(self.fixed_size))
end
formspec = formspec .. self:tab_header()
formspec = formspec ..
self.tablist[self.last_tab_index].get_formspec(
self,
self.tablist[self.last_tab_index].name,
self.tablist[self.last_tab_index].tabdata,
self.tablist[self.last_tab_index].tabsize
)
end
return formspec
end
--------------------------------------------------------------------------------
local function handle_buttons(self,fields)
if self.hidden then
return false
end
if self:handle_tab_buttons(fields) then
return true
end
if self.glb_btn_handler ~= nil and
self.glb_btn_handler(self,fields) then
return true
end
if self.tablist[self.last_tab_index].button_handler ~= nil then
return
self.tablist[self.last_tab_index].button_handler(
self,
fields,
self.tablist[self.last_tab_index].name,
self.tablist[self.last_tab_index].tabdata
)
end
return false
end
--------------------------------------------------------------------------------
local function handle_events(self,event)
if self.hidden then
return false
end
if self.glb_evt_handler ~= nil and
self.glb_evt_handler(self,event) then
return true
end
if self.tablist[self.last_tab_index].evt_handler ~= nil then
return
self.tablist[self.last_tab_index].evt_handler(
self,
event,
self.tablist[self.last_tab_index].name,
self.tablist[self.last_tab_index].tabdata
)
end
return false
end
--------------------------------------------------------------------------------
local function tab_header(self)
local toadd = ""
for i=1,#self.tablist,1 do
if toadd ~= "" then
toadd = toadd .. ","
end
toadd = toadd .. self.tablist[i].caption
end
return string.format("tabheader[%f,%f;%s;%s;%i;true;false]",
self.header_x, self.header_y, self.name, toadd, self.last_tab_index);
end
--------------------------------------------------------------------------------
local function switch_to_tab(self, index)
--first call on_change for tab to leave
if self.tablist[self.last_tab_index].on_change ~= nil then
self.tablist[self.last_tab_index].on_change("LEAVE",
self.current_tab, self.tablist[index].name)
end
--update tabview data
self.last_tab_index = index
local old_tab = self.current_tab
self.current_tab = self.tablist[index].name
if (self.autosave_tab) then
core.setting_set(self.name .. "_LAST",self.current_tab)
end
-- call for tab to enter
if self.tablist[index].on_change ~= nil then
self.tablist[index].on_change("ENTER",
old_tab,self.current_tab)
end
end
--------------------------------------------------------------------------------
local function handle_tab_buttons(self,fields)
--save tab selection to config file
if fields[self.name] then
local index = tonumber(fields[self.name])
switch_to_tab(self, index)
return true
end
return false
end
--------------------------------------------------------------------------------
local function set_tab_by_name(self, name)
for i=1,#self.tablist,1 do
if self.tablist[i].name == name then
switch_to_tab(self, i)
return true
end
end
return false
end
--------------------------------------------------------------------------------
local function hide_tabview(self)
self.hidden=true
--call on_change as we're not gonna show self tab any longer
if self.tablist[self.last_tab_index].on_change ~= nil then
self.tablist[self.last_tab_index].on_change("LEAVE",
self.current_tab, nil)
end
end
--------------------------------------------------------------------------------
local function show_tabview(self)
self.hidden=false
-- call for tab to enter
if self.tablist[self.last_tab_index].on_change ~= nil then
self.tablist[self.last_tab_index].on_change("ENTER",
nil,self.current_tab)
end
end
local tabview_metatable = {
add = add_tab,
handle_buttons = handle_buttons,
handle_events = handle_events,
get_formspec = get_formspec,
show = show_tabview,
hide = hide_tabview,
delete = function(self) ui.delete(self) end,
set_parent = function(self,parent) self.parent = parent end,
set_autosave_tab =
function(self,value) self.autosave_tab = value end,
set_tab = set_tab_by_name,
set_global_button_handler =
function(self,handler) self.glb_btn_handler = handler end,
set_global_event_handler =
function(self,handler) self.glb_evt_handler = handler end,
set_fixed_size =
function(self,state) self.fixed_size = state end,
tab_header = tab_header,
handle_tab_buttons = handle_tab_buttons
}
tabview_metatable.__index = tabview_metatable
--------------------------------------------------------------------------------
function tabview_create(name, size, tabheaderpos)
local self = {}
self.name = name
self.type = "toplevel"
self.width = size.x
self.height = size.y
self.header_x = tabheaderpos.x
self.header_y = tabheaderpos.y
setmetatable(self, tabview_metatable)
self.fixed_size = true
self.hidden = true
self.current_tab = nil
self.last_tab_index = 1
self.tablist = {}
self.autosave_tab = false
ui.add(self)
return self
end

View File

@ -0,0 +1,208 @@
--Minetest
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
ui = {}
ui.childlist = {}
ui.default = nil
--------------------------------------------------------------------------------
function ui.add(child)
--TODO check child
ui.childlist[child.name] = child
return child.name
end
--------------------------------------------------------------------------------
function ui.delete(child)
if ui.childlist[child.name] == nil then
return false
end
ui.childlist[child.name] = nil
return true
end
--------------------------------------------------------------------------------
function ui.set_default(name)
ui.default = name
end
--------------------------------------------------------------------------------
function ui.find_by_name(name)
return ui.childlist[name]
end
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Internal functions not to be called from user
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
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 = ""
-- 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") .. "]"
elseif gamedata ~= nil and gamedata.errormessage ~= nil then
formspec = wordwrap_quickhack(gamedata.errormessage)
local error_title
if string.find(gamedata.errormessage, "ModError") then
error_title = fgettext("An error occured in a Lua script, such as a mod:")
else
error_title = fgettext("An error occured:")
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") .. "]"
else
local active_toplevel_ui_elements = 0
for key,value in pairs(ui.childlist) do
if (value.type == "toplevel") then
local retval = value:get_formspec()
if retval ~= nil and retval ~= "" then
active_toplevel_ui_elements = active_toplevel_ui_elements +1
formspec = formspec .. retval
end
end
end
-- no need to show addons if there ain't a toplevel element
if (active_toplevel_ui_elements > 0) then
for key,value in pairs(ui.childlist) do
if (value.type == "addon") then
local retval = value:get_formspec()
if retval ~= nil and retval ~= "" then
formspec = formspec .. retval
end
end
end
end
if (active_toplevel_ui_elements > 1) then
core.log("warning", "more than one active ui "..
"element, self most likely isn't intended")
end
if (active_toplevel_ui_elements == 0) then
core.log("warning", "no toplevel ui element "..
"active; switching to default")
ui.childlist[ui.default]:show()
formspec = ui.childlist[ui.default]:get_formspec()
end
end
core.update_formspec(formspec)
end
--------------------------------------------------------------------------------
function ui.handle_buttons(fields)
for key,value in pairs(ui.childlist) do
local retval = value:handle_buttons(fields)
if retval then
ui.update()
return
end
end
end
--------------------------------------------------------------------------------
function ui.handle_events(event)
for key,value in pairs(ui.childlist) do
if value.handle_events ~= nil then
local retval = value:handle_events(event)
if retval then
return retval
end
end
end
end
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- initialize callbacks
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
core.button_handler = function(fields)
if fields["btn_reconnect_yes"] then
gamedata.reconnect_requested = false
gamedata.errormessage = nil
gamedata.do_reconnect = true
core.start()
return
elseif fields["btn_reconnect_no"] or fields["btn_error_confirm"] then
gamedata.errormessage = nil
gamedata.reconnect_requested = false
ui.update()
return
end
if ui.handle_buttons(fields) then
ui.update()
end
end
--------------------------------------------------------------------------------
core.event_handler = function(event)
if ui.handle_events(event) then
ui.update()
return
end
if event == "Refresh" then
ui.update()
return
end
end

View File

@ -0,0 +1,217 @@
-- Minetest: builtin/auth.lua
--
-- Authentication handler
--
function core.string_to_privs(str, delim)
assert(type(str) == "string")
delim = delim or ','
local privs = {}
for _, priv in pairs(string.split(str, delim)) do
privs[priv:trim()] = true
end
return privs
end
function core.privs_to_string(privs, delim)
assert(type(privs) == "table")
delim = delim or ','
local list = {}
for priv, bool in pairs(privs) do
if bool then
list[#list + 1] = priv
end
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")
core.auth_file_path = core.get_worldpath().."/auth.txt"
core.auth_table = {}
local function read_auth_file()
local newtable = {}
local file, errmsg = io.open(core.auth_file_path, 'rb')
if not file then
core.log("info", core.auth_file_path.." could not be opened for reading ("..errmsg.."); assuming new world")
return
end
for line in file:lines() do
if line ~= "" then
local fields = line:split(":", true)
local name, password, privilege_string, last_login = unpack(fields)
last_login = tonumber(last_login)
if not (name and password and privilege_string) then
error("Invalid line in auth.txt: "..dump(line))
end
local privileges = core.string_to_privs(privilege_string)
newtable[name] = {password=password, privileges=privileges, last_login=last_login}
end
end
io.close(file)
core.auth_table = newtable
core.notify_authentication_modified()
end
local function save_auth_file()
local newtable = {}
-- Check table for validness before attempting to save
for name, stuff in pairs(core.auth_table) do
assert(type(name) == "string")
assert(name ~= "")
assert(type(stuff) == "table")
assert(type(stuff.password) == "string")
assert(type(stuff.privileges) == "table")
assert(stuff.last_login == nil or type(stuff.last_login) == "number")
end
local file, errmsg = io.open(core.auth_file_path, 'w+b')
if not file then
error(core.auth_file_path.." could not be opened for writing: "..errmsg)
end
for name, stuff in pairs(core.auth_table) do
local priv_string = core.privs_to_string(stuff.privileges)
local parts = {name, stuff.password, priv_string, stuff.last_login or ""}
file:write(table.concat(parts, ":").."\n")
end
io.close(file)
end
read_auth_file()
core.builtin_auth_handler = {
get_auth = function(name)
assert(type(name) == "string")
-- Figure out what password to use for a new player (singleplayer
-- always has an empty password, otherwise use default, which is
-- usually empty too)
local new_password_hash = ""
-- If not in authentication table, return nil
if not core.auth_table[name] then
return nil
end
-- Figure out what privileges the player should have.
-- Take a copy of the privilege table
local privileges = {}
for priv, _ in pairs(core.auth_table[name].privileges) do
privileges[priv] = true
end
-- If singleplayer, give all privileges except those marked as give_to_singleplayer = false
if core.is_singleplayer() then
for priv, def in pairs(core.registered_privileges) do
if def.give_to_singleplayer then
privileges[priv] = true
end
end
-- For the admin, give everything
elseif name == core.setting_get("name") then
for priv, def in pairs(core.registered_privileges) do
privileges[priv] = true
end
end
-- All done
return {
password = core.auth_table[name].password,
privileges = privileges,
-- Is set to nil if unknown
last_login = core.auth_table[name].last_login,
}
end,
create_auth = function(name, password)
assert(type(name) == "string")
assert(type(password) == "string")
core.log('info', "Built-in authentication handler adding player '"..name.."'")
core.auth_table[name] = {
password = password,
privileges = core.string_to_privs(core.setting_get("default_privs")),
last_login = os.time(),
}
save_auth_file()
end,
set_password = function(name, password)
assert(type(name) == "string")
assert(type(password) == "string")
if not core.auth_table[name] then
core.builtin_auth_handler.create_auth(name, password)
else
core.log('info', "Built-in authentication handler setting password of player '"..name.."'")
core.auth_table[name].password = password
save_auth_file()
end
return true
end,
set_privileges = function(name, privileges)
assert(type(name) == "string")
assert(type(privileges) == "table")
if not core.auth_table[name] then
core.builtin_auth_handler.create_auth(name,
core.get_password_hash(name,
core.setting_get("default_password")))
end
core.auth_table[name].privileges = privileges
core.notify_authentication_modified(name)
save_auth_file()
end,
reload = function()
read_auth_file()
return true
end,
record_login = function(name)
assert(type(name) == "string")
assert(core.auth_table[name]).last_login = os.time()
save_auth_file()
end,
}
function core.register_authentication_handler(handler)
if core.registered_auth_handler then
error("Add-on authentication handler already registered by "..core.registered_auth_handler_modname)
end
core.registered_auth_handler = handler
core.registered_auth_handler_modname = core.get_current_modname()
handler.mod_origin = core.registered_auth_handler_modname
end
function core.get_auth_handler()
return core.registered_auth_handler or core.builtin_auth_handler
end
local function auth_pass(name)
return function(...)
local auth_handler = core.get_auth_handler()
if auth_handler[name] then
return auth_handler[name](...)
end
return false
end
end
core.set_player_password = auth_pass("set_password")
core.set_player_privs = auth_pass("set_privileges")
core.auth_reload = auth_pass("reload")
local record_login = auth_pass("record_login")
core.register_on_joinplayer(function(player)
record_login(player:get_player_name())
end)
core.register_on_prejoinplayer(function(name, ip)
local auth = core.auth_table
if auth[name] ~= nil then
return
end
local name_lower = name:lower()
for k in pairs(auth) do
if k:lower() == name_lower then
return string.format("\nCannot create new player called '%s'. "..
"Another account called '%s' is already registered. "..
"Please check the spelling if it's your account "..
"or use a different nickname.", name, k)
end
end
end)

View File

@ -0,0 +1,960 @@
-- Minetest: builtin/chatcommands.lua
--
-- Chat command handler
--
core.registered_chatcommands = {}
core.chatcommands = core.registered_chatcommands -- BACKWARDS COMPATIBILITY
function core.register_chatcommand(cmd, def)
def = def or {}
def.params = def.params or ""
def.description = def.description or ""
def.privs = def.privs or {}
def.mod_origin = core.get_current_modname() or "??"
core.registered_chatcommands[cmd] = def
end
function core.unregister_chatcommand(name)
if core.registered_chatcommands[name] then
core.registered_chatcommands[name] = nil
else
core.log("warning", "Not unregistering chatcommand " ..name..
" because it doesn't exist.")
end
end
function core.override_chatcommand(name, redefinition)
local chatcommand = core.registered_chatcommands[name]
assert(chatcommand, "Attempt to override non-existent chatcommand "..name)
for k, v in pairs(redefinition) do
rawset(chatcommand, k, v)
end
core.registered_chatcommands[name] = chatcommand
end
core.register_on_chat_message(function(name, message)
local cmd, param = string.match(message, "^/([^ ]+) *(.*)")
if not param then
param = ""
end
local cmd_def = core.registered_chatcommands[cmd]
if not cmd_def then
return false
end
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, message)
end
else
core.chat_send_player(name, "You don't have permission"
.. " to run this command (missing privileges: "
.. table.concat(missing_privs, ", ") .. ")")
end
return true -- Handled chat message
end)
if core.setting_getbool("profiler.load") then
-- Run after register_chatcommand and its register_on_chat_message
-- Before any chattcommands that should be profiled
profiler.init_chatcommand()
end
-- Parses a "range" string in the format of "here (number)" or
-- "(x1, y1, z1) (x2, y2, z2)", returning two position vectors
local function parse_range_str(player_name, str)
local p1, p2
local args = str:split(" ")
if args[1] == "here" then
p1, p2 = core.get_player_radius_area(player_name, tonumber(args[2]))
if p1 == nil then
return false, "Unable to get player " .. player_name .. " position"
end
else
p1, p2 = core.string_to_area(str)
if p1 == nil then
return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)"
end
end
return p1, p2
end
--
-- Chat commands
--
core.register_chatcommand("me", {
params = "<action>",
description = "Display chat action (e.g., '/me orders a pizza' displays"
.. " '<player name> orders a pizza')",
privs = {shout=true},
func = function(name, param)
core.chat_send_all("* " .. name .. " " .. param)
end,
})
core.register_chatcommand("admin", {
description = "Show the name of the server owner",
func = function(name)
local admin = minetest.setting_get("name")
if admin then
return true, "The administrator of this server is "..admin.."."
else
return false, "There's no administrator named in the config file."
end
end,
})
core.register_chatcommand("help", {
privs = {},
params = "[all/privs/<cmd>]",
description = "Get help for commands or list privileges",
func = function(name, param)
local function format_help_line(cmd, def)
local msg = core.colorize("#00ffff", "/"..cmd)
if def.params and def.params ~= "" then
msg = msg .. " " .. def.params
end
if def.description and def.description ~= "" then
msg = msg .. ": " .. def.description
end
return msg
end
if param == "" then
local msg = ""
local cmds = {}
for cmd, def in pairs(core.registered_chatcommands) do
if core.check_player_privs(name, def.privs) then
cmds[#cmds + 1] = cmd
end
end
table.sort(cmds)
return true, "Available commands: " .. table.concat(cmds, " ") .. "\n"
.. "Use '/help <cmd>' to get more information,"
.. " or '/help all' to list everything."
elseif param == "all" then
local cmds = {}
for cmd, def in pairs(core.registered_chatcommands) do
if core.check_player_privs(name, def.privs) then
cmds[#cmds + 1] = format_help_line(cmd, def)
end
end
table.sort(cmds)
return true, "Available commands:\n"..table.concat(cmds, "\n")
elseif param == "privs" then
local privs = {}
for priv, def in pairs(core.registered_privileges) do
privs[#privs + 1] = priv .. ": " .. def.description
end
table.sort(privs)
return true, "Available privileges:\n"..table.concat(privs, "\n")
else
local cmd = param
local def = core.registered_chatcommands[cmd]
if not def then
return false, "Command not available: "..cmd
else
return true, format_help_line(cmd, def)
end
end
end,
})
core.register_chatcommand("privs", {
params = "<name>",
description = "Print privileges of player",
func = function(caller, param)
param = param:trim()
local name = (param ~= "" and param or caller)
return true, "Privileges of " .. name .. ": "
.. core.privs_to_string(
core.get_player_privs(name), ' ')
end,
})
local function handle_grant_command(caller, grantname, grantprivstr)
local caller_privs = minetest.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(grantname) then
return false, "Player " .. grantname .. " does not exist."
end
local grantprivs = core.string_to_privs(grantprivstr)
if grantprivstr == "all" then
grantprivs = core.registered_privileges
end
local privs = core.get_player_privs(grantname)
local privs_unknown = ""
local basic_privs =
core.string_to_privs(core.setting_get("basic_privs") or "interact,shout")
for priv, _ in pairs(grantprivs) do
if not basic_privs[priv] and not caller_privs.privs then
return false, "Your privileges are insufficient."
end
if not core.registered_privileges[priv] then
privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
end
privs[priv] = true
end
if privs_unknown ~= "" then
return false, privs_unknown
end
core.set_player_privs(grantname, privs)
core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname)
if grantname ~= caller then
core.chat_send_player(grantname, caller
.. " granted you privileges: "
.. core.privs_to_string(grantprivs, ' '))
end
return true, "Privileges of " .. grantname .. ": "
.. core.privs_to_string(
core.get_player_privs(grantname), ' ')
end
core.register_chatcommand("grant", {
params = "<name> <privilege>|all",
description = "Give privilege to player",
func = function(name, param)
local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)")
if not grantname or not grantprivstr then
return false, "Invalid parameters (see /help grant)"
end
return handle_grant_command(name, grantname, grantprivstr)
end,
})
core.register_chatcommand("grantme", {
params = "<privilege>|all",
description = "Grant privileges to yourself",
func = function(name, param)
if param == "" then
return false, "Invalid parameters (see /help grantme)"
end
return handle_grant_command(name, name, param)
end,
})
core.register_chatcommand("revoke", {
params = "<name> <privilege>|all",
description = "Remove privilege 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, "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, "Invalid parameters (see /help revoke)"
elseif not core.get_auth_handler().get_auth(revoke_name) then
return false, "Player " .. revoke_name .. " does not exist."
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.setting_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, "Your privileges are insufficient."
end
end
if revoke_priv_str == "all" then
privs = {}
else
for priv, _ in pairs(revoke_privs) do
privs[priv] = nil
end
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
.. " revoked privileges from you: "
.. core.privs_to_string(revoke_privs, ' '))
end
return true, "Privileges of " .. revoke_name .. ": "
.. core.privs_to_string(
core.get_player_privs(revoke_name), ' ')
end,
})
core.register_chatcommand("setpassword", {
params = "<name> <password>",
description = "Set player's password",
privs = {password=true},
func = function(name, param)
local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
if not toname then
toname = param:match("^([^ ]+) *$")
raw_password = nil
end
if not toname then
return false, "Name field required"
end
local act_str_past = "?"
local act_str_pres = "?"
if not raw_password then
core.set_player_password(toname, "")
act_str_past = "cleared"
act_str_pres = "clears"
else
core.set_player_password(toname,
core.get_password_hash(toname,
raw_password))
act_str_past = "set"
act_str_pres = "sets"
end
if toname ~= name then
core.chat_send_player(toname, "Your password was "
.. act_str_past .. " by " .. name)
end
core.log("action", name .. " " .. act_str_pres
.. " password of " .. toname .. ".")
return true, "Password of player \"" .. toname .. "\" " .. act_str_past
end,
})
core.register_chatcommand("clearpassword", {
params = "<name>",
description = "Set empty password",
privs = {password=true},
func = function(name, param)
local toname = param
if toname == "" then
return false, "Name field required"
end
core.set_player_password(toname, '')
core.log("action", name .. " clears password of " .. toname .. ".")
return true, "Password of player \"" .. toname .. "\" cleared"
end,
})
core.register_chatcommand("auth_reload", {
params = "",
description = "Reload authentication data",
privs = {server=true},
func = function(name, param)
local done = core.auth_reload()
return done, (done and "Done." or "Failed.")
end,
})
core.register_chatcommand("teleport", {
params = "<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>",
description = "Teleport to player or position",
privs = {teleport=true},
func = function(name, param)
-- Returns (pos, true) if found, otherwise (pos, false)
local function find_free_position_near(pos)
local tries = {
{x=1,y=0,z=0},
{x=-1,y=0,z=0},
{x=0,y=0,z=1},
{x=0,y=0,z=-1},
}
for _, d in ipairs(tries) do
local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z}
local n = core.get_node_or_nil(p)
if n and n.name then
local def = core.registered_nodes[n.name]
if def and not def.walkable then
return p, true
end
end
end
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 = tonumber(minetest.setting_get("map_generation_limit") or 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, "Cannot teleport out of map bounds!"
end
teleportee = core.get_player_by_name(name)
if teleportee then
teleportee:setpos(p)
return true, "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)
if target_name then
local target = core.get_player_by_name(target_name)
if target then
p = target:getpos()
end
end
if teleportee and p then
p = find_free_position_near(p)
teleportee:setpos(p)
return true, "Teleporting to " .. target_name
.. " at "..core.pos_to_string(p)
end
if not core.check_player_privs(name, {bring=true}) then
return false, "You don't have permission to teleport other players (missing bring privilege)"
end
local teleportee = nil
local p = {}
local teleportee_name = nil
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)
if teleportee_name then
teleportee = core.get_player_by_name(teleportee_name)
end
if teleportee and p.x and p.y and p.z then
teleportee:setpos(p)
return true, "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_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
if teleportee_name then
teleportee = core.get_player_by_name(teleportee_name)
end
if target_name then
local target = core.get_player_by_name(target_name)
if target then
p = target:getpos()
end
end
if teleportee and p then
p = find_free_position_near(p)
teleportee:setpos(p)
return true, "Teleporting " .. teleportee_name
.. " to " .. target_name
.. " at " .. core.pos_to_string(p)
end
return false, 'Invalid parameters ("' .. param
.. '") or player not found (see /help teleport)'
end,
})
core.register_chatcommand("set", {
params = "[-n] <name> <value> | <name>",
description = "Set or read server configuration setting",
privs = {server=true},
func = function(name, param)
local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
if arg and arg == "-n" and setname and setvalue then
core.setting_set(setname, setvalue)
return true, setname .. " = " .. setvalue
end
local setname, setvalue = string.match(param, "([^ ]+) (.+)")
if setname and setvalue then
if not core.setting_get(setname) then
return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
end
core.setting_set(setname, setvalue)
return true, setname .. " = " .. setvalue
end
local setname = string.match(param, "([^ ]+)")
if setname then
local setvalue = core.setting_get(setname)
if not setvalue then
setvalue = "<not set>"
end
return true, setname .. " = " .. setvalue
end
return false, "Invalid parameters (see /help set)."
end,
})
local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
if ctx.total_blocks == 0 then
ctx.total_blocks = num_calls_remaining + 1
ctx.current_blocks = 0
end
ctx.current_blocks = ctx.current_blocks + 1
if ctx.current_blocks == ctx.total_blocks then
core.chat_send_player(ctx.requestor_name,
string.format("Finished emerging %d blocks in %.2fms.",
ctx.total_blocks, (os.clock() - ctx.start_time) * 1000))
end
end
local function emergeblocks_progress_update(ctx)
if ctx.current_blocks ~= ctx.total_blocks then
core.chat_send_player(ctx.requestor_name,
string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)",
ctx.current_blocks, ctx.total_blocks,
(ctx.current_blocks / ctx.total_blocks) * 100))
core.after(2, emergeblocks_progress_update, ctx)
end
end
core.register_chatcommand("emergeblocks", {
params = "(here [radius]) | (<pos1> <pos2>)",
description = "Load (or, if nonexistent, generate) map blocks "
.. "contained in area pos1 to pos2",
privs = {server=true},
func = function(name, param)
local p1, p2 = parse_range_str(name, param)
if p1 == false then
return false, p2
end
local context = {
current_blocks = 0,
total_blocks = 0,
start_time = os.clock(),
requestor_name = name
}
core.emerge_area(p1, p2, emergeblocks_callback, context)
core.after(2, emergeblocks_progress_update, context)
return true, "Started emerge of area ranging from " ..
core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
end,
})
core.register_chatcommand("deleteblocks", {
params = "(here [radius]) | (<pos1> <pos2>)",
description = "Delete map blocks contained in area pos1 to pos2",
privs = {server=true},
func = function(name, param)
local p1, p2 = parse_range_str(name, param)
if p1 == false then
return false, p2
end
if core.delete_area(p1, p2) then
return true, "Successfully cleared area ranging from " ..
core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
else
return false, "Failed to clear one or more blocks in area"
end
end,
})
core.register_chatcommand("mods", {
params = "",
description = "List mods installed on the server",
privs = {},
func = function(name, param)
return true, table.concat(core.get_modnames(), ", ")
end,
})
local function handle_give_command(cmd, giver, receiver, stackstring)
core.log("action", giver .. " invoked " .. cmd
.. ', stackstring="' .. stackstring .. '"')
local itemstack = ItemStack(stackstring)
if itemstack:is_empty() then
return false, "Cannot give an empty item"
elseif not itemstack:is_known() then
return false, "Cannot give an unknown item"
end
local receiverref = core.get_player_by_name(receiver)
if receiverref == nil then
return false, receiver .. " is not a known player"
end
local leftover = receiverref:get_inventory():add_item("main", itemstack)
local partiality
if leftover:is_empty() then
partiality = ""
elseif leftover:get_count() == itemstack:get_count() then
partiality = "could not be "
else
partiality = "partially "
end
-- The actual item stack string may be different from what the "giver"
-- entered (e.g. big numbers are always interpreted as 2^16-1).
stackstring = itemstack:to_string()
if giver == receiver then
return true, ("%q %sadded to inventory.")
:format(stackstring, partiality)
else
core.chat_send_player(receiver, ("%q %sadded to inventory.")
:format(stackstring, partiality))
return true, ("%q %sadded to %s's inventory.")
:format(stackstring, partiality, receiver)
end
end
core.register_chatcommand("give", {
params = "<name> <ItemString>",
description = "Give item to player",
privs = {give=true},
func = function(name, param)
local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
if not toname or not itemstring then
return false, "Name and ItemString required"
end
return handle_give_command("/give", name, toname, itemstring)
end,
})
core.register_chatcommand("giveme", {
params = "<ItemString>",
description = "Give item to yourself",
privs = {give=true},
func = function(name, param)
local itemstring = string.match(param, "(.+)$")
if not itemstring then
return false, "ItemString required"
end
return handle_give_command("/giveme", name, name, itemstring)
end,
})
core.register_chatcommand("spawnentity", {
params = "<EntityName> [<X>,<Y>,<Z>]",
description = "Spawn entity at given (or your) position",
privs = {give=true, interact=true},
func = function(name, param)
local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
if not entityname then
return false, "EntityName required"
end
core.log("action", ("%s invokes /spawnentity, entityname=%q")
:format(name, entityname))
local player = core.get_player_by_name(name)
if player == nil then
core.log("error", "Unable to spawn entity, player is nil")
return false, "Unable to spawn entity, player is nil"
end
if p == "" then
p = player:getpos()
else
p = core.string_to_pos(p)
if p == nil then
return false, "Invalid parameters ('" .. param .. "')"
end
end
p.y = p.y + 1
core.add_entity(p, entityname)
return true, ("%q spawned."):format(entityname)
end,
})
core.register_chatcommand("pulverize", {
params = "",
description = "Destroy item in hand",
func = function(name, param)
local player = core.get_player_by_name(name)
if not player then
core.log("error", "Unable to pulverize, no player.")
return false, "Unable to pulverize, no player."
end
if player:get_wielded_item():is_empty() then
return false, "Unable to pulverize, no item in hand."
end
player:set_wielded_item(nil)
return true, "An item was pulverized."
end,
})
-- Key = player name
core.rollback_punch_callbacks = {}
core.register_on_punchnode(function(pos, node, puncher)
local name = puncher:get_player_name()
if core.rollback_punch_callbacks[name] then
core.rollback_punch_callbacks[name](pos, node, puncher)
core.rollback_punch_callbacks[name] = nil
end
end)
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",
privs = {rollback=true},
func = function(name, param)
if not core.setting_getbool("enable_rollback_recording") then
return false, "Rollback functions are disabled."
end
local range, seconds, limit =
param:match("(%d+) *(%d*) *(%d*)")
range = tonumber(range) or 0
seconds = tonumber(seconds) or 86400
limit = tonumber(limit) or 5
if limit > 100 then
return false, "That limit is too high!"
end
core.rollback_punch_callbacks[name] = function(pos, node, puncher)
local name = puncher:get_player_name()
core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
if not actions then
core.chat_send_player(name, "Rollback functions are disabled")
return
end
local num_actions = #actions
if num_actions == 0 then
core.chat_send_player(name, "Nobody has touched"
.. " the specified location in "
.. seconds .. " seconds")
return
end
local time = os.time()
for i = num_actions, 1, -1 do
local action = actions[i]
core.chat_send_player(name,
("%s %s %s -> %s %d seconds ago.")
:format(
core.pos_to_string(action.pos),
action.actor,
action.oldnode.name,
action.newnode.name,
time - action.time))
end
end
return true, "Punch a node (range=" .. range .. ", seconds="
.. seconds .. "s, limit=" .. limit .. ")"
end,
})
core.register_chatcommand("rollback", {
params = "<player name> [<seconds>] | :<actor> [<seconds>]",
description = "Revert actions of a player. Default for <seconds> is 60",
privs = {rollback=true},
func = function(name, param)
if not core.setting_getbool("enable_rollback_recording") then
return false, "Rollback functions are disabled."
end
local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
if not target_name then
local player_name = nil
player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
if not player_name then
return false, "Invalid parameters. See /help rollback"
.. " and /help rollback_check."
end
target_name = "player:"..player_name
end
seconds = tonumber(seconds) or 60
core.chat_send_player(name, "Reverting actions of "
.. target_name .. " since "
.. seconds .. " seconds.")
local success, log = core.rollback_revert_actions_by(
target_name, seconds)
local response = ""
if #log > 100 then
response = "(log is too long to show)\n"
else
for _, line in pairs(log) do
response = response .. line .. "\n"
end
end
response = response .. "Reverting actions "
.. (success and "succeeded." or "FAILED.")
return success, response
end,
})
core.register_chatcommand("status", {
description = "Print server status",
func = function(name, param)
return true, core.get_server_status()
end,
})
core.register_chatcommand("time", {
params = "<0..23>:<0..59> | <0..24000>",
description = "Set time of day",
privs = {},
func = function(name, param)
if param == "" then
local current_time = math.floor(core.get_timeofday() * 1440)
local minutes = current_time % 60
local hour = (current_time - minutes) / 60
return true, ("Current time is %d:%02d"):format(hour, minutes)
end
local player_privs = core.get_player_privs(name)
if not player_privs.settime then
return false, "You don't have permission to run this command " ..
"(missing privilege: settime)."
end
local hour, minute = param:match("^(%d+):(%d+)$")
if not hour then
local new_time = tonumber(param)
if not new_time then
return false, "Invalid time."
end
-- Backward compatibility.
core.set_timeofday((new_time % 24000) / 24000)
core.log("action", name .. " sets time to " .. new_time)
return true, "Time of day changed."
end
hour = tonumber(hour)
minute = tonumber(minute)
if hour < 0 or hour > 23 then
return false, "Invalid hour (must be between 0 and 23 inclusive)."
elseif minute < 0 or minute > 59 then
return false, "Invalid minute (must be between 0 and 59 inclusive)."
end
core.set_timeofday((hour * 60 + minute) / 1440)
core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
return true, "Time of day changed."
end,
})
core.register_chatcommand("days", {
description = "Display day count",
func = function(name, param)
return true, "Current day is " .. core.get_day_count()
end
})
core.register_chatcommand("shutdown", {
description = "Shutdown server",
privs = {server=true},
func = function(name, param)
core.log("action", name .. " shuts down server")
core.request_shutdown()
core.chat_send_all("*** Server shutting down (operator request).")
end,
})
core.register_chatcommand("ban", {
params = "<name>",
description = "Ban IP of player",
privs = {ban=true},
func = function(name, param)
if param == "" then
return true, "Ban list: " .. core.get_ban_list()
end
if not core.get_player_by_name(param) then
return false, "No such player."
end
if not core.ban_player(param) then
return false, "Failed to ban player."
end
local desc = core.get_ban_description(param)
core.log("action", name .. " bans " .. desc .. ".")
return true, "Banned " .. desc .. "."
end,
})
core.register_chatcommand("unban", {
params = "<name/ip>",
description = "Remove IP ban",
privs = {ban=true},
func = function(name, param)
if not core.unban_player_or_ip(param) then
return false, "Failed to unban player/IP."
end
core.log("action", name .. " unbans " .. param)
return true, "Unbanned " .. param
end,
})
core.register_chatcommand("kick", {
params = "<name> [reason]",
description = "Kick a player",
privs = {kick=true},
func = function(name, param)
local tokick, reason = param:match("([^ ]+) (.+)")
tokick = tokick or param
if not core.kick_player(tokick, reason) then
return false, "Failed to kick player " .. tokick
end
local log_reason = ""
if reason then
log_reason = " with reason \"" .. reason .. "\""
end
core.log("action", name .. " kicks " .. tokick .. log_reason)
return true, "Kicked " .. tokick
end,
})
core.register_chatcommand("clearobjects", {
params = "[full|quick]",
description = "Clear all objects in world",
privs = {server=true},
func = function(name, param)
local options = {}
if param == "" or param == "full" then
options.mode = "full"
elseif param == "quick" then
options.mode = "quick"
else
return false, "Invalid usage, see /help clearobjects."
end
core.log("action", name .. " clears all objects ("
.. options.mode .. " mode).")
core.chat_send_all("Clearing all objects. This may take long."
.. " You may experience a timeout. (by "
.. name .. ")")
core.clear_objects(options)
core.log("action", "Object clearing done.")
core.chat_send_all("*** Cleared all objects.")
end,
})
core.register_chatcommand("msg", {
params = "<name> <message>",
description = "Send a private message",
privs = {shout=true},
func = function(name, param)
local sendto, message = param:match("^(%S+)%s(.+)$")
if not sendto then
return false, "Invalid usage, see /help msg."
end
if not core.get_player_by_name(sendto) then
return false, "The player " .. sendto
.. " is not online."
end
core.log("action", "PM from " .. name .. " to " .. sendto
.. ": " .. message)
core.chat_send_player(sendto, "PM from " .. name .. ": "
.. message)
return true, "Message sent."
end,
})
core.register_chatcommand("last-login", {
params = "[name]",
description = "Get the last login time of a player",
func = function(name, param)
if param == "" then
param = name
end
local pauth = core.get_auth_handler().get_auth(param)
if pauth and pauth.last_login then
-- Time in UTC, ISO 8601 format
return true, "Last login time was " ..
os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
end
return false, "Last login time is unknown"
end,
})

View File

@ -0,0 +1,27 @@
-- Minetest: builtin/constants.lua
--
-- Constants values for use with the Lua API
--
-- mapnode.h
-- Built-in Content IDs (for use with VoxelManip API)
core.CONTENT_UNKNOWN = 125
core.CONTENT_AIR = 126
core.CONTENT_IGNORE = 127
-- emerge.h
-- Block emerge status constants (for use with core.emerge_area)
core.EMERGE_CANCELLED = 0
core.EMERGE_ERRORED = 1
core.EMERGE_FROM_MEMORY = 2
core.EMERGE_FROM_DISK = 3
core.EMERGE_GENERATED = 4
-- constants.h
-- Size of mapblocks in nodes
core.MAP_BLOCKSIZE = 16
-- light.h
-- Maximum value for node 'light_source' parameter
core.LIGHT_MAX = 14

View File

@ -0,0 +1,51 @@
-- Minetest: builtin/deprecated.lua
--
-- Default material types
--
local function digprop_err()
core.log("deprecated", "The core.digprop_* functions are obsolete and need to be replaced by item groups.")
end
core.digprop_constanttime = digprop_err
core.digprop_stonelike = digprop_err
core.digprop_dirtlike = digprop_err
core.digprop_gravellike = digprop_err
core.digprop_woodlike = digprop_err
core.digprop_leaveslike = digprop_err
core.digprop_glasslike = digprop_err
function core.node_metadata_inventory_move_allow_all()
core.log("deprecated", "core.node_metadata_inventory_move_allow_all is obsolete and does nothing.")
end
function core.add_to_creative_inventory(itemstring)
core.log("deprecated", "core.add_to_creative_inventory: This function is deprecated and does nothing.")
end
--
-- EnvRef
--
core.env = {}
local envref_deprecation_message_printed = false
setmetatable(core.env, {
__index = function(table, key)
if not envref_deprecation_message_printed then
core.log("deprecated", "core.env:[...] is deprecated and should be replaced with core.[...]")
envref_deprecation_message_printed = true
end
local func = core[key]
if type(func) == "function" then
rawset(table, key, function(self, ...)
return func(...)
end)
else
rawset(table, key, nil)
end
return rawget(table, key)
end
})
function core.rollback_get_last_node_actor(pos, range, seconds)
return core.rollback_get_node_actions(pos, range, seconds, 1)[1]
end

View File

@ -0,0 +1,20 @@
-- Minetest: builtin/detached_inventory.lua
core.detached_inventories = {}
function core.create_detached_inventory(name, callbacks, player_name)
local stuff = {}
stuff.name = name
if callbacks then
stuff.allow_move = callbacks.allow_move
stuff.allow_put = callbacks.allow_put
stuff.allow_take = callbacks.allow_take
stuff.on_move = callbacks.on_move
stuff.on_put = callbacks.on_put
stuff.on_take = callbacks.on_take
end
stuff.mod_origin = core.get_current_modname() or "??"
core.detached_inventories[name] = stuff
return core.create_detached_inventory_raw(name, player_name)
end

View File

@ -0,0 +1,294 @@
-- Minetest: builtin/item.lua
local builtin_shared = ...
--
-- Falling stuff
--
core.register_entity(":__builtin:falling_node", {
initial_properties = {
visual = "wielditem",
visual_size = {x = 0.667, y = 0.667},
textures = {},
physical = true,
is_visible = false,
collide_with_objects = false,
collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
},
node = {},
set_node = function(self, node)
self.node = node
self.object:set_properties({
is_visible = true,
textures = {node.name},
})
end,
get_staticdata = function(self)
return core.serialize(self.node)
end,
on_activate = function(self, staticdata)
self.object:set_armor_groups({immortal = 1})
local node = core.deserialize(staticdata)
if node then
self:set_node(node)
elseif staticdata ~= "" then
self:set_node({name = staticdata})
end
end,
on_step = function(self, dtime)
-- Set gravity
local acceleration = self.object:getacceleration()
if not vector.equals(acceleration, {x = 0, y = -10, z = 0}) then
self.object:setacceleration({x = 0, y = -10, z = 0})
end
-- Turn to actual node when colliding with ground, or continue to move
local pos = self.object:getpos()
-- Position of bottom center point
local bcp = {x = pos.x, y = pos.y - 0.7, z = pos.z}
-- Avoid bugs caused by an unloaded node below
local bcn = core.get_node_or_nil(bcp)
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
end
if core.add_node_level(bcp, addlevel) == 0 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.name, "")
for _, dropped_item in pairs(drops) do
core.add_item(np, dropped_item)
end
end
-- Run script hook
for _, callback in pairs(core.registered_on_dignodes) do
callback(np, n2)
end
end
-- Create node and remove entity
if core.registered_nodes[self.node.name] then
core.add_node(np, self.node)
end
self.object:remove()
core.check_for_falling(np)
return
end
local vel = self.object:getvelocity()
if vector.equals(vel, {x = 0, y = 0, z = 0}) then
local npos = self.object:getpos()
self.object:setpos(vector.round(npos))
end
end
})
local function spawn_falling_node(p, node)
local obj = core.add_entity(p, "__builtin:falling_node")
if obj then
obj:get_luaentity():set_node(node)
end
end
local function drop_attached_node(p)
local nn = core.get_node(p).name
core.remove_node(p)
for _, item in pairs(core.get_node_drops(nn, "")) do
local pos = {
x = p.x + math.random()/2 - 0.25,
y = p.y + math.random()/2 - 0.25,
z = p.z + math.random()/2 - 0.25,
}
core.add_item(pos, item)
end
end
function builtin_shared.check_attached_node(p, n)
local def = core.registered_nodes[n.name]
local d = {x = 0, y = 0, z = 0}
if def.paramtype2 == "wallmounted" then
-- The fallback vector here is in case 'wallmounted to dir' is nil due
-- to voxelmanip placing a wallmounted node without resetting a
-- pre-existing param2 value that is out-of-range for wallmounted.
-- The fallback vector corresponds to param2 = 0.
d = core.wallmounted_to_dir(n.param2) or {x = 0, y = 1, z = 0}
else
d.y = -1
end
local p2 = vector.add(p, d)
local nn = core.get_node(p2).name
local def2 = core.registered_nodes[nn]
if def2 and not def2.walkable then
return false
end
return true
end
--
-- Some common functions
--
function core.check_single_for_falling(p)
local n = core.get_node(p)
if core.get_item_group(n.name, "falling_node") ~= 0 then
local p_bottom = {x = p.x, y = p.y - 1, z = p.z}
-- 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
n.level = core.get_node_level(p)
core.remove_node(p)
spawn_falling_node(p, n)
return true
end
end
if core.get_item_group(n.name, "attached_node") ~= 0 then
if not builtin_shared.check_attached_node(p, n) then
drop_attached_node(p)
return true
end
end
return false
end
-- This table is specifically ordered.
-- We don't walk diagonals, only our direct neighbors, and self.
-- Down first as likely case, but always before self. The same with sides.
-- Up must come last, so that things above self will also fall all at once.
local check_for_falling_neighbors = {
{x = -1, y = -1, z = 0},
{x = 1, y = -1, z = 0},
{x = 0, y = -1, z = -1},
{x = 0, y = -1, z = 1},
{x = 0, y = -1, z = 0},
{x = -1, y = 0, z = 0},
{x = 1, y = 0, z = 0},
{x = 0, y = 0, z = 1},
{x = 0, y = 0, z = -1},
{x = 0, y = 0, z = 0},
{x = 0, y = 1, z = 0},
}
function core.check_for_falling(p)
-- Round p to prevent falling entities to get stuck.
p = vector.round(p)
-- We make a stack, and manually maintain size for performance.
-- Stored in the stack, we will maintain tables with pos, and
-- last neighbor visited. This way, when we get back to each
-- node, we know which directions we have already walked, and
-- which direction is the next to walk.
local s = {}
local n = 0
-- The neighbor order we will visit from our table.
local v = 1
while true do
-- Push current pos onto the stack.
n = n + 1
s[n] = {p = p, v = v}
-- Select next node from neighbor list.
p = vector.add(p, check_for_falling_neighbors[v])
-- Now we check out the node. If it is in need of an update,
-- it will let us know in the return value (true = updated).
if not core.check_single_for_falling(p) then
-- If we don't need to "recurse" (walk) to it then pop
-- our previous pos off the stack and continue from there,
-- with the v value we were at when we last were at that
-- node
repeat
local pop = s[n]
p = pop.p
v = pop.v
s[n] = nil
n = n - 1
-- If there's nothing left on the stack, and no
-- more sides to walk to, we're done and can exit
if n == 0 and v == 11 then
return
end
until v < 11
-- The next round walk the next neighbor in list.
v = v + 1
else
-- If we did need to walk the neighbor, then
-- start walking it from the walk order start (1),
-- and not the order we just pushed up the stack.
v = 1
end
end
end
--
-- Global callbacks
--
local function on_placenode(p, node)
core.check_for_falling(p)
end
core.register_on_placenode(on_placenode)
local function on_dignode(p, node)
core.check_for_falling(p)
end
core.register_on_dignode(on_dignode)
local function on_punchnode(p, node)
core.check_for_falling(p)
end
core.register_on_punchnode(on_punchnode)
--
-- Globally exported functions
--
-- TODO remove this function after the 0.4.15 release
function nodeupdate(p)
core.log("deprecated", "nodeupdate: deprecated, please use core.check_for_falling instead")
core.check_for_falling(p)
end
-- TODO remove this function after the 0.4.15 release
function nodeupdate_single(p)
core.log("deprecated", "nodeupdate_single: deprecated, please use core.check_single_for_falling instead")
core.check_single_for_falling(p)
end

View File

@ -0,0 +1,33 @@
-- Minetest: builtin/features.lua
core.features = {
glasslike_framed = true,
nodebox_as_selectionbox = true,
chat_send_player_param3 = true,
get_all_craft_recipes_works = true,
use_texture_alpha = true,
no_legacy_abms = true,
texture_names_parens = true,
area_store_custom_ids = true,
add_entity_with_staticdata = true,
no_chat_message_prediction = true,
}
function core.has_feature(arg)
if type(arg) == "table" then
local missing_features = {}
local result = true
for ftr in pairs(arg) do
if not core.features[ftr] then
missing_features[ftr] = true
result = false
end
end
return result, missing_features
elseif type(arg) == "string" then
if not core.features[arg] then
return false, {[arg]=true}
end
return true, {}
end
end

View File

@ -0,0 +1,100 @@
-- Prevent anyone else accessing those functions
local forceload_block = core.forceload_block
local forceload_free_block = core.forceload_free_block
core.forceload_block = nil
core.forceload_free_block = nil
local blocks_forceloaded
local blocks_temploaded = {}
local total_forceloaded = 0
local BLOCKSIZE = core.MAP_BLOCKSIZE
local function get_blockpos(pos)
return {
x = math.floor(pos.x/BLOCKSIZE),
y = math.floor(pos.y/BLOCKSIZE),
z = math.floor(pos.z/BLOCKSIZE)}
end
-- When we create/free a forceload, it's either transient or persistent. We want
-- to add to/remove from the table that corresponds to the type of forceload, but
-- we also need the other table because whether we forceload a block depends on
-- both tables.
-- This function returns the "primary" table we are adding to/removing from, and
-- the other table.
local function get_relevant_tables(transient)
if transient then
return blocks_temploaded, blocks_forceloaded
else
return blocks_forceloaded, blocks_temploaded
end
end
function core.forceload_block(pos, transient)
local blockpos = get_blockpos(pos)
local hash = core.hash_node_position(blockpos)
local relevant_table, other_table = get_relevant_tables(transient)
if relevant_table[hash] ~= nil then
relevant_table[hash] = relevant_table[hash] + 1
return true
elseif other_table[hash] ~= nil then
relevant_table[hash] = 1
else
if total_forceloaded >= (tonumber(core.setting_get("max_forceloaded_blocks")) or 16) then
return false
end
total_forceloaded = total_forceloaded+1
relevant_table[hash] = 1
forceload_block(blockpos)
return true
end
end
function core.forceload_free_block(pos, transient)
local blockpos = get_blockpos(pos)
local hash = core.hash_node_position(blockpos)
local relevant_table, other_table = get_relevant_tables(transient)
if relevant_table[hash] == nil then return end
if relevant_table[hash] > 1 then
relevant_table[hash] = relevant_table[hash] - 1
elseif other_table[hash] ~= nil then
relevant_table[hash] = nil
else
total_forceloaded = total_forceloaded-1
relevant_table[hash] = nil
forceload_free_block(blockpos)
end
end
-- Keep the forceloaded areas after restart
local wpath = core.get_worldpath()
local function read_file(filename)
local f = io.open(filename, "r")
if f==nil then return {} end
local t = f:read("*all")
f:close()
if t=="" or t==nil then return {} end
return core.deserialize(t) or {}
end
local function write_file(filename, table)
local f = io.open(filename, "w")
f:write(core.serialize(table))
f:close()
end
blocks_forceloaded = read_file(wpath.."/force_loaded.txt")
for _, __ in pairs(blocks_forceloaded) do
total_forceloaded = total_forceloaded + 1
end
core.after(5, function()
for hash, _ in pairs(blocks_forceloaded) do
local blockpos = core.get_position_from_hash(hash)
forceload_block(blockpos)
end
end)
core.register_on_shutdown(function()
write_file(wpath.."/force_loaded.txt", blocks_forceloaded)
end)

View File

@ -0,0 +1,34 @@
local scriptpath = core.get_builtin_path()..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(gamepath.."constants.lua")
assert(loadfile(gamepath.."item.lua"))(builtin_shared)
dofile(gamepath.."register.lua")
if core.setting_getbool("profiler.load") then
profiler = dofile(scriptpath.."profiler"..DIR_DELIM.."init.lua")
end
dofile(gamepath.."item_entity.lua")
dofile(gamepath.."deprecated.lua")
dofile(gamepath.."misc.lua")
dofile(gamepath.."privileges.lua")
dofile(gamepath.."auth.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")
profiler = nil

View File

@ -0,0 +1,663 @@
-- Minetest: builtin/item.lua
local builtin_shared = ...
local function copy_pointed_thing(pointed_thing)
return {
type = pointed_thing.type,
above = vector.new(pointed_thing.above),
under = vector.new(pointed_thing.under),
ref = pointed_thing.ref,
}
end
--
-- Item definition helpers
--
function core.inventorycube(img1, img2, img3)
img2 = img2 or img1
img3 = img3 or img1
return "[inventorycube"
.. "{" .. img1:gsub("%^", "&")
.. "{" .. img2:gsub("%^", "&")
.. "{" .. img3:gsub("%^", "&")
end
function core.get_pointed_thing_position(pointed_thing, above)
if pointed_thing.type == "node" then
if above then
-- The position where a node would be placed
return pointed_thing.above
end
-- The position where a node would be dug
return pointed_thing.under
elseif pointed_thing.type == "object" then
return pointed_thing.ref and pointed_thing.ref:getpos()
end
end
function core.dir_to_facedir(dir, is6d)
--account for y if requested
if is6d and math.abs(dir.y) > math.abs(dir.x) and math.abs(dir.y) > math.abs(dir.z) then
--from above
if dir.y < 0 then
if math.abs(dir.x) > math.abs(dir.z) then
if dir.x < 0 then
return 19
else
return 13
end
else
if dir.z < 0 then
return 10
else
return 4
end
end
--from below
else
if math.abs(dir.x) > math.abs(dir.z) then
if dir.x < 0 then
return 15
else
return 17
end
else
if dir.z < 0 then
return 6
else
return 8
end
end
end
--otherwise, place horizontally
elseif math.abs(dir.x) > math.abs(dir.z) then
if dir.x < 0 then
return 3
else
return 1
end
else
if dir.z < 0 then
return 2
else
return 0
end
end
end
-- Table of possible dirs
local facedir_to_dir = {
{x= 0, y=0, z= 1},
{x= 1, y=0, z= 0},
{x= 0, y=0, z=-1},
{x=-1, y=0, z= 0},
{x= 0, y=-1, z= 0},
{x= 0, y=1, z= 0},
}
-- Mapping from facedir value to index in facedir_to_dir.
local facedir_to_dir_map = {
[0]=1, 2, 3, 4,
5, 2, 6, 4,
6, 2, 5, 4,
1, 5, 3, 6,
1, 6, 3, 5,
1, 4, 3, 2,
}
function core.facedir_to_dir(facedir)
return facedir_to_dir[facedir_to_dir_map[facedir % 32]]
end
function core.dir_to_wallmounted(dir)
if math.abs(dir.y) > math.max(math.abs(dir.x), math.abs(dir.z)) then
if dir.y < 0 then
return 1
else
return 0
end
elseif math.abs(dir.x) > math.abs(dir.z) then
if dir.x < 0 then
return 3
else
return 2
end
else
if dir.z < 0 then
return 5
else
return 4
end
end
end
-- table of dirs in wallmounted order
local wallmounted_to_dir = {
[0] = {x = 0, y = 1, z = 0},
{x = 0, y = -1, z = 0},
{x = 1, y = 0, z = 0},
{x = -1, y = 0, z = 0},
{x = 0, y = 0, z = 1},
{x = 0, y = 0, z = -1},
}
function core.wallmounted_to_dir(wallmounted)
return wallmounted_to_dir[wallmounted % 8]
end
function core.dir_to_yaw(dir)
return -math.atan2(dir.x, dir.z)
end
function core.yaw_to_dir(yaw)
return {x = -math.sin(yaw), y = 0, z = math.cos(yaw)}
end
function core.get_node_drops(nodename, toolname)
local drop = ItemStack({name=nodename}):get_definition().drop
if drop == nil then
-- default drop
return {nodename}
elseif type(drop) == "string" then
-- itemstring drop
return {drop}
elseif drop.items == nil then
-- drop = {} to disable default drop
return {}
end
-- 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
if item.rarity ~= nil then
good_rarity = item.rarity < 1 or math.random(item.rarity) == 1
end
if item.tools ~= nil then
good_tool = false
for _, tool in ipairs(item.tools) do
if tool:sub(1, 1) == '~' then
good_tool = toolname:find(tool:sub(2)) ~= nil
else
good_tool = toolname == tool
end
if good_tool then
break
end
end
end
if good_rarity and good_tool then
got_count = got_count + 1
for _, add_item in ipairs(item.items) do
got_items[#got_items+1] = add_item
end
if drop.max_items ~= nil and got_count == drop.max_items then
break
end
end
end
return got_items
end
function core.item_place_node(itemstack, placer, pointed_thing, param2)
local item = itemstack:peek_item()
local def = itemstack:get_definition()
if def.type ~= "node" or pointed_thing.type ~= "node" then
return itemstack, false
end
local under = pointed_thing.under
local oldnode_under = core.get_node_or_nil(under)
local above = pointed_thing.above
local oldnode_above = core.get_node_or_nil(above)
if not oldnode_under or not oldnode_above then
core.log("info", placer:get_player_name() .. " tried to place"
.. " node in unloaded position " .. core.pos_to_string(above))
return itemstack, false
end
local olddef_under = ItemStack({name=oldnode_under.name}):get_definition()
olddef_under = olddef_under or core.nodedef_default
local olddef_above = ItemStack({name=oldnode_above.name}):get_definition()
olddef_above = olddef_above or core.nodedef_default
if not olddef_above.buildable_to and not olddef_under.buildable_to then
core.log("info", placer:get_player_name() .. " tried to place"
.. " node in invalid position " .. core.pos_to_string(above)
.. ", replacing " .. oldnode_above.name)
return itemstack, false
end
-- Place above pointed node
local place_to = {x = above.x, y = above.y, z = above.z}
-- If node under is buildable_to, place into it instead (eg. snow)
if olddef_under.buildable_to then
core.log("info", "node under is buildable to")
place_to = {x = under.x, y = under.y, z = under.z}
end
if core.is_protected(place_to, placer:get_player_name()) and
not minetest.check_player_privs(placer, "protection_bypass") then
core.log("action", placer:get_player_name()
.. " tried to place " .. def.name
.. " at protected position "
.. core.pos_to_string(place_to))
core.record_protection_violation(place_to, placer:get_player_name())
return itemstack
end
core.log("action", placer:get_player_name() .. " 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}
-- Calculate direction for wall mounted stuff like torches and signs
if def.place_param2 ~= nil then
newnode.param2 = def.place_param2
elseif def.paramtype2 == 'wallmounted' and not param2 then
local dir = {
x = under.x - above.x,
y = under.y - above.y,
z = under.z - above.z
}
newnode.param2 = core.dir_to_wallmounted(dir)
-- Calculate the direction for furnaces and chests and stuff
elseif def.paramtype2 == 'facedir' and not param2 then
local placer_pos = placer:getpos()
if placer_pos then
local dir = {
x = above.x - placer_pos.x,
y = above.y - placer_pos.y,
z = above.z - placer_pos.z
}
newnode.param2 = core.dir_to_facedir(dir)
core.log("action", "facedir: " .. newnode.param2)
end
end
-- Check if the node is attached and if it can be placed there
if core.get_item_group(def.name, "attached_node") ~= 0 and
not builtin_shared.check_attached_node(place_to, newnode) then
core.log("action", "attached node " .. def.name ..
" can not be placed at " .. core.pos_to_string(place_to))
return itemstack, false
end
-- Add node and update
core.add_node(place_to, newnode)
local take_item = true
-- Run callback
if def.after_place_node then
-- Deepcopy place_to and pointed_thing because callback can modify it
local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
if def.after_place_node(place_to_copy, placer, itemstack,
pointed_thing_copy) then
take_item = false
end
end
-- Run script hook
local _, callback
for _, callback in ipairs(core.registered_on_placenodes) do
-- Deepcopy pos, node and pointed_thing because callback can modify them
local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
local newnode_copy = {name=newnode.name, param1=newnode.param1, param2=newnode.param2}
local oldnode_copy = {name=oldnode.name, param1=oldnode.param1, param2=oldnode.param2}
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
if callback(place_to_copy, newnode_copy, placer, oldnode_copy, itemstack, pointed_thing_copy) then
take_item = false
end
end
if take_item then
itemstack:take_item()
end
return itemstack, true
end
function core.item_place_object(itemstack, placer, pointed_thing)
local pos = core.get_pointed_thing_position(pointed_thing, true)
if pos ~= nil then
local item = itemstack:take_item()
core.add_item(pos, item)
end
return itemstack
end
function core.item_place(itemstack, placer, pointed_thing, param2)
-- Call on_rightclick if the pointed node defines it
if pointed_thing.type == "node" and placer and
not placer:get_player_control().sneak then
local n = core.get_node(pointed_thing.under)
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
end
end
if itemstack:get_definition().type == "node" then
return core.item_place_node(itemstack, placer, pointed_thing, param2)
end
return itemstack
end
function core.item_secondary_use(itemstack, placer)
return itemstack
end
function core.item_drop(itemstack, dropper, pos)
if dropper and dropper:is_player() then
local v = dropper:get_look_dir()
local p = {x=pos.x, y=pos.y+1.2, z=pos.z}
local cs = itemstack:get_count()
if dropper:get_player_control().sneak then
cs = 1
end
local item = itemstack:take_item(cs)
local obj = core.add_item(p, item)
if obj then
v.x = v.x*2
v.y = v.y*2 + 2
v.z = v.z*2
obj:setvelocity(v)
obj:get_luaentity().dropped_by = dropper:get_player_name()
return itemstack
end
else
if core.add_item(pos, itemstack) then
return itemstack
end
end
-- If we reach this, adding the object to the
-- environment failed
end
function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
for _, callback in pairs(core.registered_on_item_eats) do
local result = callback(hp_change, replace_with_item, itemstack, user, pointed_thing)
if result then
return result
end
end
if itemstack:take_item() ~= nil then
user:set_hp(user:get_hp() + hp_change)
if replace_with_item then
if itemstack:is_empty() then
itemstack:add_item(replace_with_item)
else
local inv = user:get_inventory()
if inv:room_for_item("main", {name=replace_with_item}) then
inv:add_item("main", replace_with_item)
else
local pos = user:getpos()
pos.y = math.floor(pos.y + 0.5)
core.add_item(pos, replace_with_item)
end
end
end
end
return itemstack
end
function core.item_eat(hp_change, replace_with_item)
return function(itemstack, user, pointed_thing) -- closure
return core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
end
end
function core.node_punch(pos, node, puncher, pointed_thing)
-- Run script hook
for _, callback in ipairs(core.registered_on_punchnodes) do
-- Copy pos and node because callback can modify them
local pos_copy = vector.new(pos)
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
local pointed_thing_copy = pointed_thing and copy_pointed_thing(pointed_thing) or nil
callback(pos_copy, node_copy, puncher, pointed_thing_copy)
end
end
function core.handle_node_drops(pos, drops, digger)
-- Add dropped items to object's inventory
if digger:get_inventory() then
local _, dropped_item
for _, dropped_item in ipairs(drops) do
local left = digger:get_inventory():add_item("main", dropped_item)
if not left:is_empty() then
local p = {
x = pos.x + math.random()/2-0.25,
y = pos.y + math.random()/2-0.25,
z = pos.z + math.random()/2-0.25,
}
core.add_item(p, left)
end
end
end
end
function core.node_dig(pos, node, digger)
local def = ItemStack({name=node.name}):get_definition()
if not def.diggable or (def.can_dig and not def.can_dig(pos,digger)) then
core.log("info", digger:get_player_name() .. " tried to dig "
.. node.name .. " which is not diggable "
.. core.pos_to_string(pos))
return
end
if core.is_protected(pos, digger:get_player_name()) and
not minetest.check_player_privs(digger, "protection_bypass") then
core.log("action", digger:get_player_name()
.. " tried to dig " .. node.name
.. " at protected position "
.. core.pos_to_string(pos))
core.record_protection_violation(pos, digger:get_player_name())
return
end
core.log('action', digger:get_player_name() .. " digs "
.. node.name .. " at " .. core.pos_to_string(pos))
local wielded = digger:get_wielded_item()
local drops = core.get_node_drops(node.name, wielded:get_name())
local wdef = wielded:get_definition()
local tp = wielded:get_tool_capabilities()
local dp = core.get_dig_params(def.groups, tp)
if wdef and wdef.after_use then
wielded = wdef.after_use(wielded, digger, node, dp) or wielded
else
-- Wear out tool
if not core.setting_getbool("creative_mode") 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})
end
end
end
digger:set_wielded_item(wielded)
-- Handle drops
core.handle_node_drops(pos, drops, digger)
local oldmetadata = nil
if def.after_dig_node then
oldmetadata = core.get_meta(pos):to_table()
end
-- Remove node and update
core.remove_node(pos)
-- Run callback
if def.after_dig_node then
-- Copy pos and node because callback can modify them
local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
def.after_dig_node(pos_copy, node_copy, oldmetadata, 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
local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
callback(pos_copy, node_copy, digger)
end
end
-- This is used to allow mods to redefine core.item_place and so on
-- NOTE: This is not the preferred way. Preferred way is to provide enough
-- callbacks to not require redefining global functions. -celeron55
local function redef_wrapper(table, name)
return function(...)
return table[name](...)
end
end
--
-- Item definition defaults
--
core.nodedef_default = {
-- Item properties
type="node",
-- name intentionally not defined here
description = "",
groups = {},
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
stack_max = 99,
usable = false,
liquids_pointable = false,
tool_capabilities = nil,
node_placement_prediction = nil,
-- Interaction callbacks
on_place = redef_wrapper(core, 'item_place'), -- core.item_place
on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop
on_use = nil,
can_dig = nil,
on_punch = redef_wrapper(core, 'node_punch'), -- core.node_punch
on_rightclick = nil,
on_dig = redef_wrapper(core, 'node_dig'), -- core.node_dig
on_receive_fields = nil,
on_metadata_inventory_move = core.node_metadata_inventory_move_allow_all,
on_metadata_inventory_offer = core.node_metadata_inventory_offer_allow_all,
on_metadata_inventory_take = core.node_metadata_inventory_take_allow_all,
-- Node properties
drawtype = "normal",
visual_scale = 1.0,
-- Don't define these because otherwise the old tile_images and
-- special_materials wouldn't be read
--tiles ={""},
--special_tiles = {
-- {name="", backface_culling=true},
-- {name="", backface_culling=true},
--},
alpha = 255,
post_effect_color = {a=0, r=0, g=0, b=0},
paramtype = "none",
paramtype2 = "none",
is_ground_content = true,
sunlight_propagates = false,
walkable = true,
pointable = true,
diggable = true,
climbable = false,
buildable_to = false,
floodable = false,
liquidtype = "none",
liquid_alternative_flowing = "",
liquid_alternative_source = "",
liquid_viscosity = 0,
drowning = 0,
light_source = 0,
damage_per_second = 0,
selection_box = {type="regular"},
legacy_facedir_simple = false,
legacy_wallmounted = false,
}
core.craftitemdef_default = {
type="craft",
-- name intentionally not defined here
description = "",
groups = {},
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
stack_max = 99,
liquids_pointable = false,
tool_capabilities = nil,
-- Interaction callbacks
on_place = redef_wrapper(core, 'item_place'), -- core.item_place
on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop
on_secondary_use = redef_wrapper(core, 'item_secondary_use'),
on_use = nil,
}
core.tooldef_default = {
type="tool",
-- name intentionally not defined here
description = "",
groups = {},
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
stack_max = 1,
liquids_pointable = false,
tool_capabilities = nil,
-- Interaction callbacks
on_place = redef_wrapper(core, 'item_place'), -- core.item_place
on_secondary_use = redef_wrapper(core, 'item_secondary_use'),
on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop
on_use = nil,
}
core.noneitemdef_default = { -- This is used for the hand and unknown items
type="none",
-- name intentionally not defined here
description = "",
groups = {},
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
stack_max = 99,
liquids_pointable = false,
tool_capabilities = nil,
-- Interaction callbacks
on_place = redef_wrapper(core, 'item_place'),
on_secondary_use = redef_wrapper(core, 'item_secondary_use'),
on_drop = nil,
on_use = nil,
}

View File

@ -0,0 +1,216 @@
-- Minetest: builtin/item_entity.lua
function core.spawn_item(pos, item)
-- Take item in any format
local stack = ItemStack(item)
local obj = core.add_entity(pos, "__builtin:item")
-- Don't use obj if it couldn't be added to the map.
if obj then
obj:get_luaentity():set_item(stack:to_string())
end
return obj
end
-- If item_entity_ttl is not set, enity will have default life time
-- Setting it to -1 disables the feature
local time_to_live = tonumber(core.setting_get("item_entity_ttl"))
if not time_to_live then
time_to_live = 900
end
core.register_entity(":__builtin:item", {
initial_properties = {
hp_max = 1,
physical = true,
collide_with_objects = false,
collisionbox = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3},
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 = '',
physical_state = true,
age = 0,
set_item = function(self, itemstring)
self.itemstring = itemstring
local stack = ItemStack(itemstring)
local count = stack:get_count()
local max_count = stack:get_stack_max()
if count > max_count then
count = max_count
self.itemstring = stack:get_name().." "..max_count
end
local s = 0.2 + 0.1 * (count / max_count)
local c = s
local itemtable = stack:to_table()
local itemname = nil
if itemtable then
itemname = stack:to_table().name
end
local item_texture = nil
local item_type = ""
if core.registered_items[itemname] then
item_texture = core.registered_items[itemname].inventory_image
item_type = core.registered_items[itemname].type
end
local prop = {
is_visible = true,
visual = "wielditem",
textures = {itemname},
visual_size = {x = s, y = s},
collisionbox = {-c, -c, -c, c, c, c},
automatic_rotate = math.pi * 0.5,
}
self.object:set_properties(prop)
end,
get_staticdata = function(self)
return core.serialize({
itemstring = self.itemstring,
always_collect = self.always_collect,
age = self.age,
dropped_by = self.dropped_by
})
end,
on_activate = function(self, staticdata, dtime_s)
if string.sub(staticdata, 1, string.len("return")) == "return" then
local data = core.deserialize(staticdata)
if data and type(data) == "table" then
self.itemstring = data.itemstring
self.always_collect = data.always_collect
if data.age then
self.age = data.age + dtime_s
else
self.age = dtime_s
end
self.dropped_by = data.dropped_by
end
else
self.itemstring = staticdata
end
self.object:set_armor_groups({immortal = 1})
self.object:setvelocity({x = 0, y = 2, z = 0})
self.object:setacceleration({x = 0, y = -10, z = 0})
self:set_item(self.itemstring)
end,
try_merge_with = function(self, own_stack, object, obj)
local stack = ItemStack(obj.itemstring)
if own_stack:get_name() == stack:get_name() and stack:get_free_space() > 0 then
local overflow = false
local count = stack:get_count() + own_stack:get_count()
local max_count = stack:get_stack_max()
if count > max_count then
overflow = true
count = count - max_count
else
self.itemstring = ''
end
local pos = object:getpos()
pos.y = pos.y + (count - stack:get_count()) / max_count * 0.15
object:moveto(pos, false)
local s, c
local max_count = stack:get_stack_max()
local name = stack:get_name()
if not overflow then
obj.itemstring = name .. " " .. count
s = 0.2 + 0.1 * (count / max_count)
c = s
object:set_properties({
visual_size = {x = s, y = s},
collisionbox = {-c, -c, -c, c, c, c}
})
self.object:remove()
-- merging succeeded
return true
else
s = 0.4
c = 0.3
object:set_properties({
visual_size = {x = s, y = s},
collisionbox = {-c, -c, -c, c, c, c}
})
obj.itemstring = name .. " " .. max_count
s = 0.2 + 0.1 * (count / max_count)
c = s
self.object:set_properties({
visual_size = {x = s, y = s},
collisionbox = {-c, -c, -c, c, c, c}
})
self.itemstring = name .. " " .. count
end
end
-- merging didn't succeed
return false
end,
on_step = function(self, dtime)
self.age = self.age + dtime
if time_to_live > 0 and self.age > time_to_live then
self.itemstring = ''
self.object:remove()
return
end
local p = self.object:getpos()
p.y = p.y - 0.5
local node = core.get_node_or_nil(p)
local in_unloaded = (node == nil)
if in_unloaded then
-- Don't infinetly fall into unloaded map
self.object:setvelocity({x = 0, y = 0, z = 0})
self.object:setacceleration({x = 0, y = 0, z = 0})
self.physical_state = false
self.object:set_properties({physical = false})
return
end
local nn = node.name
-- If node is not registered or node is walkably solid and resting on nodebox
local v = self.object:getvelocity()
if not core.registered_nodes[nn] or core.registered_nodes[nn].walkable and v.y == 0 then
if self.physical_state then
local own_stack = ItemStack(self.object:get_luaentity().itemstring)
-- Merge with close entities of the same item
for _, object in ipairs(core.get_objects_inside_radius(p, 0.8)) do
local obj = object:get_luaentity()
if obj and obj.name == "__builtin:item"
and obj.physical_state == false then
if self:try_merge_with(own_stack, object, obj) then
return
end
end
end
self.object:setvelocity({x = 0, y = 0, z = 0})
self.object:setacceleration({x = 0, y = 0, z = 0})
self.physical_state = false
self.object:set_properties({physical = false})
end
else
if not self.physical_state then
self.object:setvelocity({x = 0, y = 0, z = 0})
self.object:setacceleration({x = 0, y = -10, z = 0})
self.physical_state = true
self.object:set_properties({physical = true})
end
end
end,
on_punch = function(self, hitter)
local inv = hitter:get_inventory()
if inv and self.itemstring ~= '' then
local left = inv:add_item("main", self.itemstring)
if left and not left:is_empty() then
self.itemstring = left:to_string()
return
end
end
self.itemstring = ''
self.object:remove()
end,
})

View File

@ -0,0 +1,250 @@
-- Minetest: builtin/misc.lua
--
-- Misc. API functions
--
local jobs = {}
local time = 0.0
local last = core.get_us_time() / 1000000
core.register_globalstep(function(dtime)
local new = core.get_us_time() / 1000000
if new > last then
time = time + (new - last)
else
-- Overflow, we may lose a little bit of time here but
-- only 1 tick max, potentially running timers slightly
-- too early.
time = time + new
end
last = new
if #jobs < 1 then
return
end
-- 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.
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)
end
end
end)
function core.after(after, func, ...)
assert(tonumber(after) and type(func) == "function",
"Invalid core.after invocation")
jobs[#jobs + 1] = {
func = func,
expire = time + after,
arg = {...},
mod_origin = core.get_last_run_mod()
}
end
function core.check_player_privs(name, ...)
local arg_type = type(name)
if (arg_type == "userdata" or arg_type == "table") and
name.get_player_name then -- If it quacks like a Player...
name = name:get_player_name()
elseif arg_type ~= "string" then
error("Invalid core.check_player_privs argument type: " .. arg_type, 2)
end
local requested_privs = {...}
local player_privs = core.get_player_privs(name)
local missing_privileges = {}
if type(requested_privs[1]) == "table" then
-- We were provided with a table like { privA = true, privB = true }.
for priv, value in pairs(requested_privs[1]) do
if value and not player_privs[priv] then
missing_privileges[#missing_privileges + 1] = priv
end
end
else
-- Only a list, we can process it directly.
for key, priv in pairs(requested_privs) do
if not player_privs[priv] then
missing_privileges[#missing_privileges + 1] = priv
end
end
end
if #missing_privileges > 0 then
return false, missing_privileges
end
return true, ""
end
local player_list = {}
core.register_on_joinplayer(function(player)
local player_name = player:get_player_name()
player_list[player_name] = player
if not minetest.is_singleplayer() then
core.chat_send_all("*** " .. player_name .. " joined the game.")
end
end)
core.register_on_leaveplayer(function(player, timed_out)
local player_name = player:get_player_name()
player_list[player_name] = nil
local announcement = "*** " .. player_name .. " left the game."
if timed_out then
announcement = announcement .. " (timed out)"
end
core.chat_send_all(announcement)
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 minetest.player_exists(name)
return minetest.get_auth_handler().get_auth(name) ~= nil
end
-- Returns two position vectors representing a box of `radius` in each
-- direction centered around the player corresponding to `player_name`
function core.get_player_radius_area(player_name, radius)
local player = core.get_player_by_name(player_name)
if player == nil then
return nil
end
local p1 = player:getpos()
local p2 = p1
if radius then
p1 = vector.subtract(p1, radius)
p2 = vector.add(p2, radius)
end
return p1, p2
end
function core.hash_node_position(pos)
return (pos.z+32768)*65536*65536 + (pos.y+32768)*65536 + pos.x+32768
end
function core.get_position_from_hash(hash)
local pos = {}
pos.x = (hash%65536) - 32768
hash = math.floor(hash/65536)
pos.y = (hash%65536) - 32768
hash = math.floor(hash/65536)
pos.z = (hash%65536) - 32768
return pos
end
function core.get_item_group(name, group)
if not core.registered_items[name] or not
core.registered_items[name].groups[group] then
return 0
end
return core.registered_items[name].groups[group]
end
function core.get_node_group(name, group)
core.log("deprecated", "Deprecated usage of get_node_group, use get_item_group instead")
return core.get_item_group(name, group)
end
function core.setting_get_pos(name)
local value = core.setting_get(name)
if not value then
return nil
end
return core.string_to_pos(value)
end
-- To be overriden by protection mods
function core.is_protected(pos, name)
return false
end
function core.record_protection_violation(pos, name)
for _, func in pairs(core.registered_on_protection_violation) do
func(pos, name)
end
end
local raillike_ids = {}
local raillike_cur_id = 0
function core.raillike_group(name)
local id = raillike_ids[name]
if not id then
raillike_cur_id = raillike_cur_id + 1
raillike_ids[name] = raillike_cur_id
id = raillike_cur_id
end
return id
end
-- HTTP callback interface
function core.http_add_fetch(httpenv)
httpenv.fetch = function(req, callback)
local handle = httpenv.fetch_async(req)
local function update_http_status()
local res = httpenv.fetch_async_get(handle)
if res.completed then
callback(res)
else
core.after(0, update_http_status)
end
end
core.after(0, update_http_status)
end
return httpenv
end
if minetest.setting_getbool("disable_escape_sequences") then
function core.get_color_escape_sequence(color)
return ""
end
function core.get_background_escape_sequence(color)
return ""
end
function core.colorize(color, message)
return message
end
else
local ESCAPE_CHAR = string.char(0x1b)
function core.get_color_escape_sequence(color)
return ESCAPE_CHAR .. "(c@" .. color .. ")"
end
function core.get_background_escape_sequence(color)
return ESCAPE_CHAR .. "(b@" .. color .. ")"
end
function core.colorize(color, message)
return core.get_color_escape_sequence(color) .. message .. core.get_color_escape_sequence("#ffffff")
end
end
function core.close_formspec(player_name, formname)
return minetest.show_formspec(player_name, formname, "")
end

View File

@ -0,0 +1,92 @@
-- Minetest: builtin/privileges.lua
--
-- Privileges
--
core.registered_privileges = {}
function core.register_privilege(name, param)
local function fill_defaults(def)
if def.give_to_singleplayer == nil then
def.give_to_singleplayer = true
end
if def.description == nil then
def.description = "(no description)"
end
end
local def = {}
if type(param) == "table" then
def = param
else
def = {description = param}
end
fill_defaults(def)
core.registered_privileges[name] = def
end
core.register_privilege("interact", "Can interact with things and modify the world")
core.register_privilege("shout", "Can speak in chat")
core.register_privilege("basic_privs", "Can modify 'shout' and 'interact' privileges")
core.register_privilege("privs", "Can modify privileges")
core.register_privilege("teleport", {
description = "Can use /teleport command",
give_to_singleplayer = false,
})
core.register_privilege("bring", {
description = "Can teleport other players",
give_to_singleplayer = false,
})
core.register_privilege("settime", {
description = "Can use /time",
give_to_singleplayer = false,
})
core.register_privilege("server", {
description = "Can do server maintenance stuff",
give_to_singleplayer = false,
})
core.register_privilege("protection_bypass", {
description = "Can bypass node protection in the world",
give_to_singleplayer = false,
})
core.register_privilege("ban", {
description = "Can ban and unban players",
give_to_singleplayer = false,
})
core.register_privilege("kick", {
description = "Can kick players",
give_to_singleplayer = false,
})
core.register_privilege("give", {
description = "Can use /give and /giveme",
give_to_singleplayer = false,
})
core.register_privilege("password", {
description = "Can use /setpassword and /clearpassword",
give_to_singleplayer = false,
})
core.register_privilege("fly", {
description = "Can fly using the free_move mode",
give_to_singleplayer = false,
})
core.register_privilege("fast", {
description = "Can walk fast using the fast_move mode",
give_to_singleplayer = false,
})
core.register_privilege("noclip", {
description = "Can fly through walls",
give_to_singleplayer = false,
})
core.register_privilege("rollback", {
description = "Can use the rollback functionality",
give_to_singleplayer = false,
})
core.register_privilege("zoom", {
description = "Can zoom the camera",
give_to_singleplayer = false,
})
core.register_privilege("debug", {
description = "Allows enabling various debug options that may affect gameplay",
give_to_singleplayer = false,
})

View File

@ -0,0 +1,562 @@
-- Minetest: builtin/misc_register.lua
--
-- Make raw registration functions inaccessible to anyone except this file
--
local register_item_raw = core.register_item_raw
core.register_item_raw = nil
local unregister_item_raw = core.unregister_item_raw
core.unregister_item_raw = nil
local register_alias_raw = core.register_alias_raw
core.register_alias_raw = nil
--
-- Item / entity / ABM / LBM registration functions
--
core.registered_abms = {}
core.registered_lbms = {}
core.registered_entities = {}
core.registered_items = {}
core.registered_nodes = {}
core.registered_craftitems = {}
core.registered_tools = {}
core.registered_aliases = {}
-- For tables that are indexed by item name:
-- If table[X] does not exist, default to table[core.registered_aliases[X]]
local alias_metatable = {
__index = function(t, name)
return rawget(t, core.registered_aliases[name])
end
}
setmetatable(core.registered_items, alias_metatable)
setmetatable(core.registered_nodes, alias_metatable)
setmetatable(core.registered_craftitems, alias_metatable)
setmetatable(core.registered_tools, alias_metatable)
-- These item names may not be used because they would interfere
-- with legacy itemstrings
local forbidden_item_names = {
MaterialItem = true,
MaterialItem2 = true,
MaterialItem3 = true,
NodeItem = true,
node = true,
CraftItem = true,
craft = true,
MBOItem = true,
ToolItem = true,
tool = true,
}
local function check_modname_prefix(name)
if name:sub(1,1) == ":" then
-- If the name starts with a colon, we can skip the modname prefix
-- mechanism.
return name:sub(2)
else
-- Enforce that the name starts with the correct mod name.
local expected_prefix = core.get_current_modname() .. ":"
if name:sub(1, #expected_prefix) ~= expected_prefix then
error("Name " .. name .. " does not follow naming conventions: " ..
"\"" .. expected_prefix .. "\" or \":\" prefix required")
end
-- Enforce that the name only contains letters, numbers and underscores.
local subname = name:sub(#expected_prefix+1)
if subname:find("[^%w_]") then
error("Name " .. name .. " does not follow naming conventions: " ..
"contains unallowed characters")
end
return name
end
end
function core.register_abm(spec)
-- Add to core.registered_abms
core.registered_abms[#core.registered_abms + 1] = spec
spec.mod_origin = core.get_current_modname() or "??"
end
function core.register_lbm(spec)
-- Add to core.registered_lbms
check_modname_prefix(spec.name)
core.registered_lbms[#core.registered_lbms + 1] = spec
spec.mod_origin = core.get_current_modname() or "??"
end
function core.register_entity(name, prototype)
-- Check name
if name == nil then
error("Unable to register entity: Name is nil")
end
name = check_modname_prefix(tostring(name))
prototype.name = name
prototype.__index = prototype -- so that it can be used as a metatable
-- Add to core.registered_entities
core.registered_entities[name] = prototype
prototype.mod_origin = core.get_current_modname() or "??"
end
function core.register_item(name, itemdef)
-- Check name
if name == nil then
error("Unable to register item: Name is nil")
end
name = check_modname_prefix(tostring(name))
if forbidden_item_names[name] then
error("Unable to register item: Name is forbidden: " .. name)
end
itemdef.name = name
-- Apply defaults and add to registered_* table
if itemdef.type == "node" then
-- Use the nodebox as selection box if it's not set manually
if itemdef.drawtype == "nodebox" and not itemdef.selection_box then
itemdef.selection_box = itemdef.node_box
elseif itemdef.drawtype == "fencelike" and not itemdef.selection_box then
itemdef.selection_box = {
type = "fixed",
fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8},
}
end
if itemdef.light_source and itemdef.light_source > core.LIGHT_MAX then
itemdef.light_source = core.LIGHT_MAX
core.log("warning", "Node 'light_source' value exceeds maximum," ..
" limiting to maximum: " ..name)
end
setmetatable(itemdef, {__index = core.nodedef_default})
core.registered_nodes[itemdef.name] = itemdef
elseif itemdef.type == "craft" then
setmetatable(itemdef, {__index = core.craftitemdef_default})
core.registered_craftitems[itemdef.name] = itemdef
elseif itemdef.type == "tool" then
setmetatable(itemdef, {__index = core.tooldef_default})
core.registered_tools[itemdef.name] = itemdef
elseif itemdef.type == "none" then
setmetatable(itemdef, {__index = core.noneitemdef_default})
else
error("Unable to register item: Type is invalid: " .. dump(itemdef))
end
-- Flowing liquid uses param2
if itemdef.type == "node" and itemdef.liquidtype == "flowing" then
itemdef.paramtype2 = "flowingliquid"
end
-- BEGIN Legacy stuff
if itemdef.cookresult_itemstring ~= nil and itemdef.cookresult_itemstring ~= "" then
core.register_craft({
type="cooking",
output=itemdef.cookresult_itemstring,
recipe=itemdef.name,
cooktime=itemdef.furnace_cooktime
})
end
if itemdef.furnace_burntime ~= nil and itemdef.furnace_burntime >= 0 then
core.register_craft({
type="fuel",
recipe=itemdef.name,
burntime=itemdef.furnace_burntime
})
end
-- END Legacy stuff
itemdef.mod_origin = core.get_current_modname() or "??"
-- Disable all further modifications
getmetatable(itemdef).__newindex = {}
--core.log("Registering item: " .. itemdef.name)
core.registered_items[itemdef.name] = itemdef
core.registered_aliases[itemdef.name] = nil
register_item_raw(itemdef)
end
function core.unregister_item(name)
if not core.registered_items[name] then
core.log("warning", "Not unregistering item " ..name..
" because it doesn't exist.")
return
end
-- Erase from registered_* table
local type = core.registered_items[name].type
if type == "node" then
core.registered_nodes[name] = nil
elseif type == "craft" then
core.registered_craftitems[name] = nil
elseif type == "tool" then
core.registered_tools[name] = nil
end
core.registered_items[name] = nil
unregister_item_raw(name)
end
function core.register_node(name, nodedef)
nodedef.type = "node"
core.register_item(name, nodedef)
end
function core.register_craftitem(name, craftitemdef)
craftitemdef.type = "craft"
-- BEGIN Legacy stuff
if craftitemdef.inventory_image == nil and craftitemdef.image ~= nil then
craftitemdef.inventory_image = craftitemdef.image
end
-- END Legacy stuff
core.register_item(name, craftitemdef)
end
function core.register_tool(name, tooldef)
tooldef.type = "tool"
tooldef.stack_max = 1
-- BEGIN Legacy stuff
if tooldef.inventory_image == nil and tooldef.image ~= nil then
tooldef.inventory_image = tooldef.image
end
if tooldef.tool_capabilities == nil and
(tooldef.full_punch_interval ~= nil or
tooldef.basetime ~= nil or
tooldef.dt_weight ~= nil or
tooldef.dt_crackiness ~= nil or
tooldef.dt_crumbliness ~= nil or
tooldef.dt_cuttability ~= nil or
tooldef.basedurability ~= nil or
tooldef.dd_weight ~= nil or
tooldef.dd_crackiness ~= nil or
tooldef.dd_crumbliness ~= nil or
tooldef.dd_cuttability ~= nil) then
tooldef.tool_capabilities = {
full_punch_interval = tooldef.full_punch_interval,
basetime = tooldef.basetime,
dt_weight = tooldef.dt_weight,
dt_crackiness = tooldef.dt_crackiness,
dt_crumbliness = tooldef.dt_crumbliness,
dt_cuttability = tooldef.dt_cuttability,
basedurability = tooldef.basedurability,
dd_weight = tooldef.dd_weight,
dd_crackiness = tooldef.dd_crackiness,
dd_crumbliness = tooldef.dd_crumbliness,
dd_cuttability = tooldef.dd_cuttability,
}
end
-- END Legacy stuff
core.register_item(name, tooldef)
end
function core.register_alias(name, convert_to)
if forbidden_item_names[name] then
error("Unable to register alias: Name is forbidden: " .. name)
end
if core.registered_items[name] ~= nil then
core.log("warning", "Not registering alias, item with same name" ..
" is already defined: " .. name .. " -> " .. convert_to)
else
--core.log("Registering alias: " .. name .. " -> " .. convert_to)
core.registered_aliases[name] = convert_to
register_alias_raw(name, convert_to)
end
end
function core.register_alias_force(name, convert_to)
if forbidden_item_names[name] then
error("Unable to register alias: Name is forbidden: " .. name)
end
if core.registered_items[name] ~= nil then
core.unregister_item(name)
core.log("info", "Removed item " ..name..
" while attempting to force add an alias")
end
--core.log("Registering alias: " .. name .. " -> " .. convert_to)
core.registered_aliases[name] = convert_to
register_alias_raw(name, convert_to)
end
function core.on_craft(itemstack, player, old_craft_list, craft_inv)
for _, func in ipairs(core.registered_on_crafts) do
itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack
end
return itemstack
end
function core.craft_predict(itemstack, player, old_craft_list, craft_inv)
for _, func in ipairs(core.registered_craft_predicts) do
itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack
end
return itemstack
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:
-- 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
--
-- Built-in node definitions. Also defined in C.
--
core.register_item(":unknown", {
type = "none",
description = "Unknown Item",
inventory_image = "unknown_item.png",
on_place = core.item_place,
on_secondary_use = core.item_secondary_use,
on_drop = core.item_drop,
groups = {not_in_creative_inventory=1},
diggable = true,
})
core.register_node(":air", {
description = "Air (you hacker you!)",
inventory_image = "air.png",
wield_image = "air.png",
drawtype = "airlike",
paramtype = "light",
sunlight_propagates = true,
walkable = false,
pointable = false,
diggable = false,
buildable_to = true,
floodable = true,
air_equivalent = true,
drop = "",
groups = {not_in_creative_inventory=1},
})
core.register_node(":ignore", {
description = "Ignore (you hacker you!)",
inventory_image = "ignore.png",
wield_image = "ignore.png",
drawtype = "airlike",
paramtype = "none",
sunlight_propagates = false,
walkable = false,
pointable = false,
diggable = false,
buildable_to = true, -- A way to remove accidentally placed ignores
air_equivalent = true,
drop = "",
groups = {not_in_creative_inventory=1},
})
-- The hand (bare definition)
core.register_item(":", {
type = "none",
groups = {not_in_creative_inventory=1},
})
function core.override_item(name, redefinition)
if redefinition.name ~= nil then
error("Attempt to redefine name of "..name.." to "..dump(redefinition.name), 2)
end
if redefinition.type ~= nil then
error("Attempt to redefine type of "..name.." to "..dump(redefinition.type), 2)
end
local item = core.registered_items[name]
if not item then
error("Attempt to override non-existent item "..name, 2)
end
for k, v in pairs(redefinition) do
rawset(item, k, v)
end
register_item_raw(item)
end
core.callback_origins = {}
function core.run_callbacks(callbacks, mode, ...)
assert(type(callbacks) == "table")
local cb_len = #callbacks
if cb_len == 0 then
if mode == 2 or mode == 3 then
return true
elseif mode == 4 or mode == 5 then
return false
end
end
local ret = nil
for i = 1, cb_len do
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](...)
if mode == 0 and i == 1 then
ret = cb_ret
elseif mode == 1 and i == cb_len then
ret = cb_ret
elseif mode == 2 then
if not cb_ret or i == 1 then
ret = cb_ret
end
elseif mode == 3 then
if cb_ret then
return cb_ret
end
ret = cb_ret
elseif mode == 4 then
if (cb_ret and not ret) or i == 1 then
ret = cb_ret
end
elseif mode == 5 and cb_ret then
return cb_ret
end
end
return ret
end
--
-- Callback registration
--
local function make_registration()
local t = {}
local registerfunc = function(func)
t[#t + 1] = func
core.callback_origins[func] = {
mod = core.get_current_modname() or "??",
name = debug.getinfo(1, "n").name or "??"
}
--local origin = core.callback_origins[func]
--print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func))
end
return t, registerfunc
end
local function make_registration_reverse()
local t = {}
local registerfunc = function(func)
table.insert(t, 1, func)
core.callback_origins[func] = {
mod = core.get_current_modname() or "??",
name = debug.getinfo(1, "n").name or "??"
}
--local origin = core.callback_origins[func]
--print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func))
end
return t, registerfunc
end
local function make_registration_wrap(reg_fn_name, clear_fn_name)
local list = {}
local orig_reg_fn = core[reg_fn_name]
core[reg_fn_name] = function(def)
local retval = orig_reg_fn(def)
if retval ~= nil then
if def.name ~= nil then
list[def.name] = def
else
list[retval] = def
end
end
return retval
end
local orig_clear_fn = core[clear_fn_name]
core[clear_fn_name] = function()
for k in pairs(list) do
list[k] = nil
end
return orig_clear_fn()
end
return list
end
core.registered_on_player_hpchanges = { modifiers = { }, loggers = { } }
function core.registered_on_player_hpchange(player, hp_change)
local last = false
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)
if type(hp_change) ~= "number" then
local debuginfo = debug.getinfo(func)
error("The register_on_hp_changes function has to return a number at " ..
debuginfo.short_src .. " line " .. debuginfo.linedefined)
end
if last then
break
end
end
for i, func in ipairs(core.registered_on_player_hpchanges.loggers) do
func(player, hp_change)
end
return hp_change
end
function core.register_on_player_hpchange(func, modifier)
if modifier then
core.registered_on_player_hpchanges.modifiers[#core.registered_on_player_hpchanges.modifiers + 1] = func
else
core.registered_on_player_hpchanges.loggers[#core.registered_on_player_hpchanges.loggers + 1] = func
end
core.callback_origins[func] = {
mod = core.get_current_modname() or "??",
name = debug.getinfo(1, "n").name or "??"
}
end
core.registered_biomes = make_registration_wrap("register_biome", "clear_registered_biomes")
core.registered_ores = make_registration_wrap("register_ore", "clear_registered_ores")
core.registered_decorations = make_registration_wrap("register_decoration", "clear_registered_decorations")
core.registered_on_chat_messages, core.register_on_chat_message = make_registration()
core.registered_globalsteps, core.register_globalstep = make_registration()
core.registered_playerevents, core.register_playerevent = make_registration()
core.registered_on_shutdown, core.register_on_shutdown = make_registration()
core.registered_on_punchnodes, core.register_on_punchnode = make_registration()
core.registered_on_placenodes, core.register_on_placenode = make_registration()
core.registered_on_dignodes, core.register_on_dignode = make_registration()
core.registered_on_generateds, core.register_on_generated = make_registration()
core.registered_on_newplayers, core.register_on_newplayer = make_registration()
core.registered_on_dieplayers, core.register_on_dieplayer = make_registration()
core.registered_on_respawnplayers, core.register_on_respawnplayer = make_registration()
core.registered_on_prejoinplayers, core.register_on_prejoinplayer = make_registration()
core.registered_on_joinplayers, core.register_on_joinplayer = make_registration()
core.registered_on_leaveplayers, core.register_on_leaveplayer = make_registration()
core.registered_on_player_receive_fields, core.register_on_player_receive_fields = make_registration_reverse()
core.registered_on_cheats, core.register_on_cheat = make_registration()
core.registered_on_crafts, core.register_on_craft = make_registration()
core.registered_craft_predicts, core.register_craft_predict = make_registration()
core.registered_on_protection_violation, core.register_on_protection_violation = make_registration()
core.registered_on_item_eats, core.register_on_item_eat = make_registration()
core.registered_on_punchplayers, core.register_on_punchplayer = make_registration()
--
-- Compatibility for on_mapgen_init()
--
core.register_on_mapgen_init = function(func) func(core.get_mapgen_params()) end

View File

@ -0,0 +1,165 @@
local health_bar_definition =
{
hud_elem_type = "statbar",
position = { x=0.5, y=1 },
text = "heart.png",
number = 20,
direction = 0,
size = { x=24, y=24 },
offset = { x=(-10*24)-25, y=-(48+24+16)},
}
local breath_bar_definition =
{
hud_elem_type = "statbar",
position = { x=0.5, y=1 },
text = "bubble.png",
number = 20,
direction = 0,
size = { x=24, y=24 },
offset = {x=25,y=-(48+24+16)},
}
local hud_ids = {}
local function initialize_builtin_statbars(player)
if not player:is_player() then
return
end
local name = player:get_player_name()
if name == "" then
return
end
if (hud_ids[name] == nil) then
hud_ids[name] = {}
-- flags are not transmitted to client on connect, we need to make sure
-- our current flags are transmitted by sending them actively
player:hud_set_flags(player:hud_get_flags())
end
if player:hud_get_flags().healthbar and
core.is_yes(core.setting_get("enable_damage")) then
if hud_ids[name].id_healthbar == nil then
health_bar_definition.number = player:get_hp()
hud_ids[name].id_healthbar = player:hud_add(health_bar_definition)
end
else
if hud_ids[name].id_healthbar ~= nil then
player:hud_remove(hud_ids[name].id_healthbar)
hud_ids[name].id_healthbar = nil
end
end
if (player:get_breath() < 11) then
if player:hud_get_flags().breathbar and
core.is_yes(core.setting_get("enable_damage")) then
if hud_ids[name].id_breathbar == nil then
hud_ids[name].id_breathbar = player:hud_add(breath_bar_definition)
end
else
if hud_ids[name].id_breathbar ~= nil then
player:hud_remove(hud_ids[name].id_breathbar)
hud_ids[name].id_breathbar = nil
end
end
elseif hud_ids[name].id_breathbar ~= nil then
player:hud_remove(hud_ids[name].id_breathbar)
hud_ids[name].id_breathbar = nil
end
end
local function cleanup_builtin_statbars(player)
if not player:is_player() then
return
end
local name = player:get_player_name()
if name == "" then
return
end
hud_ids[name] = nil
end
local function player_event_handler(player,eventname)
assert(player:is_player())
local name = player:get_player_name()
if name == "" then
return
end
if eventname == "health_changed" then
initialize_builtin_statbars(player)
if hud_ids[name].id_healthbar ~= nil then
player:hud_change(hud_ids[name].id_healthbar,"number",player:get_hp())
return true
end
end
if eventname == "breath_changed" then
initialize_builtin_statbars(player)
if hud_ids[name].id_breathbar ~= nil then
player:hud_change(hud_ids[name].id_breathbar,"number",player:get_breath()*2)
return true
end
end
if eventname == "hud_changed" then
initialize_builtin_statbars(player)
return true
end
return false
end
function core.hud_replace_builtin(name, definition)
if definition == nil or
type(definition) ~= "table" or
definition.hud_elem_type ~= "statbar" then
return false
end
if name == "health" then
health_bar_definition = definition
for name,ids in pairs(hud_ids) do
local player = core.get_player_by_name(name)
if player and hud_ids[name].id_healthbar then
player:hud_remove(hud_ids[name].id_healthbar)
initialize_builtin_statbars(player)
end
end
return true
end
if name == "breath" then
breath_bar_definition = definition
for name,ids in pairs(hud_ids) do
local player = core.get_player_by_name(name)
if player and hud_ids[name].id_breathbar then
player:hud_remove(hud_ids[name].id_breathbar)
initialize_builtin_statbars(player)
end
end
return true
end
return false
end
core.register_on_joinplayer(initialize_builtin_statbars)
core.register_on_leaveplayer(cleanup_builtin_statbars)
core.register_playerevent(player_event_handler)

View File

@ -0,0 +1,25 @@
-- Minetest: builtin/static_spawn.lua
local function warn_invalid_static_spawnpoint()
if core.setting_get("static_spawnpoint") and
not core.setting_get_pos("static_spawnpoint") then
core.log("error", "The static_spawnpoint setting is invalid: \""..
core.setting_get("static_spawnpoint").."\"")
end
end
warn_invalid_static_spawnpoint()
local function put_player_in_spawn(player_obj)
local static_spawnpoint = core.setting_get_pos("static_spawnpoint")
if not static_spawnpoint then
return false
end
core.log("action", "Moving " .. player_obj:get_player_name() ..
" to static spawnpoint at " .. core.pos_to_string(static_spawnpoint))
player_obj:setpos(static_spawnpoint)
return true
end
core.register_on_newplayer(put_player_in_spawn)
core.register_on_respawnplayer(put_player_in_spawn)

View File

@ -0,0 +1,132 @@
VoxelArea = {
MinEdge = {x=1, y=1, z=1},
MaxEdge = {x=0, y=0, z=0},
ystride = 0,
zstride = 0,
}
function VoxelArea:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
local e = o:getExtent()
o.ystride = e.x
o.zstride = e.x * e.y
return o
end
function VoxelArea:getExtent()
local MaxEdge, MinEdge = self.MaxEdge, self.MinEdge
return {
x = MaxEdge.x - MinEdge.x + 1,
y = MaxEdge.y - MinEdge.y + 1,
z = MaxEdge.z - MinEdge.z + 1,
}
end
function VoxelArea:getVolume()
local e = self:getExtent()
return e.x * e.y * e.z
end
function VoxelArea:index(x, y, z)
local MinEdge = self.MinEdge
local i = (z - MinEdge.z) * self.zstride +
(y - MinEdge.y) * self.ystride +
(x - MinEdge.x) + 1
return math.floor(i)
end
function VoxelArea:indexp(p)
local MinEdge = self.MinEdge
local i = (p.z - MinEdge.z) * self.zstride +
(p.y - MinEdge.y) * self.ystride +
(p.x - MinEdge.x) + 1
return math.floor(i)
end
function VoxelArea:position(i)
local p = {}
local MinEdge = self.MinEdge
i = i - 1
p.z = math.floor(i / self.zstride) + MinEdge.z
i = i % self.zstride
p.y = math.floor(i / self.ystride) + MinEdge.y
i = i % self.ystride
p.x = math.floor(i) + MinEdge.x
return p
end
function VoxelArea:contains(x, y, z)
local MaxEdge, MinEdge = self.MaxEdge, self.MinEdge
return (x >= MinEdge.x) and (x <= MaxEdge.x) and
(y >= MinEdge.y) and (y <= MaxEdge.y) and
(z >= MinEdge.z) and (z <= MaxEdge.z)
end
function VoxelArea:containsp(p)
local MaxEdge, MinEdge = self.MaxEdge, self.MinEdge
return (p.x >= MinEdge.x) and (p.x <= MaxEdge.x) and
(p.y >= MinEdge.y) and (p.y <= MaxEdge.y) and
(p.z >= MinEdge.z) and (p.z <= MaxEdge.z)
end
function VoxelArea:containsi(i)
return (i >= 1) and (i <= self:getVolume())
end
function VoxelArea:iter(minx, miny, minz, maxx, maxy, maxz)
local i = self:index(minx, miny, minz) - 1
local xrange = maxx - minx + 1
local nextaction = i + 1 + xrange
local y = 0
local yrange = maxy - miny + 1
local yreqstride = self.ystride - xrange
local z = 0
local zrange = maxz - minz + 1
local multistride = self.zstride - ((yrange - 1) * self.ystride + xrange)
return function()
-- continue i until it needs to jump
i = i + 1
if i ~= nextaction then
return i
end
-- continue y until maxy is exceeded
y = y + 1
if y ~= yrange then
-- set i to index(minx, miny + y, minz + z) - 1
i = i + yreqstride
nextaction = i + xrange
return i
end
-- continue z until maxz is exceeded
z = z + 1
if z == zrange then
-- cuboid finished, return nil
return
end
-- set i to index(minx, miny, minz + z) - 1
i = i + multistride
y = 0
nextaction = i + xrange
return i
end
end
function VoxelArea:iterp(minp, maxp)
return self:iter(minp.x, minp.y, minp.z, maxp.x, maxp.y, maxp.z)
end

50
minetest/builtin/init.lua Normal file
View File

@ -0,0 +1,50 @@
--
-- This file contains built-in stuff in Minetest implemented in Lua.
--
-- It is always loaded and executed after registration of the C API,
-- before loading and running any mods.
--
-- Initialize some very basic things
function core.debug(...) core.log(table.concat({...}, "\t")) end
if core.print then
local core_print = core.print
-- Override native print and use
-- terminal if that's turned on
function print(...)
local n, t = select("#", ...), {...}
for i = 1, n do
t[i] = tostring(t[i])
end
core_print(table.concat(t, "\t"))
end
core.print = nil -- don't pollute our namespace
end
math.randomseed(os.time())
os.setlocale("C", "numeric")
minetest = core
-- Load other files
local scriptdir = core.get_builtin_path() .. DIR_DELIM
local gamepath = scriptdir .. "game" .. DIR_DELIM
local commonpath = scriptdir .. "common" .. DIR_DELIM
local asyncpath = scriptdir .. "async" .. DIR_DELIM
dofile(commonpath .. "strict.lua")
dofile(commonpath .. "serialize.lua")
dofile(commonpath .. "misc_helpers.lua")
if INIT == "game" then
dofile(gamepath .. "init.lua")
elseif INIT == "mainmenu" then
local mm_script = core.setting_get("main_menu_script")
if mm_script and mm_script ~= "" then
dofile(mm_script)
else
dofile(core.get_mainmenu_path() .. DIR_DELIM .. "init.lua")
end
elseif INIT == "async" then
dofile(asyncpath .. "init.lua")
else
error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT)))
end

View File

@ -0,0 +1,335 @@
--Minetest
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--------------------------------------------------------------------------------
-- Global menu data
--------------------------------------------------------------------------------
menudata = {}
--------------------------------------------------------------------------------
-- Local cached values
--------------------------------------------------------------------------------
local min_supp_proto, max_supp_proto
function common_update_cached_supp_proto()
min_supp_proto = core.get_min_supp_proto()
max_supp_proto = core.get_max_supp_proto()
end
common_update_cached_supp_proto()
--------------------------------------------------------------------------------
-- Menu helper functions
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local function render_client_count(n)
if n > 99 then return '99+'
elseif n >= 0 then return tostring(n)
else return '?' end
end
local function configure_selected_world_params(idx)
local worldconfig = modmgr.get_worldconfig(menudata.worldlist:get_list()[idx].path)
if worldconfig.creative_mode then
core.setting_set("creative_mode", worldconfig.creative_mode)
end
if worldconfig.enable_damage then
core.setting_set("enable_damage", worldconfig.enable_damage)
end
end
--------------------------------------------------------------------------------
function image_column(tooltip, flagname)
return "image,tooltip=" .. core.formspec_escape(tooltip) .. "," ..
"0=" .. core.formspec_escape(defaulttexturedir .. "blank.png") .. "," ..
"1=" .. core.formspec_escape(defaulttexturedir .. "server_flags_" .. flagname .. ".png") .. "," ..
"2=" .. core.formspec_escape(defaulttexturedir .. "server_ping_4.png") .. "," ..
"3=" .. core.formspec_escape(defaulttexturedir .. "server_ping_3.png") .. "," ..
"4=" .. core.formspec_escape(defaulttexturedir .. "server_ping_2.png") .. "," ..
"5=" .. core.formspec_escape(defaulttexturedir .. "server_ping_1.png")
end
--------------------------------------------------------------------------------
function order_favorite_list(list)
local res = {}
--orders the favorite list after support
for i = 1, #list do
local fav = list[i]
if is_server_protocol_compat(fav.proto_min, fav.proto_max) then
res[#res + 1] = fav
end
end
for i = 1, #list do
local fav = list[i]
if not is_server_protocol_compat(fav.proto_min, fav.proto_max) then
res[#res + 1] = fav
end
end
return res
end
--------------------------------------------------------------------------------
function render_serverlist_row(spec, is_favorite)
local text = ""
if spec.name then
text = text .. core.formspec_escape(spec.name:trim())
elseif spec.address then
text = text .. spec.address:trim()
if spec.port then
text = text .. ":" .. spec.port
end
end
local details = ""
local grey_out = not is_server_protocol_compat(spec.proto_min, spec.proto_max)
if is_favorite then
details = "1,"
else
details = "0,"
end
if spec.ping then
local ping = spec.ping * 1000
if ping <= 50 then
details = details .. "2,"
elseif ping <= 100 then
details = details .. "3,"
elseif ping <= 250 then
details = details .. "4,"
else
details = details .. "5,"
end
else
details = details .. "0,"
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)
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
elseif clients_percent <= 90 then clients_color = '#ffdc97' -- 60-90%: yellow
elseif clients_percent == 100 then clients_color = '#dd5b5b' -- full server: red (darker)
else clients_color = '#ffba97' -- 90-100%: orange
end
details = details .. clients_color .. ',' ..
render_client_count(spec.clients) .. ',/,' ..
render_client_count(spec.clients_max) .. ','
elseif grey_out then
details = details .. '#aaaaaa,?,/,?,'
else
details = details .. ',?,/,?,'
end
if spec.creative then
details = details .. "1,"
else
details = details .. "0,"
end
if spec.damage then
details = details .. "1,"
else
details = details .. "0,"
end
if spec.pvp then
details = details .. "1,"
else
details = details .. "0,"
end
return details .. (grey_out and '#aaaaaa,' or ',') .. text
end
--------------------------------------------------------------------------------
os.tempfolder = function()
if core.setting_get("TMPFolder") then
return core.setting_get("TMPFolder") .. DIR_DELIM .. "MT_" .. math.random(0,10000)
end
local filetocheck = os.tmpname()
os.remove(filetocheck)
local randname = "MTTempModFolder_" .. math.random(0,10000)
if DIR_DELIM == "\\" then
local tempfolder = os.getenv("TEMP")
return tempfolder .. filetocheck
else
local backstring = filetocheck:reverse()
return filetocheck:sub(0,filetocheck:len()-backstring:find(DIR_DELIM)+1) ..randname
end
end
--------------------------------------------------------------------------------
function menu_render_worldlist()
local retval = ""
local current_worldlist = menudata.worldlist:get_list()
for i, v in ipairs(current_worldlist) do
if retval ~= "" then retval = retval .. "," end
retval = retval .. core.formspec_escape(v.name) ..
" \\[" .. core.formspec_escape(v.gameid) .. "\\]"
end
return retval
end
--------------------------------------------------------------------------------
function menu_handle_key_up_down(fields, textlist, settingname)
local oldidx, newidx = core.get_textlist_index(textlist), 1
if fields.key_up or fields.key_down then
if fields.key_up and oldidx and oldidx > 1 then
newidx = oldidx - 1
elseif fields.key_down and oldidx and
oldidx < menudata.worldlist:size() then
newidx = oldidx + 1
end
core.setting_set(settingname, menudata.worldlist:get_raw_index(newidx))
configure_selected_world_params(newidx)
return true
end
return false
end
--------------------------------------------------------------------------------
function asyncOnlineFavourites()
if not menudata.public_known then
menudata.public_known = {{
name = fgettext("Loading..."),
description = fgettext_ne("Try reenabling public serverlist and check your internet connection.")
}}
end
menudata.favorites = menudata.public_known
menudata.favorites_is_public = true
if not menudata.public_downloading then
menudata.public_downloading = true
else
return
end
core.handle_async(
function(param)
return core.get_favorites("online")
end,
nil,
function(result)
menudata.public_downloading = nil
local favs = order_favorite_list(result)
if favs[1] then
menudata.public_known = favs
menudata.favorites = menudata.public_known
menudata.favorites_is_public = true
end
core.event_handler("Refresh")
end
)
end
--------------------------------------------------------------------------------
function text2textlist(xpos, ypos, width, height, tl_name, textlen, text, transparency)
local textlines = core.splittext(text, textlen)
local retval = "textlist[" .. xpos .. "," .. ypos .. ";" .. width ..
"," .. height .. ";" .. tl_name .. ";"
for i = 1, #textlines do
textlines[i] = textlines[i]:gsub("\r", "")
retval = retval .. core.formspec_escape(textlines[i]) .. ","
end
retval = retval .. ";0;"
if transparency then retval = retval .. "true" end
retval = retval .. "]"
return retval
end
--------------------------------------------------------------------------------
function is_server_protocol_compat(server_proto_min, server_proto_max)
if (not server_proto_min) or (not server_proto_max) then
-- There is no info. Assume the best and act as if we would be compatible.
return true
end
return min_supp_proto <= server_proto_max and max_supp_proto >= server_proto_min
end
--------------------------------------------------------------------------------
function is_server_protocol_compat_or_error(server_proto_min, server_proto_max)
if not is_server_protocol_compat(server_proto_min, server_proto_max) then
local server_prot_ver_info, client_prot_ver_info
local s_p_min = server_proto_min
local s_p_max = server_proto_max
if s_p_min ~= s_p_max then
server_prot_ver_info = fgettext_ne("Server supports protocol versions between $1 and $2. ",
s_p_min, s_p_max)
else
server_prot_ver_info = fgettext_ne("Server enforces protocol version $1. ",
s_p_min)
end
if min_supp_proto ~= max_supp_proto then
client_prot_ver_info= fgettext_ne("We support protocol versions between version $1 and $2.",
min_supp_proto, max_supp_proto)
else
client_prot_ver_info = fgettext_ne("We only support protocol version $1.", min_supp_proto)
end
gamedata.errormessage = fgettext_ne("Protocol version mismatch. ")
.. server_prot_ver_info
.. client_prot_ver_info
return false
end
return true
end
--------------------------------------------------------------------------------
function menu_worldmt(selected, setting, value)
local world = menudata.worldlist:get_list()[selected]
if world then
local filename = world.path .. DIR_DELIM .. "world.mt"
local world_conf = Settings(filename)
if value then
if not world_conf:write() then
core.log("error", "Failed to write world config file")
end
world_conf:set(setting, value)
world_conf:write()
else
return world_conf:get(setting)
end
else
return nil
end
end
function menu_worldmt_legacy(selected)
local modes_names = {"creative_mode", "enable_damage", "server_announce"}
for _, mode_name in pairs(modes_names) do
local mode_val = menu_worldmt(selected, mode_name)
if mode_val then
core.setting_set(mode_name, mode_val)
else
menu_worldmt(selected, mode_name, core.setting_get(mode_name))
end
end
end

View File

@ -0,0 +1,335 @@
--Minetest
--Copyright (C) 2013 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--------------------------------------------------------------------------------
local enabled_all = false
local function modname_valid(name)
return not name:find("[^a-z0-9_]")
end
local function get_formspec(data)
local mod = data.list:get_list()[data.selected_mod]
local retval =
"size[11.5,7.5,true]" ..
"label[0.5,0;" .. fgettext("World:") .. "]" ..
"label[1.75,0;" .. data.worldspec.name .. "]"
if data.hide_gamemods then
retval = retval .. "checkbox[1,6;cb_hide_gamemods;" .. fgettext("Hide Game") .. ";true]"
else
retval = retval .. "checkbox[1,6;cb_hide_gamemods;" .. fgettext("Hide Game") .. ";false]"
end
if data.hide_modpackcontents then
retval = retval .. "checkbox[6,6;cb_hide_mpcontent;" .. fgettext("Hide mp content") .. ";true]"
else
retval = retval .. "checkbox[6,6;cb_hide_mpcontent;" .. fgettext("Hide mp content") .. ";false]"
end
if mod == nil then
mod = {name=""}
end
local hard_deps, soft_deps = modmgr.get_dependencies(mod.path)
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]" ..
"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") .. "]"
if mod ~= nil and mod.name ~= "" and mod.typ ~= "game_mod" then
if mod.is_modpack then
local rawlist = data.list:get_raw_list()
local all_enabled = true
for j=1,#rawlist,1 do
if rawlist[j].modpack == mod.name and
rawlist[j].enabled ~= true then
all_enabled = false
break
end
end
if all_enabled == false then
retval = retval .. "button[5.5,0.125;2.5,0.5;btn_mp_enable;" .. fgettext("Enable MP") .. "]"
else
retval = retval .. "button[5.5,0.125;2.5,0.5;btn_mp_disable;" .. fgettext("Disable MP") .. "]"
end
else
if mod.enabled then
retval = retval .. "checkbox[5.5,-0.125;cb_mod_enable;" .. fgettext("enabled") .. ";true]"
else
retval = retval .. "checkbox[5.5,-0.125;cb_mod_enable;" .. fgettext("enabled") .. ";false]"
end
end
end
if enabled_all then
retval = retval ..
"button[8.75,0.125;2.5,0.5;btn_disable_all_mods;" .. fgettext("Disable all") .. "]" ..
"textlist[5.5,0.75;5.75,5.4;world_config_modlist;"
else
retval = retval ..
"button[8.75,0.125;2.5,0.5;btn_enable_all_mods;" .. fgettext("Enable all") .. "]" ..
"textlist[5.5,0.75;5.75,5.4;world_config_modlist;"
end
retval = retval .. modmgr.render_modlist(data.list)
retval = retval .. ";" .. data.selected_mod .."]"
return retval
end
local function enable_mod(this, toset)
local mod = this.data.list:get_list()[this.data.selected_mod]
if mod.typ == "game_mod" then
-- game mods can't be enabled or disabled
elseif not mod.is_modpack then
if toset == nil then
mod.enabled = not mod.enabled
else
mod.enabled = toset
end
else
local list = this.data.list:get_raw_list()
for i=1,#list,1 do
if list[i].modpack == mod.name then
if toset == nil then
toset = not list[i].enabled
end
list[i].enabled = toset
end
end
end
end
local function handle_buttons(this, fields)
if fields["world_config_modlist"] ~= nil then
local event = core.explode_textlist_event(fields["world_config_modlist"])
this.data.selected_mod = event.index
core.setting_set("world_config_selected_mod", event.index)
if event.type == "DCL" then
enable_mod(this)
end
return true
end
if fields["key_enter"] ~= nil then
enable_mod(this)
return true
end
if fields["cb_mod_enable"] ~= nil then
local toset = core.is_yes(fields["cb_mod_enable"])
enable_mod(this,toset)
return true
end
if fields["btn_mp_enable"] ~= nil or
fields["btn_mp_disable"] then
local toset = (fields["btn_mp_enable"] ~= nil)
enable_mod(this,toset)
return true
end
if fields["cb_hide_gamemods"] ~= nil or
fields["cb_hide_mpcontent"] ~= nil then
local current = this.data.list:get_filtercriteria()
if current == nil then
current = {}
end
if fields["cb_hide_gamemods"] ~= nil then
if core.is_yes(fields["cb_hide_gamemods"]) then
current.hide_game = true
this.data.hide_gamemods = true
core.setting_set("world_config_hide_gamemods", "true")
else
current.hide_game = false
this.data.hide_gamemods = false
core.setting_set("world_config_hide_gamemods", "false")
end
end
if fields["cb_hide_mpcontent"] ~= nil then
if core.is_yes(fields["cb_hide_mpcontent"]) then
current.hide_modpackcontents = true
this.data.hide_modpackcontents = true
core.setting_set("world_config_hide_modpackcontents", "true")
else
current.hide_modpackcontents = false
this.data.hide_modpackcontents = false
core.setting_set("world_config_hide_modpackcontents", "false")
end
end
this.data.list:set_filtercriteria(current)
return true
end
if fields["btn_config_world_save"] then
local filename = this.data.worldspec.path ..
DIR_DELIM .. "world.mt"
local worldfile = Settings(filename)
local mods = worldfile:to_table()
local rawlist = this.data.list:get_raw_list()
local i,mod
for i,mod in ipairs(rawlist) do
if not mod.is_modpack and
mod.typ ~= "game_mod" then
if modname_valid(mod.name) then
worldfile:set("load_mod_"..mod.name, tostring(mod.enabled))
else
if mod.enabled then
gamedata.errormessage = fgettext_ne("Failed to enable mod \"$1\" as it contains disallowed characters. Only chararacters [a-z0-9_] are allowed.", mod.name)
end
end
mods["load_mod_"..mod.name] = nil
end
end
-- Remove mods that are not present anymore
for key,value in pairs(mods) do
if key:sub(1,9) == "load_mod_" then
worldfile:remove(key)
end
end
if not worldfile:write() then
core.log("error", "Failed to write world config file")
end
this:delete()
return true
end
if fields["btn_config_world_cancel"] then
this:delete()
return true
end
if fields.btn_enable_all_mods then
local list = this.data.list:get_raw_list()
for i = 1, #list do
if list[i].typ ~= "game_mod" and not list[i].is_modpack then
list[i].enabled = true
end
end
enabled_all = true
return true
end
if fields.btn_disable_all_mods then
local list = this.data.list:get_raw_list()
for i = 1, #list do
if list[i].typ ~= "game_mod" and not list[i].is_modpack then
list[i].enabled = false
end
end
enabled_all = false
return true
end
return false
end
function create_configure_world_dlg(worldidx)
local dlg = dialog_create("sp_config_world",
get_formspec,
handle_buttons,
nil)
dlg.data.hide_gamemods = core.setting_getbool("world_config_hide_gamemods")
dlg.data.hide_modpackcontents = core.setting_getbool("world_config_hide_modpackcontents")
dlg.data.selected_mod = tonumber(core.setting_get("world_config_selected_mod"))
if dlg.data.selected_mod == nil then
dlg.data.selected_mod = 0
end
dlg.data.worldspec = core.get_worlds()[worldidx]
if dlg.data.worldspec == nil then dlg:delete() return nil end
dlg.data.worldconfig = modmgr.get_worldconfig(dlg.data.worldspec.path)
if dlg.data.worldconfig == nil or dlg.data.worldconfig.id == nil or
dlg.data.worldconfig.id == "" then
dlg:delete()
return nil
end
dlg.data.list = filterlist.create(
modmgr.preparemodlist, --refresh
modmgr.comparemod, --compare
function(element,uid) --uid match
if element.name == uid then
return true
end
end,
function(element,criteria)
if criteria.hide_game and
element.typ == "game_mod" then
return false
end
if criteria.hide_modpackcontents and
element.modpack ~= nil then
return false
end
return true
end, --filter
{ 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

@ -0,0 +1,143 @@
--Minetest
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local function create_world_formspec(dialogdata)
local mapgens = core.get_mapgen_names()
local current_seed = core.setting_get("fixed_map_seed") or ""
local current_mg = core.setting_get("mg_name")
local mglist = ""
local selindex = 1
local i = 1
for k,v in pairs(mapgens) do
if current_mg == v then
selindex = i
end
i = i + 1
mglist = mglist .. v .. ","
end
mglist = mglist:sub(1, -2)
local gameid = core.setting_get("menu_last_game")
local game, gameidx = nil , 0
if gameid ~= nil then
game, gameidx = gamemgr.find_by_gameid(gameid)
if gameidx == nil then
gameidx = 0
end
end
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;;]" ..
"label[2,1;" .. fgettext("Seed") .. "]"..
"field[4.5,1.4;6,0.5;te_seed;;".. current_seed .. "]" ..
"label[2,2;" .. fgettext("Mapgen") .. "]"..
"dropdown[4.2,2;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]" ..
"label[2,3;" .. fgettext("Game") .. "]"..
"textlist[4.2,3;5.8,2.3;games;" .. gamemgr.gamelist() ..
";" .. gameidx .. ";true]" ..
"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 #gamemgr.games == 0 then
retval = retval .. "box[2,4;8,1;#ff8800]label[2.25,4;" ..
fgettext("You have no subgames installed.") .. "]label[2.25,4.4;" ..
fgettext("Download one from minetest.net") .. "]"
elseif #gamemgr.games == 1 and gamemgr.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 subgame, such as minetest_game, from minetest.net") .. "]"
end
return retval
end
local function create_world_buttonhandler(this, fields)
if fields["world_create_confirm"] or
fields["key_enter"] then
local worldname = fields["te_world_name"]
local gameindex = core.get_textlist_index("games")
if gameindex ~= nil and
worldname ~= "" then
local message = nil
core.setting_set("fixed_map_seed", fields["te_seed"])
if not menudata.worldlist:uid_exists_raw(worldname) then
core.setting_set("mg_name",fields["dd_mapgen"])
message = core.create_world(worldname,gameindex)
else
message = fgettext("A world named \"$1\" already exists", worldname)
end
if message ~= nil then
gamedata.errormessage = message
else
core.setting_set("menu_last_game",gamemgr.games[gameindex].id)
if this.data.update_worldlist_filter then
menudata.worldlist:set_filtercriteria(gamemgr.games[gameindex].id)
mm_texture.update("singleplayer", gamemgr.games[gameindex].id)
end
menudata.worldlist:refresh()
core.setting_set("mainmenu_last_selected_world",
menudata.worldlist:raw_index_by_uid(worldname))
end
else
gamedata.errormessage =
fgettext("No worldname given or no game selected")
end
this:delete()
return true
end
if fields["games"] then
return true
end
if fields["world_create_cancel"] then
this:delete()
return true
end
return false
end
function create_create_world_dlg(update_worldlistfilter)
local retval = dialog_create("sp_create_world",
create_world_formspec,
create_world_buttonhandler,
nil)
retval.update_worldlist_filter = update_worldlistfilter
return retval
end

View File

@ -0,0 +1,69 @@
--Minetest
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--------------------------------------------------------------------------------
local function delete_mod_formspec(dialogdata)
dialogdata.mod = modmgr.global_mods:get_list()[dialogdata.selected]
local retval =
"size[11.5,4.5,true]" ..
"label[2,2;" ..
fgettext("Are you sure you want to delete \"$1\"?", dialogdata.mod.name) .. "]"..
"button[3.25,3.5;2.5,0.5;dlg_delete_mod_confirm;" .. fgettext("Delete") .. "]" ..
"button[5.75,3.5;2.5,0.5;dlg_delete_mod_cancel;" .. fgettext("Cancel") .. "]"
return retval
end
--------------------------------------------------------------------------------
local function delete_mod_buttonhandler(this, fields)
if fields["dlg_delete_mod_confirm"] ~= nil then
if this.data.mod.path ~= nil and
this.data.mod.path ~= "" and
this.data.mod.path ~= core.get_modpath() then
if not core.delete_dir(this.data.mod.path) then
gamedata.errormessage = fgettext("Modmgr: failed to delete \"$1\"", this.data.mod.path)
end
modmgr.refresh_globals()
else
gamedata.errormessage = fgettext("Modmgr: invalid modpath \"$1\"", this.data.mod.path)
end
this:delete()
return true
end
if fields["dlg_delete_mod_cancel"] then
this:delete()
return true
end
return false
end
--------------------------------------------------------------------------------
function create_delete_mod_dlg(selected_index)
local retval = dialog_create("dlg_delete_mod",
delete_mod_formspec,
delete_mod_buttonhandler,
nil)
retval.data.selected = selected_index
return retval
end

View File

@ -0,0 +1,64 @@
--Minetest
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local function delete_world_formspec(dialogdata)
local retval =
"size[11.5,4.5,true]" ..
"label[2,2;" ..
fgettext("Delete World \"$1\"?", dialogdata.delete_name) .. "]" ..
"button[3.25,3.5;2.5,0.5;world_delete_confirm;" .. fgettext("Delete") .. "]" ..
"button[5.75,3.5;2.5,0.5;world_delete_cancel;" .. fgettext("Cancel") .. "]"
return retval
end
local function delete_world_buttonhandler(this, fields)
if fields["world_delete_confirm"] then
if this.data.delete_index > 0 and
this.data.delete_index <= #menudata.worldlist:get_raw_list() then
core.delete_world(this.data.delete_index)
menudata.worldlist:refresh()
end
this:delete()
return true
end
if fields["world_delete_cancel"] then
this:delete()
return true
end
return false
end
function create_delete_world_dlg(name_to_del,index_to_del)
assert(name_to_del ~= nil and type(name_to_del) == "string" and name_to_del ~= "")
assert(index_to_del ~= nil and type(index_to_del) == "number")
local retval = dialog_create("delete_world",
delete_world_formspec,
delete_world_buttonhandler,
nil)
retval.data.delete_name = name_to_del
retval.data.delete_index = index_to_del
return retval
end

View File

@ -0,0 +1,67 @@
--Minetest
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--------------------------------------------------------------------------------
local function rename_modpack_formspec(dialogdata)
dialogdata.mod = modmgr.global_mods:get_list()[dialogdata.selected]
local retval =
"size[11.5,4.5,true]" ..
"field[2.5,2;7,0.5;te_modpack_name;".. fgettext("Rename Modpack:") .. ";" ..
dialogdata.mod.name .. "]" ..
"button[3.25,3.5;2.5,0.5;dlg_rename_modpack_confirm;"..
fgettext("Accept") .. "]" ..
"button[5.75,3.5;2.5,0.5;dlg_rename_modpack_cancel;"..
fgettext("Cancel") .. "]"
return retval
end
--------------------------------------------------------------------------------
local function rename_modpack_buttonhandler(this, fields)
if fields["dlg_rename_modpack_confirm"] ~= nil then
local oldpath = core.get_modpath() .. DIR_DELIM .. this.data.mod.name
local targetpath = core.get_modpath() .. DIR_DELIM .. fields["te_modpack_name"]
core.copy_dir(oldpath,targetpath,false)
modmgr.refresh_globals()
modmgr.selected_mod = modmgr.global_mods:get_current_index(
modmgr.global_mods:raw_index_by_uid(fields["te_modpack_name"]))
this:delete()
return true
end
if fields["dlg_rename_modpack_cancel"] then
this:delete()
return true
end
return false
end
--------------------------------------------------------------------------------
function create_rename_modpack_dlg(selected_index)
local retval = dialog_create("dlg_delete_mod",
rename_modpack_formspec,
rename_modpack_buttonhandler,
nil)
retval.data.selected = selected_index
return retval
end

View File

@ -0,0 +1,778 @@
--Minetest
--Copyright (C) 2015 PilzAdam
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local FILENAME = "settingtypes.txt"
local CHAR_CLASSES = {
SPACE = "[%s]",
VARIABLE = "[%w_%-%.]",
INTEGER = "[+-]?[%d]",
FLOAT = "[+-]?[%d%.]",
FLAGS = "[%w_%-%.,]",
}
-- returns error message, or nil
local function parse_setting_line(settings, line, read_all, base_level, allow_secure)
-- comment
local comment = line:match("^#" .. CHAR_CLASSES.SPACE .. "*(.*)$")
if comment then
if settings.current_comment == "" then
settings.current_comment = comment
else
settings.current_comment = settings.current_comment .. "\n" .. comment
end
return
end
-- clear current_comment so only comments directly above a setting are bound to it
-- but keep a local reference to it for variables in the current line
local current_comment = settings.current_comment
settings.current_comment = ""
-- empty lines
if line:match("^" .. CHAR_CLASSES.SPACE .. "*$") then
return
end
-- category
local stars, category = line:match("^%[([%*]*)([^%]]+)%]$")
if category then
table.insert(settings, {
name = category,
level = stars:len() + base_level,
type = "category",
})
return
end
-- settings
local first_part, name, readable_name, setting_type = line:match("^"
-- this first capture group matches the whole first part,
-- so we can later strip it from the rest of the line
.. "("
.. "([" .. CHAR_CLASSES.VARIABLE .. "+)" -- variable name
.. CHAR_CLASSES.SPACE .. "*"
.. "%(([^%)]*)%)" -- readable name
.. CHAR_CLASSES.SPACE .. "*"
.. "(" .. CHAR_CLASSES.VARIABLE .. "+)" -- type
.. CHAR_CLASSES.SPACE .. "*"
.. ")")
if not first_part then
return "Invalid line"
end
if name:match("secure%.[.]*") and not allow_secure then
return "Tried to add \"secure.\" setting"
end
if readable_name == "" then
readable_name = nil
end
local remaining_line = line:sub(first_part:len() + 1)
if setting_type == "int" then
local default, min, max = remaining_line:match("^"
-- first int is required, the last 2 are optional
.. "(" .. CHAR_CLASSES.INTEGER .. "+)" .. CHAR_CLASSES.SPACE .. "*"
.. "(" .. CHAR_CLASSES.INTEGER .. "*)" .. CHAR_CLASSES.SPACE .. "*"
.. "(" .. CHAR_CLASSES.INTEGER .. "*)"
.. "$")
if not default or not tonumber(default) then
return "Invalid integer setting"
end
min = tonumber(min)
max = tonumber(max)
table.insert(settings, {
name = name,
readable_name = readable_name,
type = "int",
default = default,
min = min,
max = max,
comment = current_comment,
})
return
end
if setting_type == "string" or setting_type == "noise_params"
or setting_type == "key" or setting_type == "v3f" then
local default = remaining_line:match("^(.*)$")
if not default then
return "Invalid string setting"
end
if setting_type == "key" and not read_all then
-- ignore key type if read_all is false
return
end
table.insert(settings, {
name = name,
readable_name = readable_name,
type = setting_type,
default = default,
comment = current_comment,
})
return
end
if setting_type == "bool" then
if remaining_line ~= "false" and remaining_line ~= "true" then
return "Invalid boolean setting"
end
table.insert(settings, {
name = name,
readable_name = readable_name,
type = "bool",
default = remaining_line,
comment = current_comment,
})
return
end
if setting_type == "float" then
local default, min, max = remaining_line:match("^"
-- first float is required, the last 2 are optional
.. "(" .. CHAR_CLASSES.FLOAT .. "+)" .. CHAR_CLASSES.SPACE .. "*"
.. "(" .. CHAR_CLASSES.FLOAT .. "*)" .. CHAR_CLASSES.SPACE .. "*"
.. "(" .. CHAR_CLASSES.FLOAT .. "*)"
.."$")
if not default or not tonumber(default) then
return "Invalid float setting"
end
min = tonumber(min)
max = tonumber(max)
table.insert(settings, {
name = name,
readable_name = readable_name,
type = "float",
default = default,
min = min,
max = max,
comment = current_comment,
})
return
end
if setting_type == "enum" then
local default, values = remaining_line:match("^"
-- first value (default) may be empty (i.e. is optional)
.. "(" .. CHAR_CLASSES.VARIABLE .. "*)" .. CHAR_CLASSES.SPACE .. "*"
.. "(" .. CHAR_CLASSES.FLAGS .. "+)"
.. "$")
if not default or values == "" then
return "Invalid enum setting"
end
table.insert(settings, {
name = name,
readable_name = readable_name,
type = "enum",
default = default,
values = values:split(",", true),
comment = current_comment,
})
return
end
if setting_type == "path" then
local default = remaining_line:match("^(.*)$")
if not default then
return "Invalid path setting"
end
table.insert(settings, {
name = name,
readable_name = readable_name,
type = "path",
default = default,
comment = current_comment,
})
return
end
if setting_type == "flags" then
local default, possible = remaining_line:match("^"
-- first value (default) may be empty (i.e. is optional)
-- this is implemented by making the last value optional, and
-- swapping them around if it turns out empty.
.. "(" .. CHAR_CLASSES.FLAGS .. "+)" .. CHAR_CLASSES.SPACE .. "*"
.. "(" .. CHAR_CLASSES.FLAGS .. "*)"
.. "$")
if not default or not possible then
return "Invalid flags setting"
end
if possible == "" then
possible = default
default = ""
end
table.insert(settings, {
name = name,
readable_name = readable_name,
type = "flags",
default = default,
possible = possible,
comment = current_comment,
})
return
end
return "Invalid setting type \"" .. setting_type .. "\""
end
local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure)
-- store this helper variable in the table so it's easier to pass to parse_setting_line()
result.current_comment = ""
local line = file:read("*line")
while line do
local error_msg = parse_setting_line(result, line, read_all, base_level, allow_secure)
if error_msg then
core.log("error", error_msg .. " in " .. filepath .. " \"" .. line .. "\"")
end
line = file:read("*line")
end
result.current_comment = nil
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() .. DIR_DELIM .. FILENAME
local file = io.open(builtin_path, "r")
local settings = {}
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()
if parse_mods then
-- Parse games
local games_category_initialized = false
local index = 1
local game = gamemgr.get_game(index)
while game do
local path = game.path .. DIR_DELIM .. FILENAME
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
table.insert(settings, {
name = "Games",
level = 0,
type = "category",
})
games_category_initialized = true
end
table.insert(settings, {
name = game.name,
level = 1,
type = "category",
})
parse_single_file(file, path, read_all, settings, 2, false)
file:close()
end
index = index + 1
game = gamemgr.get_game(index)
end
-- Parse mods
local mods_category_initialized = false
local mods = {}
get_mods(core.get_modpath(), mods)
for _, mod in ipairs(mods) do
local path = mod.path .. DIR_DELIM .. FILENAME
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
table.insert(settings, {
name = "Mods",
level = 0,
type = "category",
})
mods_category_initialized = true
end
table.insert(settings, {
name = mod.name,
level = 1,
type = "category",
})
parse_single_file(file, path, read_all, settings, 2, false)
file:close()
end
end
end
return settings
end
local function filter_settings(settings, searchstring)
if not searchstring or searchstring == "" then
return settings, -1
end
-- Setup the keyword list
local keywords = {}
for word in searchstring:lower():gmatch("%S+") do
table.insert(keywords, word)
end
local result = {}
local category_stack = {}
local current_level = 0
local best_setting = nil
for _, entry in pairs(settings) do
if entry.type == "category" then
-- Remove all settingless categories
while #category_stack > 0 and entry.level <= current_level do
table.remove(category_stack, #category_stack)
if #category_stack > 0 then
current_level = category_stack[#category_stack].level
else
current_level = 0
end
end
-- Push category onto stack
category_stack[#category_stack + 1] = entry
current_level = entry.level
else
-- See if setting matches keywords
local setting_score = 0
for k = 1, #keywords do
local keyword = keywords[k]
if string.find(entry.name:lower(), keyword, 1, true) then
setting_score = setting_score + 1
end
if entry.readable_name and
string.find(fgettext(entry.readable_name):lower(), keyword, 1, true) then
setting_score = setting_score + 1
end
if entry.comment and
string.find(fgettext_ne(entry.comment):lower(), keyword, 1, true) then
setting_score = setting_score + 1
end
end
-- Add setting to results if match
if setting_score > 0 then
-- Add parent categories
for _, category in pairs(category_stack) do
result[#result + 1] = category
end
category_stack = {}
-- Add setting
result[#result + 1] = entry
entry.score = setting_score
if not best_setting or
setting_score > result[best_setting].score then
best_setting = #result
end
end
end
end
return result, best_setting or -1
end
local full_settings = parse_config_file(false, true)
local search_string = ""
local settings = full_settings
local selected_setting = 1
local function get_current_value(setting)
local value = core.setting_get(setting.name)
if value == nil then
value = setting.default
end
return value
end
local function create_change_setting_formspec(dialogdata)
local setting = settings[selected_setting]
local formspec = "size[10,5.2,true]" ..
"button[5,4.5;2,1;btn_done;" .. fgettext("Save") .. "]" ..
"button[3,4.5;2,1;btn_cancel;" .. fgettext("Cancel") .. "]" ..
"tablecolumns[color;text]" ..
"tableoptions[background=#00000000;highlight=#00000000;border=false]" ..
"table[0,0;10,3;info;"
if setting.readable_name then
formspec = formspec .. "#FFFF00," .. fgettext(setting.readable_name)
.. " (" .. core.formspec_escape(setting.name) .. "),"
else
formspec = formspec .. "#FFFF00," .. core.formspec_escape(setting.name) .. ","
end
formspec = formspec .. ",,"
local comment_text = ""
if setting.comment == "" then
comment_text = fgettext_ne("(No description of setting given)")
else
comment_text = fgettext_ne(setting.comment)
end
for _, comment_line in ipairs(comment_text:split("\n", true)) do
formspec = formspec .. "," .. core.formspec_escape(comment_line) .. ","
end
if setting.type == "flags" then
formspec = formspec .. ",,"
.. "," .. fgettext("Please enter a comma seperated list of flags.") .. ","
.. "," .. fgettext("Possible values are: ")
.. core.formspec_escape(setting.possible:gsub(",", ", ")) .. ","
elseif setting.type == "noise_params" then
formspec = formspec .. ",,"
.. "," .. fgettext("Format: <offset>, <scale>, (<spreadX>, <spreadY>, <spreadZ>), <seed>, <octaves>, <persistence>") .. ","
.. "," .. fgettext("Optionally the lacunarity can be appended with a leading comma.") .. ","
elseif setting.type == "v3f" then
formspec = formspec .. ",,"
.. "," .. fgettext_ne("Format is 3 numbers separated by commas and inside brackets.") .. ","
end
formspec = formspec:sub(1, -2) -- remove trailing comma
formspec = formspec .. ";1]"
if setting.type == "bool" then
local selected_index
if core.is_yes(get_current_value(setting)) then
selected_index = 2
else
selected_index = 1
end
formspec = formspec .. "dropdown[0.5,3.5;3,1;dd_setting_value;"
.. fgettext("Disabled") .. "," .. fgettext("Enabled") .. ";"
.. selected_index .. "]"
elseif setting.type == "enum" then
local selected_index = 0
formspec = formspec .. "dropdown[0.5,3.5;3,1;dd_setting_value;"
for index, value in ipairs(setting.values) do
-- translating value is not possible, since it's the value
-- that we set the setting to
formspec = formspec .. core.formspec_escape(value) .. ","
if get_current_value(setting) == value then
selected_index = index
end
end
if #setting.values > 0 then
formspec = formspec:sub(1, -2) -- remove trailing comma
end
formspec = formspec .. ";" .. selected_index .. "]"
elseif setting.type == "path" then
local current_value = dialogdata.selected_path
if not current_value then
current_value = get_current_value(setting)
end
formspec = formspec .. "field[0.5,4;7.5,1;te_setting_value;;"
.. core.formspec_escape(current_value) .. "]"
.. "button[8,3.75;2,1;btn_browser_path;" .. fgettext("Browse") .. "]"
else
-- TODO: fancy input for float, int, flags, noise_params, v3f
local width = 10
local text = get_current_value(setting)
if dialogdata.error_message then
formspec = formspec .. "tablecolumns[color;text]" ..
"tableoptions[background=#00000000;highlight=#00000000;border=false]" ..
"table[5,3.9;5,0.6;error_message;#FF0000,"
.. core.formspec_escape(dialogdata.error_message) .. ";0]"
width = 5
if dialogdata.entered_text then
text = dialogdata.entered_text
end
end
formspec = formspec .. "field[0.5,4;" .. width .. ",1;te_setting_value;;"
.. core.formspec_escape(text) .. "]"
end
return formspec
end
local function handle_change_setting_buttons(this, fields)
if fields["btn_done"] or fields["key_enter"] then
local setting = settings[selected_setting]
if setting.type == "bool" then
local new_value = fields["dd_setting_value"]
-- Note: new_value is the actual (translated) value shown in the dropdown
core.setting_setbool(setting.name, new_value == fgettext("Enabled"))
elseif setting.type == "enum" then
local new_value = fields["dd_setting_value"]
core.setting_set(setting.name, new_value)
elseif setting.type == "int" then
local new_value = tonumber(fields["te_setting_value"])
if not new_value or math.floor(new_value) ~= new_value then
this.data.error_message = fgettext_ne("Please enter a valid integer.")
this.data.entered_text = fields["te_setting_value"]
core.update_formspec(this:get_formspec())
return true
end
if setting.min and new_value < setting.min then
this.data.error_message = fgettext_ne("The value must be at least $1.", setting.min)
this.data.entered_text = fields["te_setting_value"]
core.update_formspec(this:get_formspec())
return true
end
if setting.max and new_value > setting.max then
this.data.error_message = fgettext_ne("The value must not be larger than $1.", setting.max)
this.data.entered_text = fields["te_setting_value"]
core.update_formspec(this:get_formspec())
return true
end
core.setting_set(setting.name, new_value)
elseif setting.type == "float" then
local new_value = tonumber(fields["te_setting_value"])
if not new_value then
this.data.error_message = fgettext_ne("Please enter a valid number.")
this.data.entered_text = fields["te_setting_value"]
core.update_formspec(this:get_formspec())
return true
end
core.setting_set(setting.name, new_value)
elseif setting.type == "flags" then
local new_value = fields["te_setting_value"]
for _,value in ipairs(new_value:split(",", true)) do
value = value:trim()
local possible = "," .. setting.possible .. ","
if not possible:find("," .. value .. ",", 0, true) then
this.data.error_message = fgettext_ne("\"$1\" is not a valid flag.", value)
this.data.entered_text = fields["te_setting_value"]
core.update_formspec(this:get_formspec())
return true
end
end
core.setting_set(setting.name, new_value)
else
local new_value = fields["te_setting_value"]
core.setting_set(setting.name, new_value)
end
core.setting_save()
this:delete()
return true
end
if fields["btn_cancel"] then
this:delete()
return true
end
if fields["btn_browser_path"] then
core.show_file_open_dialog("dlg_browse_path", fgettext_ne("Select path"))
end
if fields["dlg_browse_path_accepted"] then
this.data.selected_path = fields["dlg_browse_path_accepted"]
core.update_formspec(this:get_formspec())
end
return false
end
local function create_settings_formspec(tabview, name, tabdata)
local formspec = "size[12,6.5;true]" ..
"tablecolumns[color;tree;text,width=32;text]" ..
"tableoptions[background=#00000000;border=false]" ..
"field[0.3,0.1;10.2,1;search_string;;" .. core.formspec_escape(search_string) .. "]" ..
"field_close_on_enter[search_string;false]" ..
"button[10.2,-0.2;2,1;search;" .. fgettext("Search") .. "]" ..
"table[0,0.8;12,4.5;list_settings;"
local current_level = 0
for _, entry in ipairs(settings) do
local name
if not core.setting_getbool("main_menu_technical_settings") and entry.readable_name then
name = fgettext_ne(entry.readable_name)
else
name = entry.name
end
if entry.type == "category" then
current_level = entry.level
formspec = formspec .. "#FFFF00," .. current_level .. "," .. fgettext(name) .. ",,"
elseif entry.type == "bool" then
local value = get_current_value(entry)
if core.is_yes(value) then
value = fgettext("Enabled")
else
value = fgettext("Disabled")
end
formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
.. value .. ","
elseif entry.type == "key" then
-- ignore key settings, since we have a special dialog for them
else
formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
.. core.formspec_escape(get_current_value(entry)) .. ","
end
end
if #settings > 0 then
formspec = formspec:sub(1, -2) -- remove trailing comma
end
formspec = formspec .. ";" .. selected_setting .. "]" ..
"button[0,6;4,1;btn_back;".. fgettext("< Back to Settings page") .. "]" ..
"button[10,6;2,1;btn_edit;" .. fgettext("Edit") .. "]" ..
"button[7,6;3,1;btn_restore;" .. fgettext("Restore Default") .. "]" ..
"checkbox[0,5.3;cb_tech_settings;" .. fgettext("Show technical names") .. ";"
.. dump(core.setting_getbool("main_menu_technical_settings")) .. "]"
return formspec
end
local function handle_settings_buttons(this, fields, tabname, tabdata)
local list_enter = false
if fields["list_settings"] then
selected_setting = core.get_table_index("list_settings")
if core.explode_table_event(fields["list_settings"]).type == "DCL" then
-- Directly toggle booleans
local setting = settings[selected_setting]
if setting and setting.type == "bool" then
local current_value = get_current_value(setting)
core.setting_setbool(setting.name, not core.is_yes(current_value))
core.setting_save()
return true
else
list_enter = true
end
else
return true
end
end
if fields.search or fields.key_enter_field == "search_string" then
if search_string == fields.search_string then
if selected_setting > 0 then
-- Go to next result on enter press
local i = selected_setting + 1
local looped = false
while i > #settings or settings[i].type == "category" do
i = i + 1
if i > #settings then
-- Stop infinte looping
if looped then
return false
end
i = 1
looped = true
end
end
selected_setting = i
core.update_formspec(this:get_formspec())
return true
end
else
-- Search for setting
search_string = fields.search_string
settings, selected_setting = filter_settings(full_settings, search_string)
core.update_formspec(this:get_formspec())
end
return true
end
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)
edit_dialog:set_parent(this)
this:hide()
edit_dialog:show()
end
return true
end
if fields["btn_restore"] then
local setting = settings[selected_setting]
if setting and setting.type ~= "category" then
core.setting_set(setting.name, setting.default)
core.setting_save()
core.update_formspec(this:get_formspec())
end
return true
end
if fields["btn_back"] then
this:delete()
return true
end
if fields["cb_tech_settings"] then
core.setting_set("main_menu_technical_settings", fields["cb_tech_settings"])
core.setting_save()
core.update_formspec(this:get_formspec())
return true
end
return false
end
function create_adv_settings_dlg()
local dlg = dialog_create("settings_advanced",
create_settings_formspec,
handle_settings_buttons,
nil)
return dlg
end
-- Generate minetest.conf.example and settings_translation_file.cpp
-- *** Please note ***
-- There is text in minetest.conf.example that will not be generated from
-- settingtypes.txt but must be preserved:
-- The documentation of mapgen noise parameter formats (title plus 16 lines)
-- Noise parameter 'mgv5_np_ground' in group format (13 lines)
--assert(loadfile(core.get_builtin_path()..DIR_DELIM.."mainmenu"..DIR_DELIM.."generate_from_settingtypes.lua"))(parse_config_file(true, false))

View File

@ -0,0 +1,83 @@
--Minetest
--Copyright (C) 2013 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
gamemgr = {}
--------------------------------------------------------------------------------
function gamemgr.find_by_gameid(gameid)
for i=1,#gamemgr.games,1 do
if gamemgr.games[i].id == gameid then
return gamemgr.games[i], i
end
end
return nil, nil
end
--------------------------------------------------------------------------------
function gamemgr.get_game_mods(gamespec, retval)
if gamespec ~= nil and
gamespec.gamemods_path ~= nil and
gamespec.gamemods_path ~= "" then
get_mods(gamespec.gamemods_path, retval)
end
end
--------------------------------------------------------------------------------
function gamemgr.get_game_modlist(gamespec)
local retval = ""
local game_mods = {}
gamemgr.get_game_mods(gamespec, game_mods)
for i=1,#game_mods,1 do
if retval ~= "" then
retval = retval..","
end
retval = retval .. game_mods[i].name
end
return retval
end
--------------------------------------------------------------------------------
function gamemgr.get_game(index)
if index > 0 and index <= #gamemgr.games then
return gamemgr.games[index]
end
return nil
end
--------------------------------------------------------------------------------
function gamemgr.update_gamelist()
gamemgr.games = core.get_games()
end
--------------------------------------------------------------------------------
function gamemgr.gamelist()
local retval = ""
if #gamemgr.games > 0 then
retval = retval .. gamemgr.games[1].name
for i=2,#gamemgr.games,1 do
retval = retval .. "," .. gamemgr.games[i].name
end
end
return retval
end
--------------------------------------------------------------------------------
-- read initial data
--------------------------------------------------------------------------------
gamemgr.update_gamelist()

View File

@ -0,0 +1,99 @@
local settings = ...
local concat = table.concat
local insert = table.insert
local sprintf = string.format
local rep = string.rep
local minetest_example_header = [[
# This file contains a list of all available settings and their default value for minetest.conf
# By default, all the settings are commented and not functional.
# Uncomment settings by removing the preceding #.
# minetest.conf is read by default from:
# ../minetest.conf
# ../../minetest.conf
# Any other path can be chosen by passing the path as a parameter
# to the program, eg. "minetest.exe --config ../minetest.conf.example".
# Further documentation:
# http://wiki.minetest.net/
]]
local function create_minetest_conf_example()
local result = { minetest_example_header }
for _, entry in ipairs(settings) do
if entry.type == "category" then
if entry.level == 0 then
insert(result, "#\n# " .. entry.name .. "\n#\n\n")
else
insert(result, rep("#", entry.level))
insert(result, "# " .. entry.name .. "\n\n")
end
else
if entry.comment ~= "" then
for _, comment_line in ipairs(entry.comment:split("\n", true)) do
insert(result, "# " .. comment_line .. "\n")
end
end
insert(result, "# type: " .. entry.type)
if entry.min then
insert(result, " min: " .. entry.min)
end
if entry.max then
insert(result, " max: " .. entry.max)
end
if entry.values then
insert(result, " values: " .. concat(entry.values, ", "))
end
if entry.possible then
insert(result, " possible values: " .. entry.possible:gsub(",", ", "))
end
insert(result, "\n")
local append
if entry.default ~= "" then
append = " " .. entry.default
end
insert(result, sprintf("# %s =%s\n\n", entry.name, append or ""))
end
end
return concat(result)
end
local translation_file_header = [[
// This file is automatically generated
// It conatins a bunch of fake gettext calls, to tell xgettext about the strings in config files
// To update it, refer to the bottom of builtin/mainmenu/dlg_settings_advanced.lua
fake_function() {]]
local function create_translation_file()
local result = { translation_file_header }
for _, entry in ipairs(settings) do
if entry.type == "category" then
insert(result, sprintf("\tgettext(%q);", entry.name))
else
if entry.readable_name then
insert(result, sprintf("\tgettext(%q);", entry.readable_name))
end
if entry.comment ~= "" then
local comment_escaped = entry.comment:gsub("\n", "\\n")
comment_escaped = comment_escaped:gsub("\"", "\\\"")
insert(result, "\tgettext(\"" .. comment_escaped .. "\");")
end
end
end
insert(result, "}\n")
return concat(result, "\n")
end
local file = assert(io.open("minetest.conf.example", "w"))
file:write(create_minetest_conf_example())
file:close()
file = assert(io.open("src/settings_translation_file.cpp", "w"))
file:write(create_translation_file())
file:close()

View File

@ -0,0 +1,170 @@
--Minetest
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
mt_color_grey = "#AAAAAA"
mt_color_blue = "#0000DD"
mt_color_green = "#00DD00"
mt_color_dark_green = "#003300"
--for all other colors ask sfan5 to complete his work!
local menupath = core.get_mainmenu_path()
local basepath = core.get_builtin_path()
defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" ..
DIR_DELIM .. "pack" .. DIR_DELIM
dofile(basepath .. DIR_DELIM .. "common" .. DIR_DELIM .. "async_event.lua")
dofile(basepath .. DIR_DELIM .. "common" .. DIR_DELIM .. "filterlist.lua")
dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "buttonbar.lua")
dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "dialog.lua")
dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "tabview.lua")
dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "ui.lua")
dofile(menupath .. DIR_DELIM .. "common.lua")
dofile(menupath .. DIR_DELIM .. "gamemgr.lua")
dofile(menupath .. DIR_DELIM .. "modmgr.lua")
dofile(menupath .. DIR_DELIM .. "store.lua")
dofile(menupath .. DIR_DELIM .. "textures.lua")
dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua")
dofile(menupath .. DIR_DELIM .. "dlg_settings_advanced.lua")
if PLATFORM ~= "Android" then
dofile(menupath .. DIR_DELIM .. "dlg_create_world.lua")
dofile(menupath .. DIR_DELIM .. "dlg_delete_mod.lua")
dofile(menupath .. DIR_DELIM .. "dlg_delete_world.lua")
dofile(menupath .. DIR_DELIM .. "dlg_rename_modpack.lua")
end
local tabs = {}
tabs.settings = dofile(menupath .. DIR_DELIM .. "tab_settings.lua")
tabs.mods = dofile(menupath .. DIR_DELIM .. "tab_mods.lua")
tabs.credits = dofile(menupath .. DIR_DELIM .. "tab_credits.lua")
if PLATFORM == "Android" then
tabs.simple_main = dofile(menupath .. DIR_DELIM .. "tab_simple_main.lua")
else
tabs.singleplayer = dofile(menupath .. DIR_DELIM .. "tab_singleplayer.lua")
tabs.multiplayer = dofile(menupath .. DIR_DELIM .. "tab_multiplayer.lua")
tabs.server = dofile(menupath .. DIR_DELIM .. "tab_server.lua")
tabs.texturepacks = dofile(menupath .. DIR_DELIM .. "tab_texturepacks.lua")
end
--------------------------------------------------------------------------------
local function main_event_handler(tabview, event)
if event == "MenuQuit" then
core.close()
end
return true
end
--------------------------------------------------------------------------------
local function init_globals()
-- Init gamedata
gamedata.worldindex = 0
if PLATFORM == "Android" then
local world_list = core.get_worlds()
local world_index
local found_singleplayerworld = false
for i, world in ipairs(world_list) do
if world.name == "singleplayerworld" then
found_singleplayerworld = true
world_index = i
break
end
end
if not found_singleplayerworld then
core.create_world("singleplayerworld", 1)
world_list = core.get_worlds()
for i, world in ipairs(world_list) do
if world.name == "singleplayerworld" then
world_index = i
break
end
end
end
gamedata.worldindex = world_index
else
menudata.worldlist = filterlist.create(
core.get_worlds,
compare_worlds,
-- Unique id comparison function
function(element, uid)
return element.name == uid
end,
-- Filter function
function(element, gameid)
return element.gameid == gameid
end
)
menudata.worldlist:add_sort_mechanism("alphabetic", sort_worlds_alphabetic)
menudata.worldlist:set_sortmode("alphabetic")
if not core.setting_get("menu_last_game") then
local default_game = core.setting_get("default_game") or "minetest"
core.setting_set("menu_last_game", default_game)
end
mm_texture.init()
end
-- Create main tabview
local tv_main = tabview_create("maintab", {x = 12, y = 5.4}, {x = 0, y = 0})
if PLATFORM == "Android" then
tv_main:add(tabs.simple_main)
tv_main:add(tabs.settings)
else
tv_main:set_autosave_tab(true)
tv_main:add(tabs.singleplayer)
tv_main:add(tabs.multiplayer)
tv_main:add(tabs.server)
tv_main:add(tabs.settings)
tv_main:add(tabs.texturepacks)
end
tv_main:add(tabs.mods)
tv_main:add(tabs.credits)
tv_main:set_global_event_handler(main_event_handler)
tv_main:set_fixed_size(false)
if PLATFORM ~= "Android" then
tv_main:set_tab(core.setting_get("maintab_LAST"))
end
ui.set_default("maintab")
tv_main:show()
-- Create modstore ui
if PLATFORM == "Android" then
modstore.init({x = 12, y = 6}, 3, 2)
else
modstore.init({x = 12, y = 8}, 4, 3)
end
ui.update()
core.sound_play("main_menu", true)
end
init_globals()

View File

@ -0,0 +1,4 @@
-- helper file to be able to debug the simple menu on PC
-- without messing around with actual menu code!
PLATFORM = "Android"
dofile("builtin/mainmenu/init.lua")

View File

@ -0,0 +1,572 @@
--Minetest
--Copyright (C) 2013 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--------------------------------------------------------------------------------
function get_mods(path,retval,modpack)
local mods = core.get_dir_list(path, true)
for _, name in ipairs(mods) do
if name:sub(1, 1) ~= "." then
local prefix = path .. DIR_DELIM .. name .. DIR_DELIM
local toadd = {}
retval[#retval + 1] = toadd
local mod_conf = Settings(prefix .. "mod.conf"):to_table()
if mod_conf.name then
name = mod_conf.name
end
toadd.name = name
toadd.path = prefix
if modpack ~= nil and modpack ~= "" then
toadd.modpack = modpack
else
local modpackfile = io.open(prefix .. "modpack.txt")
if modpackfile then
modpackfile:close()
toadd.is_modpack = true
get_mods(prefix, retval, name)
end
end
end
end
end
--modmanager implementation
modmgr = {}
--------------------------------------------------------------------------------
function modmgr.extract(modfile)
if modfile.type == "zip" then
local tempfolder = os.tempfolder()
if tempfolder ~= nil and
tempfolder ~= "" then
core.create_dir(tempfolder)
if core.extract_zip(modfile.name,tempfolder) then
return tempfolder
end
end
end
return nil
end
-------------------------------------------------------------------------------
function modmgr.getbasefolder(temppath)
if temppath == nil then
return {
type = "invalid",
path = ""
}
end
local testfile = io.open(temppath .. DIR_DELIM .. "init.lua","r")
if testfile ~= nil then
testfile:close()
return {
type="mod",
path=temppath
}
end
testfile = io.open(temppath .. DIR_DELIM .. "modpack.txt","r")
if testfile ~= nil then
testfile:close()
return {
type="modpack",
path=temppath
}
end
local subdirs = core.get_dir_list(temppath, true)
--only single mod or modpack allowed
if #subdirs ~= 1 then
return {
type = "invalid",
path = ""
}
end
testfile =
io.open(temppath .. DIR_DELIM .. subdirs[1] ..DIR_DELIM .."init.lua","r")
if testfile ~= nil then
testfile:close()
return {
type="mod",
path= temppath .. DIR_DELIM .. subdirs[1]
}
end
testfile =
io.open(temppath .. DIR_DELIM .. subdirs[1] ..DIR_DELIM .."modpack.txt","r")
if testfile ~= nil then
testfile:close()
return {
type="modpack",
path=temppath .. DIR_DELIM .. subdirs[1]
}
end
return {
type = "invalid",
path = ""
}
end
--------------------------------------------------------------------------------
function modmgr.isValidModname(modpath)
if modpath:find("-") ~= nil then
return false
end
return true
end
--------------------------------------------------------------------------------
function modmgr.parse_register_line(line)
local pos1 = line:find("\"")
local pos2 = nil
if pos1 ~= nil then
pos2 = line:find("\"",pos1+1)
end
if pos1 ~= nil and pos2 ~= nil then
local item = line:sub(pos1+1,pos2-1)
if item ~= nil and
item ~= "" then
local pos3 = item:find(":")
if pos3 ~= nil then
local retval = item:sub(1,pos3-1)
if retval ~= nil and
retval ~= "" then
return retval
end
end
end
end
return nil
end
--------------------------------------------------------------------------------
function modmgr.parse_dofile_line(modpath,line)
local pos1 = line:find("\"")
local pos2 = nil
if pos1 ~= nil then
pos2 = line:find("\"",pos1+1)
end
if pos1 ~= nil and pos2 ~= nil then
local filename = line:sub(pos1+1,pos2-1)
if filename ~= nil and
filename ~= "" and
filename:find(".lua") then
return modmgr.identify_modname(modpath,filename)
end
end
return nil
end
--------------------------------------------------------------------------------
function modmgr.identify_modname(modpath,filename)
local testfile = io.open(modpath .. DIR_DELIM .. filename,"r")
if testfile ~= nil then
local line = testfile:read()
while line~= nil do
local modname = nil
if line:find("minetest.register_tool") then
modname = modmgr.parse_register_line(line)
end
if line:find("minetest.register_craftitem") then
modname = modmgr.parse_register_line(line)
end
if line:find("minetest.register_node") then
modname = modmgr.parse_register_line(line)
end
if line:find("dofile") then
modname = modmgr.parse_dofile_line(modpath,line)
end
if modname ~= nil then
testfile:close()
return modname
end
line = testfile:read()
end
testfile:close()
end
return nil
end
--------------------------------------------------------------------------------
function modmgr.render_modlist(render_list)
local retval = ""
if render_list == nil then
if modmgr.global_mods == nil then
modmgr.refresh_globals()
end
render_list = modmgr.global_mods
end
local list = render_list:get_list()
local last_modpack = nil
for i,v in ipairs(list) do
if retval ~= "" then
retval = retval ..","
end
local color = ""
if v.is_modpack then
local rawlist = render_list:get_raw_list()
local all_enabled = true
for j=1,#rawlist,1 do
if rawlist[j].modpack == list[i].name and
rawlist[j].enabled ~= true then
all_enabled = false
break
end
end
if all_enabled == false then
color = mt_color_grey
else
color = mt_color_dark_green
end
end
if v.typ == "game_mod" then
color = mt_color_blue
else
if v.enabled then
color = mt_color_green
end
end
retval = retval .. color
if v.modpack ~= nil then
retval = retval .. " "
end
retval = retval .. v.name
end
return retval
end
--------------------------------------------------------------------------------
function modmgr.get_dependencies(modfolder)
local toadd_hard = ""
local toadd_soft = ""
if modfolder ~= nil then
local filename = modfolder ..
DIR_DELIM .. "depends.txt"
local hard_dependencies = {}
local soft_dependencies = {}
local dependencyfile = io.open(filename,"r")
if dependencyfile then
local dependency = dependencyfile:read("*l")
while dependency do
dependency = dependency:gsub("\r", "")
if string.sub(dependency, -1, -1) == "?" then
table.insert(soft_dependencies, string.sub(dependency, 1, -2))
else
table.insert(hard_dependencies, dependency)
end
dependency = dependencyfile:read()
end
dependencyfile:close()
end
toadd_hard = table.concat(hard_dependencies, ",")
toadd_soft = table.concat(soft_dependencies, ",")
end
return toadd_hard, toadd_soft
end
--------------------------------------------------------------------------------
function modmgr.get_worldconfig(worldpath)
local filename = worldpath ..
DIR_DELIM .. "world.mt"
local worldfile = Settings(filename)
local worldconfig = {}
worldconfig.global_mods = {}
worldconfig.game_mods = {}
for key,value in pairs(worldfile:to_table()) do
if key == "gameid" then
worldconfig.id = value
elseif key:sub(0, 9) == "load_mod_" then
worldconfig.global_mods[key] = core.is_yes(value)
else
worldconfig[key] = value
end
end
--read gamemods
local gamespec = gamemgr.find_by_gameid(worldconfig.id)
gamemgr.get_game_mods(gamespec, worldconfig.game_mods)
return worldconfig
end
--------------------------------------------------------------------------------
function modmgr.installmod(modfilename,basename)
local modfile = modmgr.identify_filetype(modfilename)
local modpath = modmgr.extract(modfile)
if modpath == nil then
gamedata.errormessage = fgettext("Install Mod: file: \"$1\"", modfile.name) ..
fgettext("\nInstall Mod: unsupported filetype \"$1\" or broken archive", modfile.type)
return
end
local basefolder = modmgr.getbasefolder(modpath)
if basefolder.type == "modpack" then
local clean_path = nil
if basename ~= nil then
clean_path = "mp_" .. basename
end
if clean_path == nil then
clean_path = get_last_folder(cleanup_path(basefolder.path))
end
if clean_path ~= nil then
local targetpath = core.get_modpath() .. DIR_DELIM .. clean_path
if not core.copy_dir(basefolder.path,targetpath) then
gamedata.errormessage = fgettext("Failed to install $1 to $2", basename, targetpath)
end
else
gamedata.errormessage = fgettext("Install Mod: unable to find suitable foldername for modpack $1", modfilename)
end
end
if basefolder.type == "mod" then
local targetfolder = basename
if targetfolder == nil then
targetfolder = modmgr.identify_modname(basefolder.path,"init.lua")
end
--if heuristic failed try to use current foldername
if targetfolder == nil then
targetfolder = get_last_folder(basefolder.path)
end
if targetfolder ~= nil and modmgr.isValidModname(targetfolder) then
local targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder
core.copy_dir(basefolder.path,targetpath)
else
gamedata.errormessage = fgettext("Install Mod: unable to find real modname for: $1", modfilename)
end
end
core.delete_dir(modpath)
modmgr.refresh_globals()
end
--------------------------------------------------------------------------------
function modmgr.preparemodlist(data)
local retval = {}
local global_mods = {}
local game_mods = {}
--read global mods
local modpath = core.get_modpath()
if modpath ~= nil and
modpath ~= "" then
get_mods(modpath,global_mods)
end
for i=1,#global_mods,1 do
global_mods[i].typ = "global_mod"
retval[#retval + 1] = global_mods[i]
end
--read game mods
local gamespec = gamemgr.find_by_gameid(data.gameid)
gamemgr.get_game_mods(gamespec, game_mods)
for i=1,#game_mods,1 do
game_mods[i].typ = "game_mod"
retval[#retval + 1] = game_mods[i]
end
if data.worldpath == nil then
return retval
end
--read world mod configuration
local filename = data.worldpath ..
DIR_DELIM .. "world.mt"
local worldfile = Settings(filename)
for key,value in pairs(worldfile:to_table()) do
if key:sub(1, 9) == "load_mod_" then
key = key:sub(10)
local element = nil
for i=1,#retval,1 do
if retval[i].name == key and
not retval[i].is_modpack then
element = retval[i]
break
end
end
if element ~= nil then
element.enabled = core.is_yes(value)
else
core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
end
end
end
return retval
end
--------------------------------------------------------------------------------
function modmgr.comparemod(elem1,elem2)
if elem1 == nil or elem2 == nil then
return false
end
if elem1.name ~= elem2.name then
return false
end
if elem1.is_modpack ~= elem2.is_modpack then
return false
end
if elem1.typ ~= elem2.typ then
return false
end
if elem1.modpack ~= elem2.modpack then
return false
end
if elem1.path ~= elem2.path then
return false
end
return true
end
--------------------------------------------------------------------------------
function modmgr.mod_exists(basename)
if modmgr.global_mods == nil then
modmgr.refresh_globals()
end
if modmgr.global_mods:raw_index_by_uid(basename) > 0 then
return true
end
return false
end
--------------------------------------------------------------------------------
function modmgr.get_global_mod(idx)
if modmgr.global_mods == nil then
return nil
end
if idx == nil or idx < 1 or
idx > modmgr.global_mods:size() then
return nil
end
return modmgr.global_mods:get_list()[idx]
end
--------------------------------------------------------------------------------
function modmgr.refresh_globals()
modmgr.global_mods = filterlist.create(
modmgr.preparemodlist, --refresh
modmgr.comparemod, --compare
function(element,uid) --uid match
if element.name == uid then
return true
end
end,
nil, --filter
{}
)
modmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list)
modmgr.global_mods:set_sortmode("alphabetic")
end
--------------------------------------------------------------------------------
function modmgr.identify_filetype(name)
if name:sub(-3):lower() == "zip" then
return {
name = name,
type = "zip"
}
end
if name:sub(-6):lower() == "tar.gz" or
name:sub(-3):lower() == "tgz"then
return {
name = name,
type = "tgz"
}
end
if name:sub(-6):lower() == "tar.bz2" then
return {
name = name,
type = "tbz"
}
end
if name:sub(-2):lower() == "7z" then
return {
name = name,
type = "7z"
}
end
return {
name = name,
type = "ukn"
}
end

View File

@ -0,0 +1,614 @@
--Minetest
--Copyright (C) 2013 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--------------------------------------------------------------------------------
--modstore implementation
modstore = {}
--------------------------------------------------------------------------------
-- @function [parent=#modstore] init
function modstore.init(size, unsortedmods, searchmods)
modstore.mods_on_unsorted_page = unsortedmods
modstore.mods_on_search_page = searchmods
modstore.modsperpage = modstore.mods_on_unsorted_page
modstore.basetexturedir = core.get_texturepath() .. DIR_DELIM .. "base" ..
DIR_DELIM .. "pack" .. DIR_DELIM
modstore.lastmodtitle = ""
modstore.last_search = ""
modstore.searchlist = filterlist.create(
function()
if modstore.modlist_unsorted ~= nil and
modstore.modlist_unsorted.data ~= nil then
return modstore.modlist_unsorted.data
end
return {}
end,
function(element,modid)
if element.id == modid then
return true
end
return false
end, --compare fct
nil, --uid match fct
function(element,substring)
if substring == nil or
substring == "" then
return false
end
substring = substring:upper()
if element.title ~= nil and
element.title:upper():find(substring) ~= nil then
return true
end
if element.details ~= nil and
element.details.author ~= nil and
element.details.author:upper():find(substring) ~= nil then
return true
end
if element.details ~= nil and
element.details.description ~= nil and
element.details.description:upper():find(substring) ~= nil then
return true
end
return false
end --filter fct
)
modstore.current_list = nil
modstore.tv_store = tabview_create("modstore",size,{x=0,y=0})
modstore.tv_store:set_global_event_handler(modstore.handle_events)
modstore.tv_store:add(
{
name = "unsorted",
caption = fgettext("Unsorted"),
cbf_formspec = modstore.unsorted_tab,
cbf_button_handler = modstore.handle_buttons,
on_change =
function() modstore.modsperpage = modstore.mods_on_unsorted_page end
}
)
modstore.tv_store:add(
{
name = "search",
caption = fgettext("Search"),
cbf_formspec = modstore.getsearchpage,
cbf_button_handler = modstore.handle_buttons,
on_change = modstore.activate_search_tab
}
)
end
--------------------------------------------------------------------------------
-- @function [parent=#modstore] nametoindex
function modstore.nametoindex(name)
for i=1,#modstore.tabnames,1 do
if modstore.tabnames[i] == name then
return i
end
end
return 1
end
--------------------------------------------------------------------------------
-- @function [parent=#modstore] showdownloading
function modstore.showdownloading(title)
local new_dlg = dialog_create("store_downloading",
function(data)
return "size[6,2]label[0.25,0.75;" ..
fgettext("Downloading $1, please wait...", data.title) .. "]"
end,
function(this,fields)
if fields["btn_hidden_close_download"] ~= nil then
if fields["btn_hidden_close_download"].successfull then
modstore.lastmodentry = fields["btn_hidden_close_download"]
modstore.successfulldialog(this)
else
this.parent:show()
this:delete()
modstore.lastmodtitle = ""
end
return true
end
return false
end,
nil)
new_dlg:set_parent(modstore.tv_store)
modstore.tv_store:hide()
new_dlg.data.title = title
new_dlg:show()
end
--------------------------------------------------------------------------------
-- @function [parent=#modstore] successfulldialog
function modstore.successfulldialog(downloading_dlg)
local new_dlg = dialog_create("store_downloading",
function(data)
local retval = ""
retval = retval .. "size[6,2,true]"
if modstore.lastmodentry ~= nil then
retval = retval .. "label[0,0.25;" .. fgettext("Successfully installed:") .. "]"
retval = retval .. "label[3,0.25;" .. modstore.lastmodentry.moddetails.title .. "]"
retval = retval .. "label[0,0.75;" .. fgettext("Shortname:") .. "]"
retval = retval .. "label[3,0.75;" .. core.formspec_escape(modstore.lastmodentry.moddetails.basename) .. "]"
end
retval = retval .. "button[2.2,1.5;1.5,0.5;btn_confirm_mod_successfull;" .. fgettext("Ok") .. "]"
return retval
end,
function(this,fields)
if fields["btn_confirm_mod_successfull"] ~= nil then
this.parent:show()
downloading_dlg:delete()
this:delete()
return true
end
return false
end,
nil)
new_dlg:set_parent(modstore.tv_store)
modstore.tv_store:hide()
new_dlg:show()
end
--------------------------------------------------------------------------------
-- @function [parent=#modstore] handle_buttons
function modstore.handle_buttons(parent, fields, name, data)
if fields["btn_modstore_page_up"] then
if modstore.current_list ~= nil and modstore.current_list.page > 0 then
modstore.current_list.page = modstore.current_list.page - 1
end
return true
end
if fields["btn_modstore_page_down"] then
if modstore.current_list ~= nil and
modstore.current_list.page <modstore.current_list.pagecount-1 then
modstore.current_list.page = modstore.current_list.page +1
end
return true
end
if fields["btn_modstore_search"] or
(fields["key_enter"] and fields["te_modstore_search"] ~= nil) then
modstore.last_search = fields["te_modstore_search"]
filterlist.set_filtercriteria(modstore.searchlist,fields["te_modstore_search"])
filterlist.refresh(modstore.searchlist)
modstore.currentlist = {
page = 0,
pagecount = math.ceil(filterlist.size(modstore.searchlist) / modstore.modsperpage),
data = filterlist.get_list(modstore.searchlist),
}
return true
end
if fields["btn_modstore_close"] then
local maintab = ui.find_by_name("maintab")
parent:hide()
maintab:show()
return true
end
for key,value in pairs(fields) do
local foundat = key:find("btn_install_mod_")
if ( foundat == 1) then
local modid = tonumber(key:sub(17))
for i=1,#modstore.modlist_unsorted.data,1 do
if modstore.modlist_unsorted.data[i].id == modid then
local moddetails = modstore.modlist_unsorted.data[i].details
modstore.lastmodtitle = moddetails.title
if not core.handle_async(
function(param)
local fullurl = core.setting_get("modstore_download_url") ..
param.moddetails.download_url
if param.version ~= nil then
local found = false
for i=1,#param.moddetails.versions, 1 do
if param.moddetails.versions[i].date:sub(1,10) == param.version then
fullurl = core.setting_get("modstore_download_url") ..
param.moddetails.versions[i].download_url
found = true
end
end
if not found then
core.log("error","no download url found for version " .. dump(param.version))
return {
moddetails = param.moddetails,
successfull = false
}
end
end
if core.download_file(fullurl,param.filename) then
return {
texturename = param.texturename,
moddetails = param.moddetails,
filename = param.filename,
successfull = true
}
else
core.log("error","downloading " .. dump(fullurl) .. " failed")
return {
moddetails = param.moddetails,
successfull = false
}
end
end,
{
moddetails = moddetails,
version = fields["dd_version" .. modid],
filename = os.tempfolder() .. "_MODNAME_" .. moddetails.basename .. ".zip",
texturename = modstore.modlist_unsorted.data[i].texturename
},
function(result)
--print("Result from async: " .. dump(result.successfull))
if result.successfull then
modmgr.installmod(result.filename,result.moddetails.basename)
os.remove(result.filename)
else
gamedata.errormessage = "Failed to download " .. result.moddetails.title
end
if gamedata.errormessage == nil then
core.button_handler({btn_hidden_close_download=result})
else
core.button_handler({btn_hidden_close_download={successfull=false}})
end
end
) then
print("ERROR: async event failed")
gamedata.errormessage = "Failed to download " .. modstore.lastmodtitle
end
modstore.showdownloading(modstore.lastmodtitle)
return true
end
end
return true
end
end
return false
end
--------------------------------------------------------------------------------
-- @function [parent=#modstore] handle_events
function modstore.handle_events(this,event)
if (event == "MenuQuit") then
this:hide()
return true
end
end
--------------------------------------------------------------------------------
-- @function [parent=#modstore] update_modlist
function modstore.update_modlist()
modstore.modlist_unsorted = {}
modstore.modlist_unsorted.data = {}
modstore.modlist_unsorted.pagecount = 1
modstore.modlist_unsorted.page = 0
core.handle_async(
function(param)
return core.get_modstore_list()
end,
nil,
function(result)
if result ~= nil then
modstore.modlist_unsorted = {}
modstore.modlist_unsorted.data = result
if modstore.modlist_unsorted.data ~= nil then
modstore.modlist_unsorted.pagecount =
math.ceil((#modstore.modlist_unsorted.data / modstore.modsperpage))
else
modstore.modlist_unsorted.data = {}
modstore.modlist_unsorted.pagecount = 1
end
modstore.modlist_unsorted.page = 0
modstore.fetchdetails()
core.event_handler("Refresh")
end
end
)
end
--------------------------------------------------------------------------------
-- @function [parent=#modstore] fetchdetails
function modstore.fetchdetails()
for i=1,#modstore.modlist_unsorted.data,1 do
core.handle_async(
function(param)
param.details = core.get_modstore_details(tostring(param.modid))
return param
end,
{
modid=modstore.modlist_unsorted.data[i].id,
listindex=i
},
function(result)
if result ~= nil and
modstore.modlist_unsorted ~= nil
and modstore.modlist_unsorted.data ~= nil and
modstore.modlist_unsorted.data[result.listindex] ~= nil and
modstore.modlist_unsorted.data[result.listindex].id ~= nil then
modstore.modlist_unsorted.data[result.listindex].details = result.details
core.event_handler("Refresh")
end
end
)
end
end
--------------------------------------------------------------------------------
-- @function [parent=#modstore] getscreenshot
function modstore.getscreenshot(ypos,listentry)
if listentry.details ~= nil and
(listentry.details.screenshot_url == nil or
listentry.details.screenshot_url == "") then
if listentry.texturename == nil then
listentry.texturename = defaulttexturedir .. "no_screenshot.png"
end
return "image[0,".. ypos .. ";3,2;" ..
core.formspec_escape(listentry.texturename) .. "]"
end
if listentry.details ~= nil and
listentry.texturename == nil then
--make sure we don't download multiple times
listentry.texturename = "in progress"
--prepare url and filename
local fullurl = core.setting_get("modstore_download_url") ..
listentry.details.screenshot_url
local filename = os.tempfolder() .. "_MID_" .. listentry.id
--trigger download
core.handle_async(
--first param is downloadfct
function(param)
param.successfull = core.download_file(param.fullurl,param.filename)
return param
end,
--second parameter is data passed to async job
{
fullurl = fullurl,
filename = filename,
modid = listentry.id
},
--integrate result to raw list
function(result)
if result.successfull then
local found = false
for i=1,#modstore.modlist_unsorted.data,1 do
if modstore.modlist_unsorted.data[i].id == result.modid then
found = true
modstore.modlist_unsorted.data[i].texturename = result.filename
break
end
end
if found then
core.event_handler("Refresh")
else
core.log("error","got screenshot but didn't find matching mod: " .. result.modid)
end
end
end
)
end
if listentry.texturename ~= nil and
listentry.texturename ~= "in progress" then
return "image[0,".. ypos .. ";3,2;" ..
core.formspec_escape(listentry.texturename) .. "]"
end
return ""
end
--------------------------------------------------------------------------------
--@function [parent=#modstore] getshortmodinfo
function modstore.getshortmodinfo(ypos,listentry,details)
local retval = ""
retval = retval .. "box[0," .. ypos .. ";11.4,1.75;#FFFFFF]"
--screenshot
retval = retval .. modstore.getscreenshot(ypos,listentry)
--title + author
retval = retval .."label[2.75," .. ypos .. ";" ..
core.formspec_escape(details.title) .. " (" .. details.author .. ")]"
--description
local descriptiony = ypos + 0.5
retval = retval .. "textarea[3," .. descriptiony .. ";6.5,1.55;;" ..
core.formspec_escape(details.description) .. ";]"
--rating
local ratingy = ypos
retval = retval .."label[7," .. ratingy .. ";" ..
fgettext("Rating") .. ":]"
retval = retval .. "label[8.7," .. ratingy .. ";" .. details.rating .."]"
--versions (IMPORTANT has to be defined AFTER rating)
if details.versions ~= nil and
#details.versions > 1 then
local versiony = ypos + 0.05
retval = retval .. "dropdown[9.1," .. versiony .. ";2.48,0.25;dd_version" .. details.id .. ";"
local versions = ""
for i=1,#details.versions , 1 do
if versions ~= "" then
versions = versions .. ","
end
versions = versions .. details.versions[i].date:sub(1,10)
end
retval = retval .. versions .. ";1]"
end
if details.basename then
--install button
local buttony = ypos + 1.2
retval = retval .."button[9.1," .. buttony .. ";2.5,0.5;btn_install_mod_" .. details.id .. ";"
if modmgr.mod_exists(details.basename) then
retval = retval .. fgettext("re-Install") .."]"
else
retval = retval .. fgettext("Install") .."]"
end
end
return retval
end
--------------------------------------------------------------------------------
--@function [parent=#modstore] getmodlist
function modstore.getmodlist(list,yoffset)
modstore.current_list = list
if yoffset == nil then
yoffset = 0
end
local sb_y_start = 0.2 + yoffset
local sb_y_end = (modstore.modsperpage * 1.75) + ((modstore.modsperpage-1) * 0.15)
local close_button = "button[4," .. (sb_y_end + 0.3 + yoffset) ..
";4,0.5;btn_modstore_close;" .. fgettext("Close store") .. "]"
if #list.data == 0 then
return close_button
end
local scrollbar = ""
scrollbar = scrollbar .. "label[0.1,".. (sb_y_end + 0.25 + yoffset) ..";"
.. fgettext("Page $1 of $2", list.page+1, list.pagecount) .. "]"
scrollbar = scrollbar .. "box[11.6," .. sb_y_start .. ";0.28," .. sb_y_end .. ";#000000]"
local scrollbarpos = (sb_y_start + 0.5) +
((sb_y_end -1.6)/(list.pagecount-1)) * list.page
scrollbar = scrollbar .. "box[11.6," ..scrollbarpos .. ";0.28,0.5;#32CD32]"
scrollbar = scrollbar .. "button[11.6," .. (sb_y_start)
.. ";0.5,0.5;btn_modstore_page_up;^]"
scrollbar = scrollbar .. "button[11.6," .. (sb_y_start + sb_y_end - 0.5)
.. ";0.5,0.5;btn_modstore_page_down;v]"
local retval = ""
local endmod = (list.page * modstore.modsperpage) + modstore.modsperpage
if (endmod > #list.data) then
endmod = #list.data
end
for i=(list.page * modstore.modsperpage) +1, endmod, 1 do
--getmoddetails
local details = list.data[i].details
if details == nil then
details = {}
details.title = list.data[i].title
details.author = ""
details.rating = -1
details.description = ""
end
if details ~= nil then
local screenshot_ypos =
yoffset +(i-1 - (list.page * modstore.modsperpage))*1.9 +0.2
retval = retval .. modstore.getshortmodinfo(screenshot_ypos,
list.data[i],
details)
end
end
return retval .. scrollbar .. close_button
end
--------------------------------------------------------------------------------
--@function [parent=#modstore] getsearchpage
function modstore.getsearchpage(tabview, name, tabdata)
local retval = ""
local search = ""
if modstore.last_search ~= nil then
search = modstore.last_search
end
retval = retval ..
"button[9.5,0.2;2.5,0.5;btn_modstore_search;".. fgettext("Search") .. "]" ..
"field[0.5,0.5;9,0.5;te_modstore_search;;" .. search .. "]"
retval = retval ..
modstore.getmodlist(
modstore.currentlist,
1.75)
return retval;
end
--------------------------------------------------------------------------------
--@function [parent=#modstore] unsorted_tab
function modstore.unsorted_tab()
return modstore.getmodlist(modstore.modlist_unsorted)
end
--------------------------------------------------------------------------------
--@function [parent=#modstore] activate_search_tab
function modstore.activate_search_tab(type, old_tab, new_tab)
if old_tab == new_tab then
return
end
filterlist.set_filtercriteria(modstore.searchlist,modstore.last_search)
filterlist.refresh(modstore.searchlist)
modstore.modsperpage = modstore.mods_on_search_page
modstore.currentlist = {
page = 0,
pagecount =
math.ceil(filterlist.size(modstore.searchlist) / modstore.modsperpage),
data = filterlist.get_list(modstore.searchlist),
}
end

View File

@ -0,0 +1,99 @@
--Minetest
--Copyright (C) 2013 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--------------------------------------------------------------------------------
local core_developers = {
"Perttu Ahola (celeron55) <celeron55@gmail.com>",
"Ryan Kwolek (kwolekr) <kwolekr@minetest.net>",
"PilzAdam <pilzadam@minetest.net>",
"sfan5 <sfan5@live.de>",
"kahrl <kahrl@gmx.net>",
"sapier",
"ShadowNinja <shadowninja@minetest.net>",
"Nathanaël Courant (Nore/Ekdohibs) <nore@mesecons.net>",
"Loic Blot (nerzhul/nrz) <loic.blot@unix-experience.fr>",
"Matt Gregory (paramat)",
"est31 <MTest31@outlook.com>",
"Craig Robbins (Zeno) <craig.d.robbins@gmail.com>",
"Auke Kok (sofar) <sofar@foo-projects.org>",
"Andrew Ward (rubenwardy) <rubenwardy@gmail.com>",
}
local active_contributors = {
"Duane Robertson <duane@duanerobertson.com>",
"SmallJoker <mk939@ymail.com>",
"Lars Hofhansl <larsh@apache.org>",
"Jeija <jeija@mesecons.net>",
"Gregory Currie (gregorycu)",
"Sokomine <wegwerf@anarres.dyndns.org>",
"TeTpaAka",
"Jean-Patrick G (kilbith) <jeanpatrick.guerrero@gmail.com>",
"Diego Martínez (kaeza) <kaeza@users.sf.net>",
"Dániel Juhász (juhdanad) <juhdanad@gmail.com>",
"Rogier <rogier777@gmail.com>",
}
local previous_core_developers = {
"BlockMen",
"Maciej Kasatkin (RealBadAngel) <maciej.kasatkin@o2.pl>",
"Lisa Milne (darkrose) <lisa@ltmnet.com>",
"proller",
"Ilya Zhuravlev (xyz) <xyz@minetest.net>",
}
local previous_contributors = {
"Vanessa Ezekowitz (VanessaE) <vanessaezekowitz@gmail.com>",
"Jurgen Doser (doserj) <jurgen.doser@gmail.com>",
"MirceaKitsune <mirceakitsune@gmail.com>",
"dannydark <the_skeleton_of_a_child@yahoo.co.uk>",
"0gb.us <0gb.us@0gb.us>",
"Guiseppe Bilotta (Oblomov) <guiseppe.bilotta@gmail.com>",
"Jonathan Neuschafer <j.neuschaefer@gmx.net>",
"Nils Dagsson Moskopp (erlehmann) <nils@dieweltistgarnichtso.net>",
"Břetislav Štec (t0suj4/TBC_x)",
"Aaron Suen <warr1024@gmail.com>",
"Constantin Wenger (SpeedProg) <constantin.wenger@googlemail.com>",
"matttpt <matttpt@gmail.com>",
"JacobF <queatz@gmail.com>",
"TriBlade9 <triblade9@mail.com>",
"Zefram <zefram@fysh.org>",
}
return {
name = "credits",
caption = fgettext("Credits"),
cbf_formspec = function(tabview, name, tabdata)
local logofile = defaulttexturedir .. "logo.png"
local version = core.get_version()
return "image[0.5,1;" .. core.formspec_escape(logofile) .. "]" ..
"label[0.5,3.2;" .. version.project .. " " .. version.string .. "]" ..
"label[0.5,3.5;http://minetest.net]" ..
"tablecolumns[color;text]" ..
"tableoptions[background=#00000000;highlight=#00000000;border=false]" ..
"table[3.5,-0.25;8.5,6.05;list_credits;" ..
"#FFFF00," .. fgettext("Core Developers") .. ",," ..
table.concat(core_developers, ",,") .. ",,," ..
"#FFFF00," .. fgettext("Active Contributors") .. ",," ..
table.concat(active_contributors, ",,") .. ",,," ..
"#FFFF00," .. fgettext("Previous Core Developers") ..",," ..
table.concat(previous_core_developers, ",,") .. ",,," ..
"#FFFF00," .. fgettext("Previous Contributors") .. ",," ..
table.concat(previous_contributors, ",,") .. "," ..
";1]"
end
}

View File

@ -0,0 +1,184 @@
--Minetest
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--------------------------------------------------------------------------------
local function get_formspec(tabview, name, tabdata)
if modmgr.global_mods == nil then
modmgr.refresh_globals()
end
if tabdata.selected_mod == nil then
tabdata.selected_mod = 1
end
local retval =
"label[0.05,-0.25;".. fgettext("Installed Mods:") .. "]" ..
"textlist[0,0.25;5.1,5;modlist;" ..
modmgr.render_modlist(modmgr.global_mods) ..
";" .. tabdata.selected_mod .. "]"
retval = retval ..
-- "label[0.8,4.2;" .. fgettext("Add mod:") .. "]" ..
-- TODO Disabled due to upcoming release 0.4.8 and irrlicht messing up localization
-- "button[0.75,4.85;1.8,0.5;btn_mod_mgr_install_local;".. fgettext("Local install") .. "]" ..
-- TODO Disabled due to service being offline, and not likely to come online again, in this form
-- "button[0,4.85;5.25,0.5;btn_modstore;".. fgettext("Online mod repository") .. "]"
""
local selected_mod = nil
if filterlist.size(modmgr.global_mods) >= tabdata.selected_mod then
selected_mod = modmgr.global_mods:get_list()[tabdata.selected_mod]
end
if selected_mod ~= nil then
local modscreenshot = nil
--check for screenshot beeing available
local screenshotfilename = selected_mod.path .. DIR_DELIM .. "screenshot.png"
local error = nil
local screenshotfile,error = io.open(screenshotfilename,"r")
if error == nil then
screenshotfile:close()
modscreenshot = screenshotfilename
end
if modscreenshot == nil then
modscreenshot = defaulttexturedir .. "no_screenshot.png"
end
retval = retval
.. "image[5.5,0;3,2;" .. core.formspec_escape(modscreenshot) .. "]"
.. "label[8.25,0.6;" .. selected_mod.name .. "]"
local descriptionlines = nil
error = nil
local descriptionfilename = selected_mod.path .. "description.txt"
local descriptionfile,error = io.open(descriptionfilename,"r")
if error == nil then
local descriptiontext = descriptionfile:read("*all")
descriptionlines = core.splittext(descriptiontext,42)
descriptionfile:close()
else
descriptionlines = {}
descriptionlines[#descriptionlines + 1] = fgettext("No mod description available")
end
retval = retval ..
"label[5.5,1.7;".. fgettext("Mod information:") .. "]" ..
"textlist[5.5,2.2;6.2,2.4;description;"
for i=1,#descriptionlines,1 do
retval = retval .. core.formspec_escape(descriptionlines[i]) .. ","
end
if selected_mod.is_modpack then
retval = retval .. ";0]" ..
"button[10,4.85;2,0.5;btn_mod_mgr_rename_modpack;" ..
fgettext("Rename") .. "]"
retval = retval .. "button[5.5,4.85;4.5,0.5;btn_mod_mgr_delete_mod;"
.. fgettext("Uninstall selected modpack") .. "]"
else
--show dependencies
local toadd_hard, toadd_soft = modmgr.get_dependencies(selected_mod.path)
if toadd_hard == "" and toadd_soft == "" then
retval = retval .. "," .. fgettext("No dependencies.")
else
if toadd_hard ~= "" then
retval = retval .. "," .. fgettext("Dependencies:") .. ","
retval = retval .. toadd_hard
end
if toadd_soft ~= "" then
if toadd_hard ~= "" then
retval = retval .. ","
end
retval = retval .. "," .. fgettext("Optional dependencies:") .. ","
retval = retval .. toadd_soft
end
end
retval = retval .. ";0]"
retval = retval .. "button[5.5,4.85;4.5,0.5;btn_mod_mgr_delete_mod;"
.. fgettext("Uninstall selected mod") .. "]"
end
end
return retval
end
--------------------------------------------------------------------------------
local function handle_buttons(tabview, fields, tabname, tabdata)
if fields["modlist"] ~= nil then
local event = core.explode_textlist_event(fields["modlist"])
tabdata.selected_mod = event.index
return true
end
if fields["btn_mod_mgr_install_local"] ~= nil then
core.show_file_open_dialog("mod_mgt_open_dlg",fgettext("Select Mod File:"))
return true
end
if fields["btn_modstore"] ~= nil then
local modstore_ui = ui.find_by_name("modstore")
if modstore_ui ~= nil then
tabview:hide()
modstore.update_modlist()
modstore_ui:show()
else
print("modstore ui element not found")
end
return true
end
if fields["btn_mod_mgr_rename_modpack"] ~= nil then
local dlg_renamemp = create_rename_modpack_dlg(tabdata.selected_mod)
dlg_renamemp:set_parent(tabview)
tabview:hide()
dlg_renamemp:show()
return true
end
if fields["btn_mod_mgr_delete_mod"] ~= nil then
local dlg_delmod = create_delete_mod_dlg(tabdata.selected_mod)
dlg_delmod:set_parent(tabview)
tabview:hide()
dlg_delmod:show()
return true
end
if fields["mod_mgt_open_dlg_accepted"] ~= nil and
fields["mod_mgt_open_dlg_accepted"] ~= "" then
modmgr.installmod(fields["mod_mgt_open_dlg_accepted"],nil)
return true
end
return false
end
--------------------------------------------------------------------------------
return {
name = "mods",
caption = fgettext("Mods"),
cbf_formspec = get_formspec,
cbf_button_handler = handle_buttons,
on_change = gamemgr.update_gamelist
}

View File

@ -0,0 +1,349 @@
--Minetest
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--------------------------------------------------------------------------------
local function get_formspec(tabview, name, tabdata)
-- Update the cached supported proto info,
-- it may have changed after a change by the settings menu.
common_update_cached_supp_proto()
local fav_selected = nil
if menudata.search_result then
fav_selected = menudata.search_result[tabdata.fav_selected]
else
fav_selected = menudata.favorites[tabdata.fav_selected]
end
if not tabdata.search_for then
tabdata.search_for = ""
end
local retval =
-- Search
"field[0.15,0.35;6.05,0.27;te_search;;"..core.formspec_escape(tabdata.search_for).."]"..
"button[5.8,0.1;2,0.1;btn_mp_search;" .. fgettext("Search") .. "]" ..
-- Address / Port
"label[7.75,-0.25;" .. fgettext("Address / Port") .. "]" ..
"field[8,0.65;3.25,0.5;te_address;;" ..
core.formspec_escape(core.setting_get("address")) .. "]" ..
"field[11.1,0.65;1.4,0.5;te_port;;" ..
core.formspec_escape(core.setting_get("remote_port")) .. "]" ..
-- Name / Password
"label[7.75,0.95;" .. fgettext("Name / Password") .. "]" ..
"field[8,1.85;2.9,0.5;te_name;;" ..
core.formspec_escape(core.setting_get("name")) .. "]" ..
"pwdfield[10.73,1.85;1.77,0.5;te_pwd;]" ..
-- Description Background
"box[7.73,2.25;4.25,2.6;#999999]"..
-- Connect
"button[10.1,5.15;2,0.5;btn_mp_connect;" .. fgettext("Connect") .. "]"
if tabdata.fav_selected and fav_selected then
if gamedata.fav then
retval = retval .. "button[7.75,5.15;2.3,0.5;btn_delete_favorite;" ..
fgettext("Del. Favorite") .. "]"
end
if fav_selected.description then
retval = retval .. "textarea[8.1,2.3;4.23,2.9;;" ..
core.formspec_escape((gamedata.serverdescription or ""), true) .. ";]"
end
end
--favourites
retval = retval .. "tablecolumns[" ..
image_column(fgettext("Favorite"), "favorite") .. ";" ..
image_column(fgettext("Ping"), "") .. ",padding=0.25;" ..
"color,span=3;" ..
"text,align=right;" .. -- clients
"text,align=center,padding=0.25;" .. -- "/"
"text,align=right,padding=0.25;" .. -- clients_max
image_column(fgettext("Creative mode"), "creative") .. ",padding=1;" ..
image_column(fgettext("Damage enabled"), "damage") .. ",padding=0.25;" ..
image_column(fgettext("PvP enabled"), "pvp") .. ",padding=0.25;" ..
"color,span=1;" ..
"text,padding=1]" ..
"table[-0.15,0.6;7.75,5.15;favourites;"
if menudata.search_result then
for i = 1, #menudata.search_result do
local favs = core.get_favorites("local")
local server = menudata.search_result[i]
for fav_id = 1, #favs do
if server.address == favs[fav_id].address and
server.port == favs[fav_id].port then
server.is_favorite = true
end
end
if i ~= 1 then
retval = retval .. ","
end
retval = retval .. render_serverlist_row(server, server.is_favorite)
end
elseif #menudata.favorites > 0 then
local favs = core.get_favorites("local")
if #favs > 0 then
for i = 1, #favs do
for j = 1, #menudata.favorites do
if menudata.favorites[j].address == favs[i].address and
menudata.favorites[j].port == favs[i].port then
table.insert(menudata.favorites, i, table.remove(menudata.favorites, j))
end
end
if favs[i].address ~= menudata.favorites[i].address then
table.insert(menudata.favorites, i, favs[i])
end
end
end
retval = retval .. render_serverlist_row(menudata.favorites[1], (#favs > 0))
for i = 2, #menudata.favorites do
retval = retval .. "," .. render_serverlist_row(menudata.favorites[i], (i <= #favs))
end
end
if tabdata.fav_selected then
retval = retval .. ";" .. tabdata.fav_selected .. "]"
else
retval = retval .. ";0]"
end
return retval
end
--------------------------------------------------------------------------------
local function main_button_handler(tabview, fields, name, tabdata)
local serverlist = menudata.search_result or menudata.favorites
if fields.te_name then
gamedata.playername = fields.te_name
core.setting_set("name", fields.te_name)
end
if fields.favourites then
local event = core.explode_table_event(fields.favourites)
local fav = serverlist[event.row]
if event.type == "DCL" then
if event.row <= #serverlist then
if menudata.favorites_is_public and
not is_server_protocol_compat_or_error(
fav.proto_min, fav.proto_max) then
return true
end
gamedata.address = fav.address
gamedata.port = fav.port
gamedata.playername = fields.te_name
gamedata.selected_world = 0
if fields.te_pwd then
gamedata.password = fields.te_pwd
end
gamedata.servername = fav.name
gamedata.serverdescription = fav.description
if gamedata.address and gamedata.port then
core.setting_set("address", gamedata.address)
core.setting_set("remote_port", gamedata.port)
core.start()
end
end
return true
end
if event.type == "CHG" then
if event.row <= #serverlist then
gamedata.fav = false
local favs = core.get_favorites("local")
local address = fav.address
local port = fav.port
gamedata.serverdescription = fav.description
for i = 1, #favs do
if fav.address == favs[i].address and
fav.port == favs[i].port then
gamedata.fav = true
end
end
if address and port then
core.setting_set("address", address)
core.setting_set("remote_port", port)
end
tabdata.fav_selected = event.row
end
return true
end
end
if fields.key_up or fields.key_down then
local fav_idx = core.get_table_index("favourites")
local fav = serverlist[fav_idx]
if fav_idx then
if fields.key_up and fav_idx > 1 then
fav_idx = fav_idx - 1
elseif fields.key_down and fav_idx < #menudata.favorites then
fav_idx = fav_idx + 1
end
else
fav_idx = 1
end
if not menudata.favorites or not fav then
tabdata.fav_selected = 0
return true
end
local address = fav.address
local port = fav.port
gamedata.serverdescription = fav.description
if address and port then
core.setting_set("address", address)
core.setting_set("remote_port", port)
end
tabdata.fav_selected = fav_idx
return true
end
if fields.btn_delete_favorite then
local current_favourite = core.get_table_index("favourites")
if not current_favourite then return end
core.delete_favorite(current_favourite)
asyncOnlineFavourites()
tabdata.fav_selected = nil
core.setting_set("address", "")
core.setting_set("remote_port", "30000")
return true
end
if fields.btn_mp_search or fields.key_enter_field == "te_search" then
tabdata.fav_selected = 1
local input = fields.te_search:lower()
tabdata.search_for = fields.te_search
if #menudata.favorites < 2 then
return true
end
menudata.search_result = {}
-- setup the keyword list
local keywords = {}
for word in input:gmatch("%S+") do
table.insert(keywords, word)
end
if #keywords == 0 then
menudata.search_result = nil
return true
end
-- Search the serverlist
local search_result = {}
for i = 1, #menudata.favorites do
local server = menudata.favorites[i]
local found = 0
for k = 1, #keywords do
local keyword = keywords[k]
if server.name then
local name = server.name:lower()
local _, count = name:gsub(keyword, keyword)
found = found + count * 4
end
if server.description then
local desc = server.description:lower()
local _, count = desc:gsub(keyword, keyword)
found = found + count * 2
end
end
if found > 0 then
local points = (#menudata.favorites - i) / 5 + found
server.points = points
table.insert(search_result, server)
end
end
if #search_result > 0 then
table.sort(search_result, function(a, b)
return a.points > b.points
end)
menudata.search_result = search_result
local first_server = search_result[1]
core.setting_set("address", first_server.address)
core.setting_set("remote_port", first_server.port)
end
return true
end
if (fields.btn_mp_connect or fields.key_enter) and fields.te_address and fields.te_port then
gamedata.playername = fields.te_name
gamedata.password = fields.te_pwd
gamedata.address = fields.te_address
gamedata.port = fields.te_port
gamedata.selected_world = 0
local fav_idx = core.get_table_index("favourites")
local fav = serverlist[fav_idx]
if fav_idx and fav_idx <= #serverlist and
fav.address == fields.te_address and
fav.port == fields.te_port then
gamedata.servername = fav.name
gamedata.serverdescription = fav.description
if menudata.favorites_is_public and
not is_server_protocol_compat_or_error(
fav.proto_min, fav.proto_max) then
return true
end
else
gamedata.servername = ""
gamedata.serverdescription = ""
end
core.setting_set("address", fields.te_address)
core.setting_set("remote_port", fields.te_port)
core.start()
return true
end
return false
end
local function on_change(type, old_tab, new_tab)
if type == "LEAVE" then return end
asyncOnlineFavourites()
end
--------------------------------------------------------------------------------
return {
name = "multiplayer",
caption = fgettext("Client"),
cbf_formspec = get_formspec,
cbf_button_handler = main_button_handler,
on_change = on_change
}

View File

@ -0,0 +1,195 @@
--Minetest
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--------------------------------------------------------------------------------
local function get_formspec(tabview, name, tabdata)
local index = menudata.worldlist:get_current_index(
tonumber(core.setting_get("mainmenu_last_selected_world"))
)
local retval =
"button[4,4.15;2.6,0.5;world_delete;" .. fgettext("Delete") .. "]" ..
"button[6.5,4.15;2.8,0.5;world_create;" .. fgettext("New") .. "]" ..
"button[9.2,4.15;2.55,0.5;world_configure;" .. fgettext("Configure") .. "]" ..
"button[8.5,5;3.25,0.5;start_server;" .. fgettext("Start Game") .. "]" ..
"label[4,-0.25;" .. fgettext("Select World:") .. "]" ..
"checkbox[0.25,0.25;cb_creative_mode;" .. fgettext("Creative Mode") .. ";" ..
dump(core.setting_getbool("creative_mode")) .. "]" ..
"checkbox[0.25,0.7;cb_enable_damage;" .. fgettext("Enable Damage") .. ";" ..
dump(core.setting_getbool("enable_damage")) .. "]" ..
"checkbox[0.25,1.15;cb_server_announce;" .. fgettext("Public") .. ";" ..
dump(core.setting_getbool("server_announce")) .. "]" ..
"label[0.25,2.2;" .. fgettext("Name/Password") .. "]" ..
"field[0.55,3.2;3.5,0.5;te_playername;;" ..
core.formspec_escape(core.setting_get("name")) .. "]" ..
"pwdfield[0.55,4;3.5,0.5;te_passwd;]"
local bind_addr = core.setting_get("bind_address")
if bind_addr ~= nil and bind_addr ~= "" then
retval = retval ..
"field[0.55,5.2;2.25,0.5;te_serveraddr;" .. fgettext("Bind Address") .. ";" ..
core.formspec_escape(core.setting_get("bind_address")) .. "]" ..
"field[2.8,5.2;1.25,0.5;te_serverport;" .. fgettext("Port") .. ";" ..
core.formspec_escape(core.setting_get("port")) .. "]"
else
retval = retval ..
"field[0.55,5.2;3.5,0.5;te_serverport;" .. fgettext("Server Port") .. ";" ..
core.formspec_escape(core.setting_get("port")) .. "]"
end
retval = retval ..
"textlist[4,0.25;7.5,3.7;srv_worlds;" ..
menu_render_worldlist() ..
";" .. index .. "]"
return retval
end
--------------------------------------------------------------------------------
local function main_button_handler(this, fields, name, tabdata)
local world_doubleclick = false
if fields["srv_worlds"] ~= nil then
local event = core.explode_textlist_event(fields["srv_worlds"])
local selected = core.get_textlist_index("srv_worlds")
menu_worldmt_legacy(selected)
if event.type == "DCL" then
world_doubleclick = true
end
if event.type == "CHG" then
core.setting_set("mainmenu_last_selected_world",
menudata.worldlist:get_raw_index(core.get_textlist_index("srv_worlds")))
return true
end
end
if menu_handle_key_up_down(fields,"srv_worlds","mainmenu_last_selected_world") then
return true
end
if fields["cb_creative_mode"] then
core.setting_set("creative_mode", fields["cb_creative_mode"])
local selected = core.get_textlist_index("srv_worlds")
menu_worldmt(selected, "creative_mode", fields["cb_creative_mode"])
return true
end
if fields["cb_enable_damage"] then
core.setting_set("enable_damage", fields["cb_enable_damage"])
local selected = core.get_textlist_index("srv_worlds")
menu_worldmt(selected, "enable_damage", fields["cb_enable_damage"])
return true
end
if fields["cb_server_announce"] then
core.setting_set("server_announce", fields["cb_server_announce"])
local selected = core.get_textlist_index("srv_worlds")
menu_worldmt(selected, "server_announce", fields["cb_server_announce"])
return true
end
if fields["start_server"] ~= nil or
world_doubleclick or
fields["key_enter"] then
local selected = core.get_textlist_index("srv_worlds")
gamedata.selected_world = menudata.worldlist:get_raw_index(selected)
if selected ~= nil and gamedata.selected_world ~= 0 then
gamedata.playername = fields["te_playername"]
gamedata.password = fields["te_passwd"]
gamedata.port = fields["te_serverport"]
gamedata.address = ""
core.setting_set("port",gamedata.port)
if fields["te_serveraddr"] ~= nil then
core.setting_set("bind_address",fields["te_serveraddr"])
end
--update last game
local world = menudata.worldlist:get_raw_element(gamedata.selected_world)
if world then
local game, index = gamemgr.find_by_gameid(world.gameid)
core.setting_set("menu_last_game", game.id)
end
core.start()
else
gamedata.errormessage =
fgettext("No world created or selected!")
end
return true
end
if fields["world_create"] ~= nil then
local create_world_dlg = create_create_world_dlg(true)
create_world_dlg:set_parent(this)
create_world_dlg:show()
this:hide()
return true
end
if fields["world_delete"] ~= nil then
local selected = core.get_textlist_index("srv_worlds")
if selected ~= nil and
selected <= menudata.worldlist:size() then
local world = menudata.worldlist:get_list()[selected]
if world ~= nil and
world.name ~= nil and
world.name ~= "" then
local index = menudata.worldlist:get_raw_index(selected)
local delete_world_dlg = create_delete_world_dlg(world.name,index)
delete_world_dlg:set_parent(this)
delete_world_dlg:show()
this:hide()
end
end
return true
end
if fields["world_configure"] ~= nil then
local selected = core.get_textlist_index("srv_worlds")
if selected ~= nil then
local configdialog =
create_configure_world_dlg(
menudata.worldlist:get_raw_index(selected))
if (configdialog ~= nil) then
configdialog:set_parent(this)
configdialog:show()
this:hide()
end
end
return true
end
return false
end
--------------------------------------------------------------------------------
return {
name = "server",
caption = fgettext("Server"),
cbf_formspec = get_formspec,
cbf_button_handler = main_button_handler,
on_change = nil
}

View File

@ -0,0 +1,402 @@
--Minetest
--Copyright (C) 2013 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--------------------------------------------------------------------------------
local labels = {
leaves = {
fgettext("Opaque Leaves"),
fgettext("Simple Leaves"),
fgettext("Fancy Leaves")
},
node_highlighting = {
fgettext("Node Outlining"),
fgettext("Node Highlighting")
},
filters = {
fgettext("No Filter"),
fgettext("Bilinear Filter"),
fgettext("Trilinear Filter")
},
mipmap = {
fgettext("No Mipmap"),
fgettext("Mipmap"),
fgettext("Mipmap + Aniso. Filter")
},
antialiasing = {
fgettext("None"),
fgettext("2x"),
fgettext("4x"),
fgettext("8x")
}
}
local dd_options = {
leaves = {
table.concat(labels.leaves, ","),
{"opaque", "simple", "fancy"}
},
node_highlighting = {
table.concat(labels.node_highlighting, ","),
{"box", "halo"}
},
filters = {
table.concat(labels.filters, ","),
{"", "bilinear_filter", "trilinear_filter"}
},
mipmap = {
table.concat(labels.mipmap, ","),
{"", "mip_map", "anisotropic_filter"}
},
antialiasing = {
table.concat(labels.antialiasing, ","),
{"0", "2", "4", "8"}
}
}
local getSettingIndex = {
Leaves = function()
local style = core.setting_get("leaves_style")
for idx, name in pairs(dd_options.leaves[2]) do
if style == name then return idx end
end
return 1
end,
NodeHighlighting = function()
local style = core.setting_get("node_highlighting")
for idx, name in pairs(dd_options.node_highlighting[2]) do
if style == name then return idx end
end
return 1
end,
Filter = function()
if core.setting_get(dd_options.filters[2][3]) == "true" then
return 3
elseif core.setting_get(dd_options.filters[2][3]) == "false" and
core.setting_get(dd_options.filters[2][2]) == "true" then
return 2
end
return 1
end,
Mipmap = function()
if core.setting_get(dd_options.mipmap[2][3]) == "true" then
return 3
elseif core.setting_get(dd_options.mipmap[2][3]) == "false" and
core.setting_get(dd_options.mipmap[2][2]) == "true" then
return 2
end
return 1
end,
Antialiasing = function()
local antialiasing_setting = core.setting_get("fsaa")
for i = 1, #dd_options.antialiasing[2] do
if antialiasing_setting == dd_options.antialiasing[2][i] then
return i
end
end
return 1
end
}
local function antialiasing_fname_to_name(fname)
for i = 1, #labels.antialiasing do
if fname == labels.antialiasing[i] then
return dd_options.antialiasing[2][i]
end
end
return 0
end
local function dlg_confirm_reset_formspec(data)
return "size[8,3]" ..
"label[1,1;" .. fgettext("Are you sure to reset your singleplayer world?") .. "]" ..
"button[1,2;2.6,0.5;dlg_reset_singleplayer_confirm;" .. fgettext("Yes") .. "]" ..
"button[4,2;2.8,0.5;dlg_reset_singleplayer_cancel;" .. fgettext("No") .. "]"
end
local function dlg_confirm_reset_btnhandler(this, fields, dialogdata)
if fields["dlg_reset_singleplayer_confirm"] ~= nil then
local worldlist = core.get_worlds()
local found_singleplayerworld = false
for i = 1, #worldlist do
if worldlist[i].name == "singleplayerworld" then
found_singleplayerworld = true
gamedata.worldindex = i
end
end
if found_singleplayerworld then
core.delete_world(gamedata.worldindex)
end
core.create_world("singleplayerworld", 1)
worldlist = core.get_worlds()
found_singleplayerworld = false
for i = 1, #worldlist do
if worldlist[i].name == "singleplayerworld" then
found_singleplayerworld = true
gamedata.worldindex = i
end
end
end
this.parent:show()
this:hide()
this:delete()
return true
end
local function showconfirm_reset(tabview)
local new_dlg = dialog_create("reset_spworld",
dlg_confirm_reset_formspec,
dlg_confirm_reset_btnhandler,
nil)
new_dlg:set_parent(tabview)
tabview:hide()
new_dlg:show()
end
local function formspec(tabview, name, tabdata)
local tab_string =
"box[0,0;3.5,4.5;#999999]" ..
"checkbox[0.25,0;cb_smooth_lighting;" .. fgettext("Smooth Lighting") .. ";"
.. dump(core.setting_getbool("smooth_lighting")) .. "]" ..
"checkbox[0.25,0.5;cb_particles;" .. fgettext("Particles") .. ";"
.. dump(core.setting_getbool("enable_particles")) .. "]" ..
"checkbox[0.25,1;cb_3d_clouds;" .. fgettext("3D Clouds") .. ";"
.. dump(core.setting_getbool("enable_3d_clouds")) .. "]" ..
"checkbox[0.25,1.5;cb_opaque_water;" .. fgettext("Opaque Water") .. ";"
.. dump(core.setting_getbool("opaque_water")) .. "]" ..
"checkbox[0.25,2.0;cb_connected_glass;" .. fgettext("Connected Glass") .. ";"
.. dump(core.setting_getbool("connected_glass")) .. "]" ..
"dropdown[0.25,2.8;3.3;dd_node_highlighting;" .. dd_options.node_highlighting[1] .. ";"
.. getSettingIndex.NodeHighlighting() .. "]" ..
"dropdown[0.25,3.6;3.3;dd_leaves_style;" .. dd_options.leaves[1] .. ";"
.. getSettingIndex.Leaves() .. "]" ..
"box[3.75,0;3.75,3.45;#999999]" ..
"label[3.85,0.1;" .. fgettext("Texturing:") .. "]" ..
"dropdown[3.85,0.55;3.85;dd_filters;" .. dd_options.filters[1] .. ";"
.. getSettingIndex.Filter() .. "]" ..
"dropdown[3.85,1.35;3.85;dd_mipmap;" .. dd_options.mipmap[1] .. ";"
.. getSettingIndex.Mipmap() .. "]" ..
"label[3.85,2.15;" .. fgettext("Antialiasing:") .. "]" ..
"dropdown[3.85,2.6;3.85;dd_antialiasing;" .. dd_options.antialiasing[1] .. ";"
.. getSettingIndex.Antialiasing() .. "]" ..
"box[7.75,0;4,4.4;#999999]" ..
"checkbox[8,0;cb_shaders;" .. fgettext("Shaders") .. ";"
.. dump(core.setting_getbool("enable_shaders")) .. "]"
if PLATFORM == "Android" then
tab_string = tab_string ..
"button[8,4.75;3.75,0.5;btn_reset_singleplayer;"
.. fgettext("Reset singleplayer world") .. "]"
else
tab_string = tab_string ..
"button[8,4.85;3.75,0.5;btn_change_keys;"
.. fgettext("Change keys") .. "]"
end
tab_string = tab_string ..
"button[0,4.85;3.75,0.5;btn_advanced_settings;"
.. fgettext("Advanced Settings") .. "]"
if core.setting_get("touchscreen_threshold") ~= nil then
tab_string = tab_string ..
"label[4.3,4.1;" .. fgettext("Touchthreshold (px)") .. "]" ..
"dropdown[3.85,4.55;3.85;dd_touchthreshold;0,10,20,30,40,50;" ..
((tonumber(core.setting_get("touchscreen_threshold")) / 10) + 1) .. "]"
end
if core.setting_getbool("enable_shaders") then
tab_string = tab_string ..
"checkbox[8,0.5;cb_bumpmapping;" .. fgettext("Bump Mapping") .. ";"
.. dump(core.setting_getbool("enable_bumpmapping")) .. "]" ..
"checkbox[8,1;cb_tonemapping;" .. fgettext("Tone Mapping") .. ";"
.. dump(core.setting_getbool("tone_mapping")) .. "]" ..
"checkbox[8,1.5;cb_generate_normalmaps;" .. fgettext("Normal Mapping") .. ";"
.. dump(core.setting_getbool("generate_normalmaps")) .. "]" ..
"checkbox[8,2;cb_parallax;" .. fgettext("Parallax Occlusion") .. ";"
.. dump(core.setting_getbool("enable_parallax_occlusion")) .. "]" ..
"checkbox[8,2.5;cb_waving_water;" .. fgettext("Waving Water") .. ";"
.. dump(core.setting_getbool("enable_waving_water")) .. "]" ..
"checkbox[8,3;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";"
.. dump(core.setting_getbool("enable_waving_leaves")) .. "]" ..
"checkbox[8,3.5;cb_waving_plants;" .. fgettext("Waving Plants") .. ";"
.. dump(core.setting_getbool("enable_waving_plants")) .. "]"
else
tab_string = tab_string ..
"tablecolumns[color;text]" ..
"tableoptions[background=#00000000;highlight=#00000000;border=false]" ..
"table[8.33,0.7;3.5,4;shaders;" ..
"#888888," .. fgettext("Bump Mapping") .. "," ..
"#888888," .. fgettext("Tone Mapping") .. "," ..
"#888888," .. fgettext("Normal Mapping") .. "," ..
"#888888," .. fgettext("Parallax Occlusion") .. "," ..
"#888888," .. fgettext("Waving Water") .. "," ..
"#888888," .. fgettext("Waving Leaves") .. "," ..
"#888888," .. fgettext("Waving Plants") .. "," ..
";1]"
end
return tab_string
end
--------------------------------------------------------------------------------
local function handle_settings_buttons(this, fields, tabname, tabdata)
if fields["btn_advanced_settings"] ~= nil then
local adv_settings_dlg = create_adv_settings_dlg()
adv_settings_dlg:set_parent(this)
this:hide()
adv_settings_dlg:show()
--mm_texture.update("singleplayer", current_game())
return true
end
if fields["cb_smooth_lighting"] then
core.setting_set("smooth_lighting", fields["cb_smooth_lighting"])
return true
end
if fields["cb_particles"] then
core.setting_set("enable_particles", fields["cb_particles"])
return true
end
if fields["cb_3d_clouds"] then
core.setting_set("enable_3d_clouds", fields["cb_3d_clouds"])
return true
end
if fields["cb_opaque_water"] then
core.setting_set("opaque_water", fields["cb_opaque_water"])
return true
end
if fields["cb_connected_glass"] then
core.setting_set("connected_glass", fields["cb_connected_glass"])
return true
end
if fields["cb_shaders"] then
if (core.setting_get("video_driver") == "direct3d8" or
core.setting_get("video_driver") == "direct3d9") then
core.setting_set("enable_shaders", "false")
gamedata.errormessage = fgettext("To enable shaders the OpenGL driver needs to be used.")
else
core.setting_set("enable_shaders", fields["cb_shaders"])
end
return true
end
if fields["cb_bumpmapping"] then
core.setting_set("enable_bumpmapping", fields["cb_bumpmapping"])
return true
end
if fields["cb_tonemapping"] then
core.setting_set("tone_mapping", fields["cb_tonemapping"])
return true
end
if fields["cb_generate_normalmaps"] then
core.setting_set("generate_normalmaps", fields["cb_generate_normalmaps"])
return true
end
if fields["cb_parallax"] then
core.setting_set("enable_parallax_occlusion", fields["cb_parallax"])
return true
end
if fields["cb_waving_water"] then
core.setting_set("enable_waving_water", fields["cb_waving_water"])
return true
end
if fields["cb_waving_leaves"] then
core.setting_set("enable_waving_leaves", fields["cb_waving_leaves"])
end
if fields["cb_waving_plants"] then
core.setting_set("enable_waving_plants", fields["cb_waving_plants"])
return true
end
if fields["btn_change_keys"] then
core.show_keys_menu()
return true
end
if fields["cb_touchscreen_target"] then
core.setting_set("touchtarget", fields["cb_touchscreen_target"])
return true
end
if fields["btn_reset_singleplayer"] then
showconfirm_reset(this)
return true
end
--Note dropdowns have to be handled LAST!
local ddhandled = false
for i = 1, #labels.leaves do
if fields["dd_leaves_style"] == labels.leaves[i] then
core.setting_set("leaves_style", dd_options.leaves[2][i])
ddhandled = true
end
end
for i = 1, #labels.node_highlighting do
if fields["dd_node_highlighting"] == labels.node_highlighting[i] then
core.setting_set("node_highlighting", dd_options.node_highlighting[2][i])
ddhandled = true
end
end
if fields["dd_filters"] == labels.filters[1] then
core.setting_set("bilinear_filter", "false")
core.setting_set("trilinear_filter", "false")
ddhandled = true
elseif fields["dd_filters"] == labels.filters[2] then
core.setting_set("bilinear_filter", "true")
core.setting_set("trilinear_filter", "false")
ddhandled = true
elseif fields["dd_filters"] == labels.filters[3] then
core.setting_set("bilinear_filter", "false")
core.setting_set("trilinear_filter", "true")
ddhandled = true
end
if fields["dd_mipmap"] == labels.mipmap[1] then
core.setting_set("mip_map", "false")
core.setting_set("anisotropic_filter", "false")
ddhandled = true
elseif fields["dd_mipmap"] == labels.mipmap[2] then
core.setting_set("mip_map", "true")
core.setting_set("anisotropic_filter", "false")
ddhandled = true
elseif fields["dd_mipmap"] == labels.mipmap[3] then
core.setting_set("mip_map", "true")
core.setting_set("anisotropic_filter", "true")
ddhandled = true
end
if fields["dd_antialiasing"] then
core.setting_set("fsaa",
antialiasing_fname_to_name(fields["dd_antialiasing"]))
ddhandled = true
end
if fields["dd_touchthreshold"] then
core.setting_set("touchscreen_threshold", fields["dd_touchthreshold"])
ddhandled = true
end
return ddhandled
end
return {
name = "settings",
caption = fgettext("Settings"),
cbf_formspec = formspec,
cbf_button_handler = handle_settings_buttons
}

View File

@ -0,0 +1,220 @@
--Minetest
--Copyright (C) 2013 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--------------------------------------------------------------------------------
local function get_formspec(tabview, name, tabdata)
-- Update the cached supported proto info,
-- it may have changed after a change by the settings menu.
common_update_cached_supp_proto()
local fav_selected = menudata.favorites[tabdata.fav_selected]
local retval =
"label[9.5,0;".. fgettext("Name / Password") .. "]" ..
"field[0.25,3.35;5.5,0.5;te_address;;" ..
core.formspec_escape(core.setting_get("address")) .."]" ..
"field[5.75,3.35;2.25,0.5;te_port;;" ..
core.formspec_escape(core.setting_get("remote_port")) .."]" ..
"button[10,2.6;2,1.5;btn_mp_connect;".. fgettext("Connect") .. "]" ..
"field[9.8,1;2.6,0.5;te_name;;" ..
core.formspec_escape(core.setting_get("name")) .."]" ..
"pwdfield[9.8,2;2.6,0.5;te_pwd;]"
if tabdata.fav_selected and fav_selected then
if gamedata.fav then
retval = retval .. "button[7.7,2.6;2.3,1.5;btn_delete_favorite;" ..
fgettext("Del. Favorite") .. "]"
end
end
retval = retval .. "tablecolumns[" ..
image_column(fgettext("Favorite"), "favorite") .. ";" ..
image_column(fgettext("Ping"), "") .. ",padding=0.25;" ..
"color,span=3;" ..
"text,align=right;" .. -- clients
"text,align=center,padding=0.25;" .. -- "/"
"text,align=right,padding=0.25;" .. -- clients_max
image_column(fgettext("Creative mode"), "creative") .. ",padding=1;" ..
image_column(fgettext("Damage enabled"), "damage") .. ",padding=0.25;" ..
image_column(fgettext("PvP enabled"), "pvp") .. ",padding=0.25;" ..
"color,span=1;" ..
"text,padding=1]" .. -- name
"table[-0.05,0;9.2,2.75;favourites;"
if #menudata.favorites > 0 then
local favs = core.get_favorites("local")
if #favs > 0 then
for i = 1, #favs do
for j = 1, #menudata.favorites do
if menudata.favorites[j].address == favs[i].address and
menudata.favorites[j].port == favs[i].port then
table.insert(menudata.favorites, i,
table.remove(menudata.favorites, j))
end
end
if favs[i].address ~= menudata.favorites[i].address then
table.insert(menudata.favorites, i, favs[i])
end
end
end
retval = retval .. render_serverlist_row(menudata.favorites[1], (#favs > 0))
for i = 2, #menudata.favorites do
retval = retval .. "," .. render_serverlist_row(menudata.favorites[i], (i <= #favs))
end
end
if tabdata.fav_selected then
retval = retval .. ";" .. tabdata.fav_selected .. "]"
else
retval = retval .. ";0]"
end
-- separator
retval = retval .. "box[-0.28,3.75;12.4,0.1;#FFFFFF]"
-- checkboxes
retval = retval ..
"checkbox[8.0,3.9;cb_creative;".. fgettext("Creative Mode") .. ";" ..
dump(core.setting_getbool("creative_mode")) .. "]"..
"checkbox[8.0,4.4;cb_damage;".. fgettext("Enable Damage") .. ";" ..
dump(core.setting_getbool("enable_damage")) .. "]"
-- buttons
retval = retval ..
"button[0,3.7;8,1.5;btn_start_singleplayer;" .. fgettext("Start Singleplayer") .. "]" ..
"button[0,4.5;8,1.5;btn_config_sp_world;" .. fgettext("Config mods") .. "]"
return retval
end
--------------------------------------------------------------------------------
local function main_button_handler(tabview, fields, name, tabdata)
if fields.btn_start_singleplayer then
gamedata.selected_world = gamedata.worldindex
gamedata.singleplayer = true
core.start()
return true
end
if fields.favourites then
local event = core.explode_table_event(fields.favourites)
if event.type == "CHG" then
if event.row <= #menudata.favorites then
gamedata.fav = false
local favs = core.get_favorites("local")
local fav = menudata.favorites[event.row]
local address = fav.address
local port = fav.port
gamedata.serverdescription = fav.description
for i = 1, #favs do
if fav.address == favs[i].address and
fav.port == favs[i].port then
gamedata.fav = true
end
end
if address and port then
core.setting_set("address", address)
core.setting_set("remote_port", port)
end
tabdata.fav_selected = event.row
end
return true
end
end
if fields.btn_delete_favorite then
local current_favourite = core.get_table_index("favourites")
if not current_favourite then return end
core.delete_favorite(current_favourite)
asyncOnlineFavourites()
tabdata.fav_selected = nil
core.setting_set("address", "")
core.setting_set("remote_port", "30000")
return true
end
if fields.cb_creative then
core.setting_set("creative_mode", fields.cb_creative)
return true
end
if fields.cb_damage then
core.setting_set("enable_damage", fields.cb_damage)
return true
end
if fields.btn_mp_connect or fields.key_enter then
gamedata.playername = fields.te_name
gamedata.password = fields.te_pwd
gamedata.address = fields.te_address
gamedata.port = fields.te_port
local fav_idx = core.get_textlist_index("favourites")
if fav_idx and fav_idx <= #menudata.favorites and
menudata.favorites[fav_idx].address == fields.te_address and
menudata.favorites[fav_idx].port == fields.te_port then
local fav = menudata.favorites[fav_idx]
gamedata.servername = fav.name
gamedata.serverdescription = fav.description
if menudata.favorites_is_public and
not is_server_protocol_compat_or_error(
fav.proto_min, fav.proto_max) then
return true
end
else
gamedata.servername = ""
gamedata.serverdescription = ""
end
gamedata.selected_world = 0
core.setting_set("address", fields.te_address)
core.setting_set("remote_port", fields.te_port)
core.start()
return true
end
if fields.btn_config_sp_world then
local configdialog = create_configure_world_dlg(1)
if configdialog then
configdialog:set_parent(tabview)
tabview:hide()
configdialog:show()
end
return true
end
end
--------------------------------------------------------------------------------
local function on_activate(type,old_tab,new_tab)
if type == "LEAVE" then return end
asyncOnlineFavourites()
end
--------------------------------------------------------------------------------
return {
name = "main",
caption = fgettext("Main"),
cbf_formspec = get_formspec,
cbf_button_handler = main_button_handler,
on_change = on_activate
}

View File

@ -0,0 +1,250 @@
--Minetest
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local function current_game()
local last_game_id = core.setting_get("menu_last_game")
local game, index = gamemgr.find_by_gameid(last_game_id)
return game
end
local function singleplayer_refresh_gamebar()
local old_bar = ui.find_by_name("game_button_bar")
if old_bar ~= nil then
old_bar:delete()
end
local function game_buttonbar_button_handler(fields)
for key,value in pairs(fields) do
for j=1,#gamemgr.games,1 do
if ("game_btnbar_" .. gamemgr.games[j].id == key) then
mm_texture.update("singleplayer", gamemgr.games[j])
core.set_topleft_text(gamemgr.games[j].name)
core.setting_set("menu_last_game",gamemgr.games[j].id)
menudata.worldlist:set_filtercriteria(gamemgr.games[j].id)
local index = filterlist.get_current_index(menudata.worldlist,
tonumber(core.setting_get("mainmenu_last_selected_world")))
if not index or index < 1 then
local selected = core.get_textlist_index("sp_worlds")
if selected ~= nil and selected < #menudata.worldlist:get_list() then
index = selected
else
index = #menudata.worldlist:get_list()
end
end
menu_worldmt_legacy(index)
return true
end
end
end
end
local btnbar = buttonbar_create("game_button_bar",
game_buttonbar_button_handler,
{x=-0.3,y=5.9}, "horizontal", {x=12.4,y=1.15})
for i=1,#gamemgr.games,1 do
local btn_name = "game_btnbar_" .. gamemgr.games[i].id
local image = nil
local text = nil
local tooltip = core.formspec_escape(gamemgr.games[i].name)
if gamemgr.games[i].menuicon_path ~= nil and
gamemgr.games[i].menuicon_path ~= "" then
image = core.formspec_escape(gamemgr.games[i].menuicon_path)
else
local part1 = gamemgr.games[i].id:sub(1,5)
local part2 = gamemgr.games[i].id:sub(6,10)
local part3 = gamemgr.games[i].id:sub(11)
text = part1 .. "\n" .. part2
if part3 ~= nil and
part3 ~= "" then
text = text .. "\n" .. part3
end
end
btnbar:add_button(btn_name, text, image, tooltip)
end
end
local function get_formspec(tabview, name, tabdata)
local retval = ""
local index = filterlist.get_current_index(menudata.worldlist,
tonumber(core.setting_get("mainmenu_last_selected_world"))
)
retval = retval ..
"button[4,4.15;2.6,0.5;world_delete;".. fgettext("Delete") .. "]" ..
"button[6.5,4.15;2.8,0.5;world_create;".. fgettext("New") .. "]" ..
"button[9.2,4.15;2.55,0.5;world_configure;".. fgettext("Configure") .. "]" ..
"button[8.5,5;3.25,0.5;play;".. fgettext("Play") .. "]" ..
"label[4,-0.25;".. fgettext("Select World:") .. "]"..
"checkbox[0.25,0.25;cb_creative_mode;".. fgettext("Creative Mode") .. ";" ..
dump(core.setting_getbool("creative_mode")) .. "]"..
"checkbox[0.25,0.7;cb_enable_damage;".. fgettext("Enable Damage") .. ";" ..
dump(core.setting_getbool("enable_damage")) .. "]"..
"textlist[4,0.25;7.5,3.7;sp_worlds;" ..
menu_render_worldlist() ..
";" .. index .. "]"
return retval
end
local function main_button_handler(this, fields, name, tabdata)
assert(name == "singleplayer")
local world_doubleclick = false
if fields["sp_worlds"] ~= nil then
local event = core.explode_textlist_event(fields["sp_worlds"])
local selected = core.get_textlist_index("sp_worlds")
menu_worldmt_legacy(selected)
if event.type == "DCL" then
world_doubleclick = true
end
if event.type == "CHG" and selected ~= nil then
core.setting_set("mainmenu_last_selected_world",
menudata.worldlist:get_raw_index(selected))
return true
end
end
if menu_handle_key_up_down(fields,"sp_worlds","mainmenu_last_selected_world") then
return true
end
if fields["cb_creative_mode"] then
core.setting_set("creative_mode", fields["cb_creative_mode"])
local selected = core.get_textlist_index("sp_worlds")
menu_worldmt(selected, "creative_mode", fields["cb_creative_mode"])
return true
end
if fields["cb_enable_damage"] then
core.setting_set("enable_damage", fields["cb_enable_damage"])
local selected = core.get_textlist_index("sp_worlds")
menu_worldmt(selected, "enable_damage", fields["cb_enable_damage"])
return true
end
if fields["play"] ~= nil or
world_doubleclick or
fields["key_enter"] then
local selected = core.get_textlist_index("sp_worlds")
gamedata.selected_world = menudata.worldlist:get_raw_index(selected)
if selected ~= nil and gamedata.selected_world ~= 0 then
gamedata.singleplayer = true
core.start()
else
gamedata.errormessage =
fgettext("No world created or selected!")
end
return true
end
if fields["world_create"] ~= nil then
local create_world_dlg = create_create_world_dlg(true)
create_world_dlg:set_parent(this)
this:hide()
create_world_dlg:show()
mm_texture.update("singleplayer",current_game())
return true
end
if fields["world_delete"] ~= nil then
local selected = core.get_textlist_index("sp_worlds")
if selected ~= nil and
selected <= menudata.worldlist:size() then
local world = menudata.worldlist:get_list()[selected]
if world ~= nil and
world.name ~= nil and
world.name ~= "" then
local index = menudata.worldlist:get_raw_index(selected)
local delete_world_dlg = create_delete_world_dlg(world.name,index)
delete_world_dlg:set_parent(this)
this:hide()
delete_world_dlg:show()
mm_texture.update("singleplayer",current_game())
end
end
return true
end
if fields["world_configure"] ~= nil then
local selected = core.get_textlist_index("sp_worlds")
if selected ~= nil then
local configdialog =
create_configure_world_dlg(
menudata.worldlist:get_raw_index(selected))
if (configdialog ~= nil) then
configdialog:set_parent(this)
this:hide()
configdialog:show()
mm_texture.update("singleplayer",current_game())
end
end
return true
end
end
local function on_change(type, old_tab, new_tab)
local buttonbar = ui.find_by_name("game_button_bar")
if ( buttonbar == nil ) then
singleplayer_refresh_gamebar()
buttonbar = ui.find_by_name("game_button_bar")
end
if (type == "ENTER") then
local game = current_game()
if game then
menudata.worldlist:set_filtercriteria(game.id)
core.set_topleft_text(game.name)
mm_texture.update("singleplayer",game)
end
buttonbar:show()
else
menudata.worldlist:set_filtercriteria(nil)
buttonbar:hide()
core.set_topleft_text("")
mm_texture.update(new_tab,nil)
end
end
--------------------------------------------------------------------------------
return {
name = "singleplayer",
caption = fgettext("Singleplayer"),
cbf_formspec = get_formspec,
cbf_button_handler = main_button_handler,
on_change = on_change
}

View File

@ -0,0 +1,132 @@
--Minetest
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--------------------------------------------------------------------------------
local function filter_texture_pack_list(list)
local retval = {}
for _, item in ipairs(list) do
if item ~= "base" then
retval[#retval + 1] = item
end
end
table.sort(retval)
table.insert(retval, 1, fgettext("None"))
return retval
end
--------------------------------------------------------------------------------
local function render_texture_pack_list(list)
local retval = ""
for i, v in ipairs(list) do
if v:sub(1, 1) ~= "." then
if retval ~= "" then
retval = retval .. ","
end
retval = retval .. core.formspec_escape(v)
end
end
return retval
end
--------------------------------------------------------------------------------
local function get_formspec(tabview, name, tabdata)
local retval = "label[4,-0.25;" .. fgettext("Select texture pack:") .. "]" ..
"textlist[4,0.25;7.5,5.0;TPs;"
local current_texture_path = core.setting_get("texture_path")
local list = filter_texture_pack_list(core.get_dir_list(core.get_texturepath(), true))
local index = tonumber(core.setting_get("mainmenu_last_selected_TP"))
if not index then index = 1 end
if current_texture_path == "" then
retval = retval ..
render_texture_pack_list(list) ..
";" .. index .. "]"
return retval
end
local infofile = current_texture_path .. DIR_DELIM .. "description.txt"
-- This adds backwards compatibility for old texture pack description files named
-- "info.txt", and should be removed once all such texture packs have been updated
if not file_exists(infofile) then
infofile = current_texture_path .. DIR_DELIM .. "info.txt"
if file_exists(infofile) then
core.log("deprecated", "info.txt is deprecated. description.txt should be used instead.")
end
end
local infotext = ""
local f = io.open(infofile, "r")
if not f then
infotext = fgettext("No information available")
else
infotext = f:read("*all")
f:close()
end
local screenfile = current_texture_path .. DIR_DELIM .. "screenshot.png"
local no_screenshot
if not file_exists(screenfile) then
screenfile = nil
no_screenshot = defaulttexturedir .. "no_screenshot.png"
end
return retval ..
render_texture_pack_list(list) ..
";" .. index .. "]" ..
"image[0.25,0.25;4.05,2.7;" .. core.formspec_escape(screenfile or no_screenshot) .. "]" ..
"textarea[0.6,2.85;3.7,1.5;;" .. core.formspec_escape(infotext or "") .. ";]"
end
--------------------------------------------------------------------------------
local function main_button_handler(tabview, fields, name, tabdata)
if fields["TPs"] then
local event = core.explode_textlist_event(fields["TPs"])
if event.type == "CHG" or event.type == "DCL" then
local index = core.get_textlist_index("TPs")
core.setting_set("mainmenu_last_selected_TP", index)
local list = filter_texture_pack_list(core.get_dir_list(core.get_texturepath(), true))
local current_index = core.get_textlist_index("TPs")
if current_index and #list >= current_index then
local new_path = core.get_texturepath() .. DIR_DELIM .. list[current_index]
if list[current_index] == fgettext("None") then
new_path = ""
end
core.setting_set("texture_path", new_path)
end
end
return true
end
return false
end
--------------------------------------------------------------------------------
return {
name = "texturepacks",
caption = fgettext("Texturepacks"),
cbf_formspec = get_formspec,
cbf_button_handler = main_button_handler,
on_change = nil
}

View File

@ -0,0 +1,185 @@
--Minetest
--Copyright (C) 2013 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
mm_texture = {}
--------------------------------------------------------------------------------
function mm_texture.init()
mm_texture.defaulttexturedir = core.get_texturepath() .. DIR_DELIM .. "base" ..
DIR_DELIM .. "pack" .. DIR_DELIM
mm_texture.basetexturedir = mm_texture.defaulttexturedir
mm_texture.texturepack = core.setting_get("texture_path")
mm_texture.gameid = nil
end
--------------------------------------------------------------------------------
function mm_texture.update(tab,gamedetails)
if tab ~= "singleplayer" then
mm_texture.reset()
return
end
if gamedetails == nil then
return
end
mm_texture.update_game(gamedetails)
end
--------------------------------------------------------------------------------
function mm_texture.reset()
mm_texture.gameid = nil
local have_bg = false
local have_overlay = mm_texture.set_generic("overlay")
if not have_overlay then
have_bg = mm_texture.set_generic("background")
end
mm_texture.clear("header")
mm_texture.clear("footer")
core.set_clouds(false)
mm_texture.set_generic("footer")
mm_texture.set_generic("header")
if not have_bg then
if core.setting_getbool("menu_clouds") then
core.set_clouds(true)
else
mm_texture.set_dirt_bg()
end
end
end
--------------------------------------------------------------------------------
function mm_texture.update_game(gamedetails)
if mm_texture.gameid == gamedetails.id then
return
end
local have_bg = false
local have_overlay = mm_texture.set_game("overlay",gamedetails)
if not have_overlay then
have_bg = mm_texture.set_game("background",gamedetails)
end
mm_texture.clear("header")
mm_texture.clear("footer")
core.set_clouds(false)
if not have_bg then
if core.setting_getbool("menu_clouds") then
core.set_clouds(true)
else
mm_texture.set_dirt_bg()
end
end
mm_texture.set_game("footer",gamedetails)
mm_texture.set_game("header",gamedetails)
mm_texture.gameid = gamedetails.id
end
--------------------------------------------------------------------------------
function mm_texture.clear(identifier)
core.set_background(identifier,"")
end
--------------------------------------------------------------------------------
function mm_texture.set_generic(identifier)
--try texture pack first
if mm_texture.texturepack ~= nil then
local path = mm_texture.texturepack .. DIR_DELIM .."menu_" ..
identifier .. ".png"
if core.set_background(identifier,path) then
return true
end
end
if mm_texture.defaulttexturedir ~= nil then
local path = mm_texture.defaulttexturedir .. DIR_DELIM .."menu_" ..
identifier .. ".png"
if core.set_background(identifier,path) then
return true
end
end
return false
end
--------------------------------------------------------------------------------
function mm_texture.set_game(identifier, gamedetails)
if gamedetails == nil then
return false
end
if mm_texture.texturepack ~= nil then
local path = mm_texture.texturepack .. DIR_DELIM ..
gamedetails.id .. "_menu_" .. identifier .. ".png"
if core.set_background(identifier, path) then
return true
end
end
-- Find out how many randomized textures the subgame provides
local n = 0
local filename
local menu_files = core.get_dir_list(gamedetails.path .. DIR_DELIM .. "menu", false)
for i = 1, #menu_files do
filename = identifier .. "." .. i .. ".png"
if table.indexof(menu_files, filename) == -1 then
n = i - 1
break
end
end
-- Select random texture, 0 means standard texture
n = math.random(0, n)
if n == 0 then
filename = identifier .. ".png"
else
filename = identifier .. "." .. n .. ".png"
end
local path = gamedetails.path .. DIR_DELIM .. "menu" ..
DIR_DELIM .. filename
if core.set_background(identifier, path) then
return true
end
return false
end
function mm_texture.set_dirt_bg()
if mm_texture.texturepack ~= nil then
local path = mm_texture.texturepack .. DIR_DELIM .."default_dirt.png"
if core.set_background("background", path, true, 128) then
return true
end
end
-- Use universal fallback texture in textures/base/pack
local minimalpath = defaulttexturedir .. "menu_bg.png"
core.set_background("background", minimalpath, true, 128)
end

View File

@ -0,0 +1,72 @@
--Minetest
--Copyright (C) 2016 T4im
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local profiler_path = core.get_builtin_path()..DIR_DELIM.."profiler"..DIR_DELIM
local profiler = {}
local sampler = assert(loadfile(profiler_path .. "sampling.lua"))(profiler)
local instrumentation = assert(loadfile(profiler_path .. "instrumentation.lua"))(profiler, sampler)
local reporter = dofile(profiler_path .. "reporter.lua")
profiler.instrument = instrumentation.instrument
---
-- Delayed registration of the /profiler chat command
-- Is called later, after `core.register_chatcommand` was set up.
--
function profiler.init_chatcommand()
local instrument_profiler = core.setting_getbool("instrument.profiler") or false
if instrument_profiler then
instrumentation.init_chatcommand()
end
local param_usage = "print [filter] | dump [filter] | save [format [filter]] | reset"
core.register_chatcommand("profiler", {
description = "handle the profiler and profiling data",
params = param_usage,
privs = { server=true },
func = function(name, param)
local command, arg0 = string.match(param, "([^ ]+) ?(.*)")
local args = arg0 and string.split(arg0, " ")
if command == "dump" then
core.log("action", reporter.print(sampler.profile, arg0))
return true, "Statistics written to action log"
elseif command == "print" then
return true, reporter.print(sampler.profile, arg0)
elseif command == "save" then
return reporter.save(sampler.profile, args[1] or "txt", args[2])
elseif command == "reset" then
sampler.reset()
return true, "Statistics were reset"
end
return false, string.format(
"Usage: %s\n" ..
"Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).",
param_usage
)
end
})
if not instrument_profiler then
instrumentation.init_chatcommand()
end
end
sampler.init()
instrumentation.init()
return profiler

View File

@ -0,0 +1,232 @@
--Minetest
--Copyright (C) 2016 T4im
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local format, pairs, type = string.format, pairs, type
local core, get_current_modname = core, core.get_current_modname
local profiler, sampler = ...
local instrument_builtin = core.setting_getbool("instrument.builtin") or false
local register_functions = {
register_globalstep = 0,
register_playerevent = 0,
register_on_placenode = 0,
register_on_dignode = 0,
register_on_punchnode = 0,
register_on_generated = 0,
register_on_newplayer = 0,
register_on_dieplayer = 0,
register_on_respawnplayer = 0,
register_on_prejoinplayer = 0,
register_on_joinplayer = 0,
register_on_leaveplayer = 0,
register_on_cheat = 0,
register_on_chat_message = 0,
register_on_player_receive_fields = 0,
register_on_craft = 0,
register_craft_predict = 0,
register_on_protection_violation = 0,
register_on_item_eat = 0,
register_on_punchplayer = 0,
register_on_player_hpchange = 0,
}
---
-- Create an unique instrument name.
-- Generate a missing label with a running index number.
--
local counts = {}
local function generate_name(def)
local class, label, func_name = def.class, def.label, def.func_name
if label then
if class or func_name then
return format("%s '%s' %s", class or "", label, func_name or ""):trim()
end
return format("%s", label):trim()
elseif label == false then
return format("%s", class or func_name):trim()
end
local index_id = def.mod .. (class or func_name)
local index = counts[index_id] or 1
counts[index_id] = index + 1
return format("%s[%d] %s", class or func_name, index, class and func_name or ""):trim()
end
---
-- Keep `measure` and the closure in `instrument` lean, as these, and their
-- directly called functions are the overhead that is caused by instrumentation.
--
local time, log = core.get_us_time, sampler.log
local function measure(modname, instrument_name, start, ...)
log(modname, instrument_name, time() - start)
return ...
end
--- Automatically instrument a function to measure and log to the sampler.
-- def = {
-- mod = "",
-- class = "",
-- func_name = "",
-- -- if nil, will create a label based on registration order
-- label = "" | false,
-- }
local function instrument(def)
if not def or not def.func then
return
end
def.mod = def.mod or get_current_modname()
local modname = def.mod
local instrument_name = generate_name(def)
local func = def.func
if not instrument_builtin and modname == "*builtin*" then
return func
end
return function(...)
-- This tail-call allows passing all return values of `func`
-- also called https://en.wikipedia.org/wiki/Continuation_passing_style
-- Compared to table creation and unpacking it won't lose `nil` returns
-- and is expected to be faster
-- `measure` will be executed after time() and func(...)
return measure(modname, instrument_name, time(), func(...))
end
end
local function can_be_called(func)
-- It has to be a function or callable table
return type(func) == "function" or
((type(func) == "table" or type(func) == "userdata") and
getmetatable(func) and getmetatable(func).__call)
end
local function assert_can_be_called(func, func_name, level)
if not can_be_called(func) then
-- Then throw an *helpful* error, by pointing on our caller instead of us.
error(format("Invalid argument to %s. Expected function-like type instead of '%s'.", func_name, type(func)), level + 1)
end
end
---
-- Wraps a registration function `func` in such a way,
-- that it will automatically instrument any callback function passed as first argument.
--
local function instrument_register(func, func_name)
local register_name = func_name:gsub("^register_", "", 1)
return function(callback, ...)
assert_can_be_called(callback, func_name, 2)
register_functions[func_name] = register_functions[func_name] + 1
return func(instrument {
func = callback,
func_name = register_name
}), ...
end
end
local function init_chatcommand()
if core.setting_getbool("instrument.chatcommand") or true then
local orig_register_chatcommand = core.register_chatcommand
core.register_chatcommand = function(cmd, def)
def.func = instrument {
func = def.func,
label = "/" .. cmd,
}
orig_register_chatcommand(cmd, def)
end
end
end
---
-- Start instrumenting selected functions
--
local function init()
local is_set = core.setting_getbool
if is_set("instrument.entity") or true then
-- Explicitly declare entity api-methods.
-- Simple iteration would ignore lookup via __index.
local entity_instrumentation = {
"on_activate",
"on_step",
"on_punch",
"rightclick",
"get_staticdata",
}
-- Wrap register_entity() to instrument them on registration.
local orig_register_entity = core.register_entity
core.register_entity = function(name, prototype)
local modname = get_current_modname()
for _, func_name in pairs(entity_instrumentation) do
prototype[func_name] = instrument {
func = prototype[func_name],
mod = modname,
func_name = func_name,
label = prototype.label,
}
end
orig_register_entity(name,prototype)
end
end
if is_set("instrument.abm") or true then
-- Wrap register_abm() to automatically instrument abms.
local orig_register_abm = core.register_abm
core.register_abm = function(spec)
spec.action = instrument {
func = spec.action,
class = "ABM",
label = spec.label,
}
orig_register_abm(spec)
end
end
if is_set("instrument.lbm") or true then
-- Wrap register_lbm() to automatically instrument lbms.
local orig_register_lbm = core.register_lbm
core.register_lbm = function(spec)
spec.action = instrument {
func = spec.action,
class = "LBM",
label = spec.label or spec.name,
}
orig_register_lbm(spec)
end
end
if is_set("instrument.global_callback") or true then
for func_name, _ in pairs(register_functions) do
core[func_name] = instrument_register(core[func_name], func_name)
end
end
if is_set("instrument.profiler") or false then
-- Measure overhead of instrumentation, but keep it down for functions
-- So keep the `return` for better optimization.
profiler.empty_instrument = instrument {
func = function() return end,
mod = "*profiler*",
class = "Instrumentation overhead",
label = false,
}
end
end
return {
register_functions = register_functions,
instrument = instrument,
init = init,
init_chatcommand = init_chatcommand,
}

View File

@ -0,0 +1,277 @@
--Minetest
--Copyright (C) 2016 T4im
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local DIR_DELIM, LINE_DELIM = DIR_DELIM, "\n"
local table, unpack, string, pairs, io, os = table, unpack, string, pairs, io, os
local rep, sprintf, tonumber = string.rep, string.format, tonumber
local core, setting_get = core, core.setting_get
local reporter = {}
---
-- Shorten a string. End on an ellipsis if shortened.
--
local function shorten(str, length)
if str and str:len() > length then
return "..." .. str:sub(-(length-3))
end
return str
end
local function filter_matches(filter, text)
return not filter or string.match(text, filter)
end
local function format_number(number, fmt)
number = tonumber(number)
if not number then
return "N/A"
end
return sprintf(fmt or "%d", number)
end
local Formatter = {
new = function(self, object)
object = object or {}
object.out = {} -- output buffer
self.__index = self
return setmetatable(object, self)
end,
__tostring = function (self)
return table.concat(self.out, LINE_DELIM)
end,
print = function(self, text, ...)
if (...) then
text = sprintf(text, ...)
end
if text then
-- Avoid format unicode issues.
text = text:gsub("Ms", "µs")
end
table.insert(self.out, text or LINE_DELIM)
end,
flush = function(self)
table.insert(self.out, LINE_DELIM)
local text = table.concat(self.out, LINE_DELIM)
self.out = {}
return text
end
}
local widths = { 55, 9, 9, 9, 5, 5, 5 }
local txt_row_format = sprintf(" %%-%ds | %%%ds | %%%ds | %%%ds | %%%ds | %%%ds | %%%ds", unpack(widths))
local HR = {}
for i=1, #widths do
HR[i]= rep("-", widths[i])
end
-- ' | ' should break less with github than '-+-', when people are pasting there
HR = sprintf("-%s-", table.concat(HR, " | "))
local TxtFormatter = Formatter:new {
format_row = function(self, modname, instrument_name, statistics)
local label
if instrument_name then
label = shorten(instrument_name, widths[1] - 5)
label = sprintf(" - %s %s", label, rep(".", widths[1] - 5 - label:len()))
else -- Print mod_stats
label = shorten(modname, widths[1] - 2) .. ":"
end
self:print(txt_row_format, label,
format_number(statistics.time_min),
format_number(statistics.time_max),
format_number(statistics:get_time_avg()),
format_number(statistics.part_min, "%.1f"),
format_number(statistics.part_max, "%.1f"),
format_number(statistics:get_part_avg(), "%.1f")
)
end,
format = function(self, filter)
local profile = self.profile
self:print("Values below show absolute/relative times spend per server step by the instrumented function.")
self:print("A total of %d samples were taken", profile.stats_total.samples)
if filter then
self:print("The output is limited to '%s'", filter)
end
self:print()
self:print(
txt_row_format,
"instrumentation", "min Ms", "max Ms", "avg Ms", "min %", "max %", "avg %"
)
self:print(HR)
for modname,mod_stats in pairs(profile.stats) do
if filter_matches(filter, modname) then
self:format_row(modname, nil, mod_stats)
if mod_stats.instruments ~= nil then
for instrument_name, instrument_stats in pairs(mod_stats.instruments) do
self:format_row(nil, instrument_name, instrument_stats)
end
end
end
end
self:print(HR)
if not filter then
self:format_row("total", nil, profile.stats_total)
end
end
}
local CsvFormatter = Formatter:new {
format_row = function(self, modname, instrument_name, statistics)
self:print(
"%q,%q,%d,%d,%d,%d,%d,%f,%f,%f",
modname, instrument_name,
statistics.samples,
statistics.time_min,
statistics.time_max,
statistics:get_time_avg(),
statistics.time_all,
statistics.part_min,
statistics.part_max,
statistics:get_part_avg()
)
end,
format = function(self, filter)
self:print(
"%q,%q,%q,%q,%q,%q,%q,%q,%q,%q",
"modname", "instrumentation",
"samples",
"time min µs",
"time max µs",
"time avg µs",
"time all µs",
"part min %",
"part max %",
"part avg %"
)
for modname, mod_stats in pairs(self.profile.stats) do
if filter_matches(filter, modname) then
self:format_row(modname, "*", mod_stats)
if mod_stats.instruments ~= nil then
for instrument_name, instrument_stats in pairs(mod_stats.instruments) do
self:format_row(modname, instrument_name, instrument_stats)
end
end
end
end
end
}
local function format_statistics(profile, format, filter)
local formatter
if format == "csv" then
formatter = CsvFormatter:new {
profile = profile
}
else
formatter = TxtFormatter:new {
profile = profile
}
end
formatter:format(filter)
return formatter:flush()
end
---
-- Format the profile ready for display and
-- @return string to be printed to the console
--
function reporter.print(profile, filter)
if filter == "" then filter = nil end
return format_statistics(profile, "txt", filter)
end
---
-- Serialize the profile data and
-- @return serialized data to be saved to a file
--
local function serialize_profile(profile, format, filter)
if format == "lua" or format == "json" or format == "json_pretty" then
local stats = filter and {} or profile.stats
if filter then
for modname, mod_stats in pairs(profile.stats) do
if filter_matches(filter, modname) then
stats[modname] = mod_stats
end
end
end
if format == "lua" then
return core.serialize(stats)
elseif format == "json" then
return core.write_json(stats)
elseif format == "json_pretty" then
return core.write_json(stats, true)
end
end
-- Fall back to textual formats.
return format_statistics(profile, format, filter)
end
local worldpath = core.get_worldpath()
local function get_save_path(format, filter)
local report_path = setting_get("profiler.report_path") or ""
if report_path ~= "" then
core.mkdir(sprintf("%s%s%s", worldpath, DIR_DELIM, report_path))
end
return (sprintf(
"%s/%s/profile-%s%s.%s",
worldpath,
report_path,
os.date("%Y%m%dT%H%M%S"),
filter and ("-" .. filter) or "",
format
):gsub("[/\\]+", DIR_DELIM))-- Clean up delims
end
---
-- Save the profile to the world path.
-- @return success, log message
--
function reporter.save(profile, format, filter)
if not format or format == "" then
format = setting_get("profiler.default_report_format") or "txt"
end
if filter == "" then
filter = nil
end
local path = get_save_path(format, filter)
local output, io_err = io.open(path, "w")
if not output then
return false, "Saving of profile failed with: " .. io_err
end
local content, err = serialize_profile(profile, format, filter)
if not content then
output:close()
return false, "Saving of profile failed with: " .. err
end
output:write(content)
output:close()
local logmessage = "Profile saved to " .. path
core.log("action", logmessage)
return true, logmessage
end
return reporter

View File

@ -0,0 +1,206 @@
--Minetest
--Copyright (C) 2016 T4im
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
local setmetatable = setmetatable
local pairs, format = pairs, string.format
local min, max, huge = math.min, math.max, math.huge
local core = core
local profiler = ...
-- Split sampler and profile up, to possibly allow for rotation later.
local sampler = {}
local profile
local stats_total
local logged_time, logged_data
local _stat_mt = {
get_time_avg = function(self)
return self.time_all/self.samples
end,
get_part_avg = function(self)
if not self.part_all then
return 100 -- Extra handling for "total"
end
return self.part_all/self.samples
end,
}
_stat_mt.__index = _stat_mt
function sampler.reset()
-- Accumulated logged time since last sample.
-- This helps determining, the relative time a mod used up.
logged_time = 0
-- The measurements taken through instrumentation since last sample.
logged_data = {}
profile = {
-- Current mod statistics (max/min over the entire mod lifespan)
-- Mod specific instrumentation statistics are nested within.
stats = {},
-- Current stats over all mods.
stats_total = setmetatable({
samples = 0,
time_min = huge,
time_max = 0,
time_all = 0,
part_min = 100,
part_max = 100
}, _stat_mt)
}
stats_total = profile.stats_total
-- Provide access to the most recent profile.
sampler.profile = profile
end
---
-- Log a measurement for the sampler to pick up later.
-- Keep `log` and its often called functions lean.
-- It will directly add to the instrumentation overhead.
--
function sampler.log(modname, instrument_name, time_diff)
if time_diff <= 0 then
if time_diff < 0 then
-- This **might** have happened on a semi-regular basis with huge mods,
-- resulting in negative statistics (perhaps midnight time jumps or ntp corrections?).
core.log("warning", format(
"Time travel of %s::%s by %dµs.",
modname, instrument_name, time_diff
))
end
-- Throwing these away is better, than having them mess with the overall result.
return
end
local mod_data = logged_data[modname]
if mod_data == nil then
mod_data = {}
logged_data[modname] = mod_data
end
mod_data[instrument_name] = (mod_data[instrument_name] or 0) + time_diff
-- Update logged time since last sample.
logged_time = logged_time + time_diff
end
---
-- Return a requested statistic.
-- Initialize if necessary.
--
local function get_statistic(stats_table, name)
local statistic = stats_table[name]
if statistic == nil then
statistic = setmetatable({
samples = 0,
time_min = huge,
time_max = 0,
time_all = 0,
part_min = 100,
part_max = 0,
part_all = 0,
}, _stat_mt)
stats_table[name] = statistic
end
return statistic
end
---
-- Update a statistic table
--
local function update_statistic(stats_table, time)
stats_table.samples = stats_table.samples + 1
-- Update absolute time (µs) spend by the subject
stats_table.time_min = min(stats_table.time_min, time)
stats_table.time_max = max(stats_table.time_max, time)
stats_table.time_all = stats_table.time_all + time
-- Update relative time (%) of this sample spend by the subject
local current_part = (time/logged_time) * 100
stats_table.part_min = min(stats_table.part_min, current_part)
stats_table.part_max = max(stats_table.part_max, current_part)
stats_table.part_all = stats_table.part_all + current_part
end
---
-- Sample all logged measurements each server step.
-- Like any globalstep function, this should not be too heavy,
-- but does not add to the instrumentation overhead.
--
local function sample(dtime)
-- Rare, but happens and is currently of no informational value.
if logged_time == 0 then
return
end
for modname, instruments in pairs(logged_data) do
local mod_stats = get_statistic(profile.stats, modname)
if mod_stats.instruments == nil then
-- Current statistics for each instrumentation component
mod_stats.instruments = {}
end
local mod_time = 0
for instrument_name, time in pairs(instruments) do
if time > 0 then
mod_time = mod_time + time
local instrument_stats = get_statistic(mod_stats.instruments, instrument_name)
-- Update time of this sample spend by the instrumented function.
update_statistic(instrument_stats, time)
-- Reset logged data for the next sample.
instruments[instrument_name] = 0
end
end
-- Update time of this sample spend by this mod.
update_statistic(mod_stats, mod_time)
end
-- Update the total time spend over all mods.
stats_total.time_min = min(stats_total.time_min, logged_time)
stats_total.time_max = max(stats_total.time_max, logged_time)
stats_total.time_all = stats_total.time_all + logged_time
stats_total.samples = stats_total.samples + 1
logged_time = 0
end
---
-- Setup empty profile and register the sampling function
--
function sampler.init()
sampler.reset()
if core.setting_getbool("instrument.profiler") then
core.register_globalstep(function()
if logged_time == 0 then
return
end
return profiler.empty_instrument()
end)
core.register_globalstep(profiler.instrument {
func = sample,
mod = "*profiler*",
class = "Sampler (update stats)",
label = false,
})
else
core.register_globalstep(sample)
end
end
return sampler

File diff suppressed because it is too large Load Diff

2
minetest/client/serverlist/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -0,0 +1,4 @@
void main(void)
{
gl_FragColor = gl_Color;
}

View File

@ -0,0 +1,9 @@
uniform mat4 mWorldViewProj;
void main(void)
{
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = mWorldViewProj * gl_Vertex;
gl_FrontColor = gl_BackColor = gl_Color;
}

View File

@ -0,0 +1,32 @@
uniform sampler2D baseTexture;
uniform sampler2D normalTexture;
uniform vec3 yawVec;
void main (void)
{
vec2 uv = gl_TexCoord[0].st;
//texture sampling rate
const float step = 1.0 / 256.0;
float tl = texture2D(normalTexture, vec2(uv.x - step, uv.y + step)).r;
float t = texture2D(normalTexture, vec2(uv.x - step, uv.y - step)).r;
float tr = texture2D(normalTexture, vec2(uv.x + step, uv.y + step)).r;
float r = texture2D(normalTexture, vec2(uv.x + step, uv.y)).r;
float br = texture2D(normalTexture, vec2(uv.x + step, uv.y - step)).r;
float b = texture2D(normalTexture, vec2(uv.x, uv.y - step)).r;
float bl = texture2D(normalTexture, vec2(uv.x - step, uv.y - step)).r;
float l = texture2D(normalTexture, vec2(uv.x - step, uv.y)).r;
float dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl);
float dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr);
vec4 bump = vec4 (normalize(vec3 (dX, dY, 0.1)),1.0);
float height = 2.0 * texture2D(normalTexture, vec2(uv.x, uv.y)).r - 1.0;
vec4 base = texture2D(baseTexture, uv).rgba;
vec3 L = normalize(vec3(0.0, 0.75, 1.0));
float specular = pow(clamp(dot(reflect(L, bump.xyz), yawVec), 0.0, 1.0), 1.0);
float diffuse = dot(yawVec, bump.xyz);
vec3 color = (1.1 * diffuse + 0.05 * height + 0.5 * specular) * base.rgb;
vec4 col = vec4(color.rgb, base.a);
col *= gl_Color;
gl_FragColor = vec4(col.rgb, base.a);
}

View File

@ -0,0 +1,9 @@
uniform mat4 mWorldViewProj;
uniform mat4 mWorld;
void main(void)
{
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = mWorldViewProj * gl_Vertex;
gl_FrontColor = gl_BackColor = gl_Color;
}

View File

@ -0,0 +1,219 @@
uniform sampler2D baseTexture;
uniform sampler2D normalTexture;
uniform sampler2D textureFlags;
uniform vec4 skyBgColor;
uniform float fogDistance;
uniform vec3 eyePosition;
varying vec3 vPosition;
varying vec3 worldPosition;
varying float area_enable_parallax;
varying vec3 eyeVec;
varying vec3 tsEyeVec;
varying vec3 lightVec;
varying vec3 tsLightVec;
bool normalTexturePresent = false;
const float e = 2.718281828459;
const float BS = 10.0;
const float fogStart = FOG_START;
const float fogShadingParameter = 1 / ( 1 - fogStart);
#ifdef ENABLE_TONE_MAPPING
/* Hable's UC2 Tone mapping parameters
A = 0.22;
B = 0.30;
C = 0.10;
D = 0.20;
E = 0.01;
F = 0.30;
W = 11.2;
equation used: ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F
*/
vec3 uncharted2Tonemap(vec3 x)
{
return ((x * (0.22 * x + 0.03) + 0.002) / (x * (0.22 * x + 0.3) + 0.06)) - 0.03333;
}
vec4 applyToneMapping(vec4 color)
{
color = vec4(pow(color.rgb, vec3(2.2)), color.a);
const float gamma = 1.6;
const float exposureBias = 5.5;
color.rgb = uncharted2Tonemap(exposureBias * color.rgb);
// Precalculated white_scale from
//vec3 whiteScale = 1.0 / uncharted2Tonemap(vec3(W));
vec3 whiteScale = vec3(1.036015346);
color.rgb *= whiteScale;
return vec4(pow(color.rgb, vec3(1.0 / gamma)), color.a);
}
#endif
void get_texture_flags()
{
vec4 flags = texture2D(textureFlags, vec2(0.0, 0.0));
if (flags.r > 0.5) {
normalTexturePresent = true;
}
}
float intensity(vec3 color)
{
return (color.r + color.g + color.b) / 3.0;
}
float get_rgb_height(vec2 uv)
{
return intensity(texture2D(baseTexture, uv).rgb);
}
vec4 get_normal_map(vec2 uv)
{
vec4 bump = texture2D(normalTexture, uv).rgba;
bump.xyz = normalize(bump.xyz * 2.0 - 1.0);
return bump;
}
float find_intersection(vec2 dp, vec2 ds)
{
float depth = 1.0;
float best_depth = 0.0;
float size = 0.0625;
for (int i = 0; i < 15; i++) {
depth -= size;
float h = texture2D(normalTexture, dp + ds * depth).a;
if (depth <= h) {
best_depth = depth;
break;
}
}
depth = best_depth;
for (int i = 0; i < 4; i++) {
size *= 0.5;
float h = texture2D(normalTexture,dp + ds * depth).a;
if (depth <= h) {
best_depth = depth;
depth += size;
} else {
depth -= size;
}
}
return best_depth;
}
float find_intersectionRGB(vec2 dp, vec2 ds)
{
const float depth_step = 1.0 / 24.0;
float depth = 1.0;
for (int i = 0 ; i < 24 ; i++) {
float h = get_rgb_height(dp + ds * depth);
if (h >= depth)
break;
depth -= depth_step;
}
return depth;
}
void main(void)
{
vec3 color;
vec4 bump;
vec2 uv = gl_TexCoord[0].st;
bool use_normalmap = false;
get_texture_flags();
#ifdef ENABLE_PARALLAX_OCCLUSION
vec2 eyeRay = vec2 (tsEyeVec.x, -tsEyeVec.y);
const float scale = PARALLAX_OCCLUSION_SCALE / PARALLAX_OCCLUSION_ITERATIONS;
const float bias = PARALLAX_OCCLUSION_BIAS / PARALLAX_OCCLUSION_ITERATIONS;
#if PARALLAX_OCCLUSION_MODE == 0
// Parallax occlusion with slope information
if (normalTexturePresent && area_enable_parallax > 0.0) {
for (int i = 0; i < PARALLAX_OCCLUSION_ITERATIONS; i++) {
vec4 normal = texture2D(normalTexture, uv.xy);
float h = normal.a * scale - bias;
uv += h * normal.z * eyeRay;
}
#endif
#if PARALLAX_OCCLUSION_MODE == 1
// Relief mapping
if (normalTexturePresent && area_enable_parallax > 0.0) {
vec2 ds = eyeRay * PARALLAX_OCCLUSION_SCALE;
float dist = find_intersection(uv, ds);
uv += dist * ds;
#endif
} else if (GENERATE_NORMALMAPS == 1 && area_enable_parallax > 0.0) {
vec2 ds = eyeRay * PARALLAX_OCCLUSION_SCALE;
float dist = find_intersectionRGB(uv, ds);
uv += dist * ds;
}
#endif
#if USE_NORMALMAPS == 1
if (normalTexturePresent) {
bump = get_normal_map(uv);
use_normalmap = true;
}
#endif
#if GENERATE_NORMALMAPS == 1
if (normalTexturePresent == false) {
float tl = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y + SAMPLE_STEP));
float t = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y - SAMPLE_STEP));
float tr = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y + SAMPLE_STEP));
float r = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y));
float br = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y - SAMPLE_STEP));
float b = get_rgb_height(vec2(uv.x, uv.y - SAMPLE_STEP));
float bl = get_rgb_height(vec2(uv.x -SAMPLE_STEP, uv.y - SAMPLE_STEP));
float l = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y));
float dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl);
float dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr);
bump = vec4(normalize(vec3 (dX, dY, NORMALMAPS_STRENGTH)), 1.0);
use_normalmap = true;
}
#endif
vec4 base = texture2D(baseTexture, uv).rgba;
#ifdef ENABLE_BUMPMAPPING
if (use_normalmap) {
vec3 L = normalize(lightVec);
vec3 E = normalize(eyeVec);
float specular = pow(clamp(dot(reflect(L, bump.xyz), E), 0.0, 1.0), 1.0);
float diffuse = dot(-E,bump.xyz);
color = (diffuse + 0.1 * specular) * base.rgb;
} else {
color = base.rgb;
}
#else
color = base.rgb;
#endif
vec4 col = vec4(color.rgb * gl_Color.rgb, 1.0);
#ifdef ENABLE_TONE_MAPPING
col = applyToneMapping(col);
#endif
// Due to a bug in some (older ?) graphics stacks (possibly in the glsl compiler ?),
// the fog will only be rendered correctly if the last operation before the
// clamp() is an addition. Else, the clamp() seems to be ignored.
// E.g. the following won't work:
// float clarity = clamp(fogShadingParameter
// * (fogDistance - length(eyeVec)) / fogDistance), 0.0, 1.0);
// As additions usually come for free following a multiplication, the new formula
// should be more efficient as well.
// Note: clarity = (1 - fogginess)
float clarity = clamp(fogShadingParameter
- fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
col = mix(skyBgColor, col, clarity);
col = vec4(col.rgb, base.a);
gl_FragColor = col;
}

View File

@ -0,0 +1,144 @@
uniform mat4 mWorldViewProj;
uniform mat4 mWorld;
// Color of the light emitted by the sun.
uniform vec3 dayLight;
uniform vec3 eyePosition;
uniform float animationTimer;
varying vec3 vPosition;
varying vec3 worldPosition;
varying vec3 eyeVec;
varying vec3 lightVec;
varying vec3 tsEyeVec;
varying vec3 tsLightVec;
varying float area_enable_parallax;
// Color of the light emitted by the light sources.
const vec3 artificialLight = vec3(1.04, 1.04, 1.04);
const float e = 2.718281828459;
const float BS = 10.0;
float smoothCurve(float x)
{
return x * x * (3.0 - 2.0 * x);
}
float triangleWave(float x)
{
return abs(fract(x + 0.5) * 2.0 - 1.0);
}
float smoothTriangleWave(float x)
{
return smoothCurve(triangleWave(x)) * 2.0 - 1.0;
}
void main(void)
{
gl_TexCoord[0] = gl_MultiTexCoord0;
//TODO: make offset depending on view angle and parallax uv displacement
//thats for textures that doesnt align vertically, like dirt with grass
//gl_TexCoord[0].y += 0.008;
//Allow parallax/relief mapping only for certain kind of nodes
//Variable is also used to control area of the effect
#if (DRAW_TYPE == NDT_NORMAL || DRAW_TYPE == NDT_LIQUID || DRAW_TYPE == NDT_FLOWINGLIQUID)
area_enable_parallax = 1.0;
#else
area_enable_parallax = 0.0;
#endif
float disp_x;
float disp_z;
#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES && ENABLE_WAVING_LEAVES) || (MATERIAL_TYPE == TILE_MATERIAL_WAVING_PLANTS && ENABLE_WAVING_PLANTS)
vec4 pos2 = mWorld * gl_Vertex;
float tOffset = (pos2.x + pos2.y) * 0.001 + pos2.z * 0.002;
disp_x = (smoothTriangleWave(animationTimer * 23.0 + tOffset) +
smoothTriangleWave(animationTimer * 11.0 + tOffset)) * 0.4;
disp_z = (smoothTriangleWave(animationTimer * 31.0 + tOffset) +
smoothTriangleWave(animationTimer * 29.0 + tOffset) +
smoothTriangleWave(animationTimer * 13.0 + tOffset)) * 0.5;
#endif
#if (MATERIAL_TYPE == TILE_MATERIAL_LIQUID_TRANSPARENT || MATERIAL_TYPE == TILE_MATERIAL_LIQUID_OPAQUE) && ENABLE_WAVING_WATER
vec4 pos = gl_Vertex;
pos.y -= 2.0;
float posYbuf = (pos.z / WATER_WAVE_LENGTH + animationTimer * WATER_WAVE_SPEED * WATER_WAVE_LENGTH);
pos.y -= sin(posYbuf) * WATER_WAVE_HEIGHT + sin(posYbuf / 7.0) * WATER_WAVE_HEIGHT;
gl_Position = mWorldViewProj * pos;
#elif MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES && ENABLE_WAVING_LEAVES
vec4 pos = gl_Vertex;
pos.x += disp_x;
pos.y += disp_z * 0.1;
pos.z += disp_z;
gl_Position = mWorldViewProj * pos;
#elif MATERIAL_TYPE == TILE_MATERIAL_WAVING_PLANTS && ENABLE_WAVING_PLANTS
vec4 pos = gl_Vertex;
if (gl_TexCoord[0].y < 0.05) {
pos.x += disp_x;
pos.z += disp_z;
}
gl_Position = mWorldViewProj * pos;
#else
gl_Position = mWorldViewProj * gl_Vertex;
#endif
vPosition = gl_Position.xyz;
worldPosition = (mWorld * gl_Vertex).xyz;
// Don't generate heightmaps when too far from the eye
float dist = distance (vec3(0.0, 0.0, 0.0), vPosition);
if (dist > 150.0) {
area_enable_parallax = 0.0;
}
vec3 sunPosition = vec3 (0.0, eyePosition.y * BS + 900.0, 0.0);
vec3 normal, tangent, binormal;
normal = normalize(gl_NormalMatrix * gl_Normal);
tangent = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz);
binormal = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz);
vec3 v;
lightVec = sunPosition - worldPosition;
v.x = dot(lightVec, tangent);
v.y = dot(lightVec, binormal);
v.z = dot(lightVec, normal);
tsLightVec = normalize (v);
eyeVec = -(gl_ModelViewMatrix * gl_Vertex).xyz;
v.x = dot(eyeVec, tangent);
v.y = dot(eyeVec, binormal);
v.z = dot(eyeVec, normal);
tsEyeVec = normalize (v);
// Calculate color.
// Red, green and blue components are pre-multiplied with
// the brightness, so now we have to multiply these
// colors with the color of the incoming light.
// The pre-baked colors are halved to prevent overflow.
vec4 color;
// The alpha gives the ratio of sunlight in the incoming light.
float nightRatio = 1 - gl_Color.a;
color.rgb = gl_Color.rgb * (gl_Color.a * dayLight.rgb +
nightRatio * artificialLight.rgb) * 2;
color.a = 1;
// Emphase blue a bit in darker places
// See C++ implementation in mapblock_mesh.cpp finalColorBlend()
float brightness = (color.r + color.g + color.b) / 3;
color.b += max(0.0, 0.021 - abs(0.2 * brightness - 0.021) +
0.07 * brightness);
gl_FrontColor = gl_BackColor = clamp(color, 0.0, 1.0);
}

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