diff --git a/.omnistudent_notes.txt.swp b/.omnistudent_notes.txt.swp index f0a6e9d..255d310 100644 Binary files a/.omnistudent_notes.txt.swp and b/.omnistudent_notes.txt.swp differ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..153e1c1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,225 @@ +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(${CMAKE_VERSION} STREQUAL "2.8.2") + +# This can be read from ${PROJECT_NAME} after project() is called +project(minetest) + +set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string") + +# Also remember to set PROTOCOL_VERSION in clientserver.h when releasing +set(VERSION_MAJOR 0) +set(VERSION_MINOR 4) +set(VERSION_PATCH 8) +if(VERSION_EXTRA) + set(VERSION_PATCH ${VERSION_PATCH}-${VERSION_EXTRA}) +else() + # Comment the following line during release + set(VERSION_PATCH ${VERSION_PATCH}-dev) +endif() +set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") + +MESSAGE(STATUS "*** Will build version ${VERSION_STRING} ***") + +# Configuration options + +if(WIN32) + set(RUN_IN_PLACE 1 CACHE BOOL "Run directly in source directory structure") +else() + set(RUN_IN_PLACE 0 CACHE BOOL "Run directly in source directory structure") +endif() + +# RUN_IN_PLACE is exported as a #define value, ensure it's 1/0 instead of ON/OFF +if(RUN_IN_PLACE) + set(RUN_IN_PLACE 1) +else() + set(RUN_IN_PLACE 0) +endif() + +set(BUILD_CLIENT 1 CACHE BOOL "Build client") +if(WIN32) + set(BUILD_SERVER 0 CACHE BOOL "Build server") +else() + set(BUILD_SERVER 1 CACHE BOOL "Build server") +endif() + +set(WARN_ALL 1 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/") +include(${CMAKE_SOURCE_DIR}/cmake/Modules/misc.cmake) + +# This is done here so that relative search paths are more reasnable +find_package(Irrlicht) + +# +# Installation +# + +if(WIN32) + set(SHAREDIR ".") + set(BINDIR "bin") + set(DOCDIR "doc") + set(EXAMPLE_CONF_DIR ".") + set(LOCALEDIR "locale") +elseif(APPLE) + # Random placeholders; this isn't usually used and may not work + # See https://github.com/toabi/minetest-mac/ + set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}") + set(BINDIR "bin") + set(DOCDIR "share/doc/${PROJECT_NAME}") + set(EXAMPLE_CONF_DIR ${DOCDIR}) + set(LOCALEDIR "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(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(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}") + 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/minimal" DESTINATION "${SHAREDIR}/games") +set(MINETEST_GAME_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/games/minetest_game") +if(EXISTS ${MINETEST_GAME_SOURCE} AND IS_DIRECTORY ${MINETEST_GAME_SOURCE}) + install(FILES ${MINETEST_GAME_SOURCE}/game.conf DESTINATION "${SHAREDIR}/games/minetest_game/") + install(FILES ${MINETEST_GAME_SOURCE}/README.txt DESTINATION "${SHAREDIR}/games/minetest_game/") + install(DIRECTORY ${MINETEST_GAME_SOURCE}/mods DESTINATION "${SHAREDIR}/games/minetest_game") + install(DIRECTORY ${MINETEST_GAME_SOURCE}/menu DESTINATION "${SHAREDIR}/games/minetest_game") +endif() +if(BUILD_CLIENT) + #install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/sounds/base/pack" DESTINATION "${SHAREDIR}/sounds/base") + 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/mapformat.txt" DESTINATION "${DOCDIR}") +install(FILES "minetest.conf.example" DESTINATION "${EXAMPLE_CONF_DIR}") + +if(UNIX) + install(FILES "doc/minetest.6" "doc/minetestserver.6" DESTINATION "${MANDIR}/man6") + install(FILES "misc/minetest.desktop" DESTINATION "${XDG_APPS_DIR}") + install(FILES "misc/minetest-icon.svg" DESTINATION "${ICONDIR}/hicolor/scalable/apps") +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 ") + +if(WIN32) + # For some reason these aren't copied otherwise + # NOTE: For some reason now it seems to work without these + #if(BUILD_CLIENT) + # install(FILES bin/minetest.exe DESTINATION bin) + #endif() + #if(BUILD_SERVER) + # install(FILES bin/minetestserver.exe DESTINATION bin) + #endif() + + set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-win32") + + set(CPACK_GENERATOR ZIP) + + # This might be needed for some installer + #set(CPACK_PACKAGE_EXECUTABLES bin/minetest.exe "Minetest" bin/minetestserver.exe "Minetest Server") +elseif(APPLE) + # TODO + # see http://cmake.org/Wiki/CMake:CPackPackageGenerators#Bundle_.28OSX_only.29 + # + set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-osx") + set(CPACK_PACKAGE_ICON "") + set(CPACK_BUNDLE_NAME ${PROJECT_NAME}) + set(CPACK_BUNDLE_ICON "") + set(CPACK_BUNDLE_PLIST "") + set(CPACK_BUNDLE_STARTUP_COMMAND "Contents/MacOS/${PROJECT_NAME}") + set(CPACK_GENERATOR "Bundle") +else() + set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-linux") + set(CPACK_GENERATOR TGZ) + set(CPACK_SOURCE_GENERATOR TGZ) +endif() + +include(CPack) + diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..eb5dee1 --- /dev/null +++ b/README.txt @@ -0,0 +1,412 @@ +Minetest +============ + +An InfiniMiner/Minecraft inspired game. + +Copyright (c) 2010-2013 Perttu Ahola +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 +----------------- +- WASD: move +- Space: jump/climb +- Shift: sneak/go down +- Q: drop item +- I: inventory +- Mouse: turn/look +- Mouse left: dig/punch +- Mouse right: place/use +- Mouse wheel: select item +- Esc: pause menu +- T: chat +- 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 = ? +$share = ? +$user = ~/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 + +Command-line options: +--------------------- +- Use --help + +Compiling on GNU/Linux: +----------------------- + +Install dependencies. Here's an example for Debian/Ubuntu: +$ apt-get install build-essential libirrlicht-dev cmake libbz2-dev libpng12-dev libjpeg8-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev + +Download source, extract (this is the URL to the latest of source repository, which might not work at all times): +$ wget https://github.com/minetest/minetest/tarball/master -O master.tar.gz +$ tar xf master.tar.gz +$ cd minetest-minetest-286edd4 (or similar) + +Download minetest_game (otherwise only the "Minimal development test" game is available) +$ cd games/ +$ wget https://github.com/minetest/minetest_game/tarball/master -O minetest_game.tar.gz +$ tar xf minetest_game.tar.gz +$ mv minetest-minetest_game-* minetest_game +$ cd .. + +Build a version that runs directly from the source directory: +$ cmake . -DRUN_IN_PLACE=1 +$ make -j2 + +Run it: +$ cd 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=0 +- You can build a bare server or a bare client by specifying -DBUILD_CLIENT=0 or -DBUILD_SERVER=0 +- You can select between Release and Debug build by -DCMAKE_BUILD_TYPE= + - 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 + +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 + * 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.php +- 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 + - 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.7.1.zip + - 110214175330.zip (or whatever, this is the minetest source) + + zlib-1.2.5 + - zlib.h + + win32 + ... + + zlib125dll + - readme.txt + + dll32 + ... + + irrlicht-1.7.1 + + lib + + include + ... + + 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.7.1 + 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 + ----------------- + - 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=1 -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 + +BlockMen: + textures/base/pack/menuheader.png + +erlehmann: + misc/minetest-icon-24x24.png + misc/minetest-icon.ico + misc/minetest-icon.svg + textures/base/pack/logo.png + +License of Minetest source code +------------------------------- + +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola + +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. diff --git a/builtin/auth.lua b/builtin/auth.lua new file mode 100644 index 0000000..b6cca60 --- /dev/null +++ b/builtin/auth.lua @@ -0,0 +1,188 @@ +-- Minetest: builtin/auth.lua + +-- +-- Authentication handler +-- + +function minetest.string_to_privs(str, delim) + assert(type(str) == "string") + delim = delim or ',' + privs = {} + for _, priv in pairs(string.split(str, delim)) do + privs[priv:trim()] = true + end + return privs +end + +function minetest.privs_to_string(privs, delim) + assert(type(privs) == "table") + delim = delim or ',' + list = {} + for priv, bool in pairs(privs) do + if bool then + table.insert(list, priv) + end + end + return table.concat(list, delim) +end + +assert(minetest.string_to_privs("a,b").b == true) +assert(minetest.privs_to_string({a=true,b=true}) == "a,b") + +minetest.auth_file_path = minetest.get_worldpath().."/auth.txt" +minetest.auth_table = {} + +local function read_auth_file() + local newtable = {} + local file, errmsg = io.open(minetest.auth_file_path, 'rb') + if not file then + minetest.log("info", minetest.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 name, password, privilegestring = string.match(line, "([^:]*):([^:]*):([^:]*)") + if not name or not password or not privilegestring then + error("Invalid line in auth.txt: "..dump(line)) + end + local privileges = minetest.string_to_privs(privilegestring) + newtable[name] = {password=password, privileges=privileges} + end + end + io.close(file) + minetest.auth_table = newtable + minetest.notify_authentication_modified() +end + +local function save_auth_file() + local newtable = {} + -- Check table for validness before attempting to save + for name, stuff in pairs(minetest.auth_table) do + assert(type(name) == "string") + assert(name ~= "") + assert(type(stuff) == "table") + assert(type(stuff.password) == "string") + assert(type(stuff.privileges) == "table") + end + local file, errmsg = io.open(minetest.auth_file_path, 'w+b') + if not file then + error(minetest.auth_file_path.." could not be opened for writing: "..errmsg) + end + for name, stuff in pairs(minetest.auth_table) do + local privstring = minetest.privs_to_string(stuff.privileges) + file:write(name..":"..stuff.password..":"..privstring..'\n') + end + io.close(file) +end + +read_auth_file() + +minetest.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 minetest.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(minetest.auth_table[name].privileges) do + privileges[priv] = true + end + -- If singleplayer, give all privileges except those marked as give_to_singleplayer = false + if minetest.is_singleplayer() then + for priv, def in pairs(minetest.registered_privileges) do + if def.give_to_singleplayer then + privileges[priv] = true + end + end + -- For the admin, give everything + elseif name == minetest.setting_get("name") then + for priv, def in pairs(minetest.registered_privileges) do + privileges[priv] = true + end + end + -- All done + return { + password = minetest.auth_table[name].password, + privileges = privileges, + } + end, + create_auth = function(name, password) + assert(type(name) == "string") + assert(type(password) == "string") + minetest.log('info', "Built-in authentication handler adding player '"..name.."'") + minetest.auth_table[name] = { + password = password, + privileges = minetest.string_to_privs(minetest.setting_get("default_privs")), + } + save_auth_file() + end, + set_password = function(name, password) + assert(type(name) == "string") + assert(type(password) == "string") + if not minetest.auth_table[name] then + minetest.builtin_auth_handler.create_auth(name, password) + else + minetest.log('info', "Built-in authentication handler setting password of player '"..name.."'") + minetest.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 minetest.auth_table[name] then + minetest.builtin_auth_handler.create_auth(name, minetest.get_password_hash(name, minetest.setting_get("default_password"))) + end + minetest.auth_table[name].privileges = privileges + minetest.notify_authentication_modified(name) + save_auth_file() + end, + reload = function() + read_auth_file() + return true + end, +} + +function minetest.register_authentication_handler(handler) + if minetest.registered_auth_handler then + error("Add-on authentication handler already registered by "..minetest.registered_auth_handler_modname) + end + minetest.registered_auth_handler = handler + minetest.registered_auth_handler_modname = minetest.get_current_modname() +end + +function minetest.get_auth_handler() + if minetest.registered_auth_handler then + return minetest.registered_auth_handler + end + return minetest.builtin_auth_handler +end + +function minetest.set_player_password(name, password) + if minetest.get_auth_handler().set_password then + minetest.get_auth_handler().set_password(name, password) + end +end + +function minetest.set_player_privs(name, privs) + if minetest.get_auth_handler().set_privileges then + minetest.get_auth_handler().set_privileges(name, privs) + end +end + +function minetest.auth_reload() + if minetest.get_auth_handler().reload then + return minetest.get_auth_handler().reload() + end + return false +end + + diff --git a/builtin/builtin.lua b/builtin/builtin.lua new file mode 100644 index 0000000..99242f3 --- /dev/null +++ b/builtin/builtin.lua @@ -0,0 +1,36 @@ +-- +-- 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 +print = minetest.debug +math.randomseed(os.time()) +os.setlocale("C", "numeric") + +local errorfct = error +error = function(text) + print(debug.traceback("")) + errorfct(text) +end + +-- Load other files +local modpath = minetest.get_modpath("__builtin") +dofile(modpath.."/serialize.lua") +dofile(modpath.."/misc_helpers.lua") +dofile(modpath.."/item.lua") +dofile(modpath.."/misc_register.lua") +dofile(modpath.."/item_entity.lua") +dofile(modpath.."/deprecated.lua") +dofile(modpath.."/misc.lua") +dofile(modpath.."/privileges.lua") +dofile(modpath.."/auth.lua") +dofile(modpath.."/chatcommands.lua") +dofile(modpath.."/static_spawn.lua") +dofile(modpath.."/detached_inventory.lua") +dofile(modpath.."/falling.lua") +dofile(modpath.."/features.lua") +dofile(modpath.."/voxelarea.lua") +dofile(modpath.."/vector.lua") diff --git a/builtin/chatcommands.lua b/builtin/chatcommands.lua new file mode 100644 index 0000000..831d3a7 --- /dev/null +++ b/builtin/chatcommands.lua @@ -0,0 +1,693 @@ +-- Minetest: builtin/chatcommands.lua + +-- +-- Chat command handler +-- + +minetest.chatcommands = {} +function minetest.register_chatcommand(cmd, def) + def = def or {} + def.params = def.params or "" + def.description = def.description or "" + def.privs = def.privs or {} + minetest.chatcommands[cmd] = def +end + +minetest.register_on_chat_message(function(name, message) + local cmd, param = string.match(message, "^/([^ ]+) *(.*)") + if not param then + param = "" + end + local cmd_def = minetest.chatcommands[cmd] + if cmd_def then + local has_privs, missing_privs = minetest.check_player_privs(name, cmd_def.privs) + if has_privs then + cmd_def.func(name, param) + else + minetest.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 + return false +end) + +-- +-- Chat commands +-- +minetest.register_chatcommand("me", { + params = "", + description = "chat action (eg. /me orders a pizza)", + privs = {shout=true}, + func = function(name, param) + minetest.chat_send_all("* " .. name .. " " .. param) + end, +}) + +minetest.register_chatcommand("help", { + privs = {}, + params = "(nothing)/all/privs/", + description = "Get help for commands or list privileges", + func = function(name, param) + local format_help_line = function(cmd, def) + local msg = "/"..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 = "" + cmds = {} + for cmd, def in pairs(minetest.chatcommands) do + if minetest.check_player_privs(name, def.privs) then + table.insert(cmds, cmd) + end + end + minetest.chat_send_player(name, "Available commands: "..table.concat(cmds, " ")) + minetest.chat_send_player(name, "Use '/help ' to get more information, or '/help all' to list everything.") + elseif param == "all" then + minetest.chat_send_player(name, "Available commands:") + for cmd, def in pairs(minetest.chatcommands) do + if minetest.check_player_privs(name, def.privs) then + minetest.chat_send_player(name, format_help_line(cmd, def)) + end + end + elseif param == "privs" then + minetest.chat_send_player(name, "Available privileges:") + for priv, def in pairs(minetest.registered_privileges) do + minetest.chat_send_player(name, priv..": "..def.description) + end + else + local cmd = param + def = minetest.chatcommands[cmd] + if not def then + minetest.chat_send_player(name, "Command not available: "..cmd) + else + minetest.chat_send_player(name, format_help_line(cmd, def)) + end + end + end, +}) +minetest.register_chatcommand("privs", { + params = "", + description = "print out privileges of player", + func = function(name, param) + if param == "" then + param = name + else + --[[if not minetest.check_player_privs(name, {privs=true}) then + minetest.chat_send_player(name, "Privileges of "..param.." are hidden from you.") + return + end]] + end + minetest.chat_send_player(name, "Privileges of "..param..": "..minetest.privs_to_string(minetest.get_player_privs(param), ' ')) + end, +}) +minetest.register_chatcommand("grant", { + params = " |all", + description = "Give privilege to player", + privs = {}, + func = function(name, param) + if not minetest.check_player_privs(name, {privs=true}) and + not minetest.check_player_privs(name, {basic_privs=true}) then + minetest.chat_send_player(name, "Your privileges are insufficient.") + return + end + local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)") + if not grantname or not grantprivstr then + minetest.chat_send_player(name, "Invalid parameters (see /help grant)") + return + elseif not minetest.auth_table[grantname] then + minetest.chat_send_player(name, "Player "..grantname.." does not exist.") + return + end + local grantprivs = minetest.string_to_privs(grantprivstr) + if grantprivstr == "all" then + grantprivs = minetest.registered_privileges + end + local privs = minetest.get_player_privs(grantname) + local privs_known = true + for priv, _ in pairs(grantprivs) do + if priv ~= "interact" and priv ~= "shout" and priv ~= "interact_extra" and not minetest.check_player_privs(name, {privs=true}) then + minetest.chat_send_player(name, "Your privileges are insufficient.") + return + end + if not minetest.registered_privileges[priv] then + minetest.chat_send_player(name, "Unknown privilege: "..priv) + privs_known = false + end + privs[priv] = true + end + if not privs_known then + return + end + minetest.set_player_privs(grantname, privs) + minetest.log(name..' granted ('..minetest.privs_to_string(grantprivs, ', ')..') privileges to '..grantname) + minetest.chat_send_player(name, "Privileges of "..grantname..": "..minetest.privs_to_string(minetest.get_player_privs(grantname), ' ')) + if grantname ~= name then + minetest.chat_send_player(grantname, name.." granted you privileges: "..minetest.privs_to_string(grantprivs, ' ')) + end + end, +}) +minetest.register_chatcommand("revoke", { + params = " |all", + description = "Remove privilege from player", + privs = {}, + func = function(name, param) + if not minetest.check_player_privs(name, {privs=true}) and + not minetest.check_player_privs(name, {basic_privs=true}) then + minetest.chat_send_player(name, "Your privileges are insufficient.") + return + end + local revokename, revokeprivstr = string.match(param, "([^ ]+) (.+)") + if not revokename or not revokeprivstr then + minetest.chat_send_player(name, "Invalid parameters (see /help revoke)") + return + elseif not minetest.auth_table[revokename] then + minetest.chat_send_player(name, "Player "..revokename.." does not exist.") + return + end + local revokeprivs = minetest.string_to_privs(revokeprivstr) + local privs = minetest.get_player_privs(revokename) + for priv, _ in pairs(revokeprivs) do + if priv ~= "interact" and priv ~= "shout" and priv ~= "interact_extra" and not minetest.check_player_privs(name, {privs=true}) then + minetest.chat_send_player(name, "Your privileges are insufficient.") + return + end + end + if revokeprivstr == "all" then + privs = {} + else + for priv, _ in pairs(revokeprivs) do + privs[priv] = nil + end + end + minetest.set_player_privs(revokename, privs) + minetest.log(name..' revoked ('..minetest.privs_to_string(revokeprivs, ', ')..') privileges from '..revokename) + minetest.chat_send_player(name, "Privileges of "..revokename..": "..minetest.privs_to_string(minetest.get_player_privs(revokename), ' ')) + if revokename ~= name then + minetest.chat_send_player(revokename, name.." revoked privileges from you: "..minetest.privs_to_string(revokeprivs, ' ')) + end + end, +}) +minetest.register_chatcommand("setpassword", { + params = " ", + description = "set given password", + privs = {password=true}, + func = function(name, param) + local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$") + if not toname then + toname = string.match(param, "^([^ ]+) *$") + raw_password = nil + end + if not toname then + minetest.chat_send_player(name, "Name field required") + return + end + local actstr = "?" + if not raw_password then + minetest.set_player_password(toname, "") + actstr = "cleared" + else + minetest.set_player_password(toname, minetest.get_password_hash(toname, raw_password)) + actstr = "set" + end + minetest.chat_send_player(name, "Password of player \""..toname.."\" "..actstr) + if toname ~= name then + minetest.chat_send_player(toname, "Your password was "..actstr.." by "..name) + end + end, +}) +minetest.register_chatcommand("clearpassword", { + params = "", + description = "set empty password", + privs = {password=true}, + func = function(name, param) + toname = param + if toname == "" then + minetest.chat_send_player(name, "Name field required") + return + end + minetest.set_player_password(toname, '') + minetest.chat_send_player(name, "Password of player \""..toname.."\" cleared") + end, +}) + +minetest.register_chatcommand("auth_reload", { + params = "", + description = "reload authentication data", + privs = {server=true}, + func = function(name, param) + local done = minetest.auth_reload() + if done then + minetest.chat_send_player(name, "Done.") + else + minetest.chat_send_player(name, "Failed.") + end + end, +}) + +minetest.register_chatcommand("teleport", { + params = ",, | | ,, | ", + description = "teleport to given 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 = minetest.get_node(p) + if not minetest.registered_nodes[n.name].walkable then + return p, true + 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) + teleportee = minetest.get_player_by_name(name) + if teleportee and p.x and p.y and p.z then + minetest.chat_send_player(name, "Teleporting to ("..p.x..", "..p.y..", "..p.z..")") + teleportee:setpos(p) + return + end + + local teleportee = nil + local p = nil + local target_name = nil + target_name = string.match(param, "^([^ ]+)$") + teleportee = minetest.get_player_by_name(name) + if target_name then + local target = minetest.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) + minetest.chat_send_player(name, "Teleporting to "..target_name.." at ("..p.x..", "..p.y..", "..p.z..")") + teleportee:setpos(p) + return + end + + if minetest.check_player_privs(name, {bring=true}) then + local teleportee = nil + local p = {} + local teleportee_name = nil + teleportee_name, 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 teleportee_name then + teleportee = minetest.get_player_by_name(teleportee_name) + end + if teleportee and p.x and p.y and p.z then + minetest.chat_send_player(name, "Teleporting "..teleportee_name.." to ("..p.x..", "..p.y..", "..p.z..")") + teleportee:setpos(p) + return + 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 = minetest.get_player_by_name(teleportee_name) + end + if target_name then + local target = minetest.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) + minetest.chat_send_player(name, "Teleporting "..teleportee_name.." to "..target_name.." at ("..p.x..", "..p.y..", "..p.z..")") + teleportee:setpos(p) + return + end + end + + minetest.chat_send_player(name, "Invalid parameters (\""..param.."\") or player not found (see /help teleport)") + return + end, +}) + +minetest.register_chatcommand("set", { + params = "[-n] | ", + 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 + minetest.setting_set(setname, setvalue) + minetest.chat_send_player(name, setname.." = "..setvalue) + return + end + local setname, setvalue = string.match(param, "([^ ]+) (.+)") + if setname and setvalue then + if not minetest.setting_get(setname) then + minetest.chat_send_player(name, "Failed. Use '/set -n ' to create a new setting.") + return + end + minetest.setting_set(setname, setvalue) + minetest.chat_send_player(name, setname.." = "..setvalue) + return + end + local setname = string.match(param, "([^ ]+)") + if setname then + local setvalue = minetest.setting_get(setname) + if not setvalue then + setvalue = "" + end + minetest.chat_send_player(name, setname.." = "..setvalue) + return + end + minetest.chat_send_player(name, "Invalid parameters (see /help set)") + end, +}) + +minetest.register_chatcommand("mods", { + params = "", + description = "lists mods installed on the server", + privs = {}, + func = function(name, param) + local response = "" + local modnames = minetest.get_modnames() + for i, mod in ipairs(modnames) do + response = response .. mod + -- Add space if not at the end + if i ~= #modnames then + response = response .. " " + end + end + minetest.chat_send_player(name, response) + end, +}) + +local function handle_give_command(cmd, giver, receiver, stackstring) + minetest.log("action", giver.." invoked "..cmd..', stackstring="' + ..stackstring..'"') + minetest.log(cmd..' invoked, stackstring="'..stackstring..'"') + local itemstack = ItemStack(stackstring) + if itemstack:is_empty() then + minetest.chat_send_player(giver, 'error: cannot give an empty item') + return + elseif not itemstack:is_known() then + minetest.chat_send_player(giver, 'error: cannot give an unknown item') + return + end + local receiverref = minetest.get_player_by_name(receiver) + if receiverref == nil then + minetest.chat_send_player(giver, receiver..' is not a known player') + return + end + local leftover = receiverref:get_inventory():add_item("main", itemstack) + 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 + minetest.chat_send_player(giver, '"'..stackstring + ..'" '..partiality..'added to inventory.'); + else + minetest.chat_send_player(giver, '"'..stackstring + ..'" '..partiality..'added to '..receiver..'\'s inventory.'); + minetest.chat_send_player(receiver, '"'..stackstring + ..'" '..partiality..'added to inventory.'); + end +end + +minetest.register_chatcommand("give", { + params = " ", + 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 + minetest.chat_send_player(name, "name and itemstring required") + return + end + handle_give_command("/give", name, toname, itemstring) + end, +}) +minetest.register_chatcommand("giveme", { + params = "", + description = "give item to yourself", + privs = {give=true}, + func = function(name, param) + local itemstring = string.match(param, "(.+)$") + if not itemstring then + minetest.chat_send_player(name, "itemstring required") + return + end + handle_give_command("/giveme", name, name, itemstring) + end, +}) +minetest.register_chatcommand("spawnentity", { + params = "", + description = "spawn entity at your position", + privs = {give=true, interact=true}, + func = function(name, param) + local entityname = string.match(param, "(.+)$") + if not entityname then + minetest.chat_send_player(name, "entityname required") + return + end + minetest.log("action", '/spawnentity invoked, entityname="'..entityname..'"') + local player = minetest.get_player_by_name(name) + if player == nil then + minetest.log("error", "Unable to spawn entity, player is nil") + return true -- Handled chat message + end + local p = player:getpos() + p.y = p.y + 1 + minetest.add_entity(p, entityname) + minetest.chat_send_player(name, '"'..entityname + ..'" spawned.'); + end, +}) +minetest.register_chatcommand("pulverize", { + params = "", + description = "delete item in hand", + privs = {}, + func = function(name, param) + local player = minetest.get_player_by_name(name) + if player == nil then + minetest.log("error", "Unable to pulverize, player is nil") + return true -- Handled chat message + end + if player:get_wielded_item():is_empty() then + minetest.chat_send_player(name, 'Unable to pulverize, no item in hand.') + else + player:set_wielded_item(nil) + minetest.chat_send_player(name, 'An item was pulverized.') + end + end, +}) + +-- Key = player name +minetest.rollback_punch_callbacks = {} + +minetest.register_on_punchnode(function(pos, node, puncher) + local name = puncher:get_player_name() + if minetest.rollback_punch_callbacks[name] then + minetest.rollback_punch_callbacks[name](pos, node, puncher) + minetest.rollback_punch_callbacks[name] = nil + end +end) + +minetest.register_chatcommand("rollback_check", { + params = "[] []", + description = "check who has last touched a node or near it, ".. + "max. ago (default range=0, seconds=86400=24h)", + privs = {rollback=true}, + func = function(name, param) + local range, seconds = string.match(param, "(%d+) *(%d*)") + range = tonumber(range) or 0 + seconds = tonumber(seconds) or 86400 + minetest.chat_send_player(name, "Punch a node (limits set: range=".. + dump(range).." seconds="..dump(seconds).."s)") + minetest.rollback_punch_callbacks[name] = function(pos, node, puncher) + local name = puncher:get_player_name() + minetest.chat_send_player(name, "Checking...") + local actor, act_p, act_seconds = + minetest.rollback_get_last_node_actor(pos, range, seconds) + if actor == "" then + minetest.chat_send_player(name, "Nobody has touched the ".. + "specified location in "..dump(seconds).." seconds") + return + end + local nodedesc = "this node" + if act_p.x ~= pos.x or act_p.y ~= pos.y or act_p.z ~= pos.z then + nodedesc = minetest.pos_to_string(act_p) + end + local nodename = minetest.get_node(act_p).name + minetest.chat_send_player(name, "Last actor on "..nodedesc.. + " was "..actor..", "..dump(act_seconds).. + "s ago (node is now "..nodename..")") + end + end, +}) + +minetest.register_chatcommand("rollback", { + params = " [] | : []", + description = "revert actions of a player; default for is 60", + privs = {rollback=true}, + func = function(name, param) + 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 + minetest.chat_send_player(name, "Invalid parameters. See /help rollback and /help rollback_check") + return + end + target_name = "player:"..player_name + end + seconds = tonumber(seconds) or 60 + minetest.chat_send_player(name, "Reverting actions of ".. + dump(target_name).." since "..dump(seconds).." seconds.") + local success, log = minetest.rollback_revert_actions_by( + target_name, seconds) + if #log > 10 then + minetest.chat_send_player(name, "(log is too long to show)") + else + for _,line in ipairs(log) do + minetest.chat_send_player(name, line) + end + end + if success then + minetest.chat_send_player(name, "Reverting actions succeeded.") + else + minetest.chat_send_player(name, "Reverting actions FAILED.") + end + end, +}) + +minetest.register_chatcommand("status", { + params = "", + description = "print server status line", + privs = {}, + func = function(name, param) + minetest.chat_send_player(name, minetest.get_server_status()) + end, +}) + +minetest.register_chatcommand("time", { + params = "<0...24000>", + description = "set time of day", + privs = {settime=true}, + func = function(name, param) + if param == "" then + minetest.chat_send_player(name, "Missing parameter") + return + end + local newtime = tonumber(param) + if newtime == nil then + minetest.chat_send_player(name, "Invalid time") + else + minetest.set_timeofday((newtime % 24000) / 24000) + minetest.chat_send_player(name, "Time of day changed.") + minetest.log("action", name .. " sets time " .. newtime) + end + end, +}) + +minetest.register_chatcommand("shutdown", { + params = "", + description = "shutdown server", + privs = {server=true}, + func = function(name, param) + minetest.log("action", name .. " shuts down server") + minetest.request_shutdown() + minetest.chat_send_all("*** Server shutting down (operator request).") + end, +}) + +minetest.register_chatcommand("ban", { + params = "", + description = "ban IP of player", + privs = {ban=true}, + func = function(name, param) + if param == "" then + minetest.chat_send_player(name, "Ban list: " .. minetest.get_ban_list()) + return + end + if not minetest.get_player_by_name(param) then + minetest.chat_send_player(name, "No such player") + return + end + if not minetest.ban_player(param) then + minetest.chat_send_player(name, "Failed to ban player") + else + local desc = minetest.get_ban_description(param) + minetest.chat_send_player(name, "Banned " .. desc .. ".") + minetest.log("action", name .. " bans " .. desc .. ".") + end + end, +}) + +minetest.register_chatcommand("unban", { + params = "", + description = "remove IP ban", + privs = {ban=true}, + func = function(name, param) + if not minetest.unban_player_or_ip(param) then + minetest.chat_send_player(name, "Failed to unban player/IP") + else + minetest.chat_send_player(name, "Unbanned " .. param) + minetest.log("action", name .. " unbans " .. param) + end + end, +}) + +minetest.register_chatcommand("clearobjects", { + params = "", + description = "clear all objects in world", + privs = {server=true}, + func = function(name, param) + minetest.log("action", name .. " clears all objects") + minetest.chat_send_all("Clearing all objects. This may take long. You may experience a timeout. (by " .. name .. ")") + minetest.clear_objects() + minetest.log("action", "object clearing done") + minetest.chat_send_all("*** Cleared all objects.") + end, +}) + +minetest.register_chatcommand("msg", { + params = " ", + description = "Send a private message", + privs = {shout=true}, + func = function(name, param) + local found, _, sendto, message = param:find("^([^%s]+)%s(.+)$") + if found then + if minetest.get_player_by_name(sendto) then + minetest.log("action", "PM from "..name.." to "..sendto..": "..message) + minetest.chat_send_player(sendto, "PM from "..name..": "..message, false) + minetest.chat_send_player(name, "Message sent") + else + minetest.chat_send_player(name, "The player "..sendto.." is not online") + end + else + minetest.chat_send_player(name, "Invalid usage, see /help msg") + end + end, +}) diff --git a/builtin/deprecated.lua b/builtin/deprecated.lua new file mode 100644 index 0000000..333f64c --- /dev/null +++ b/builtin/deprecated.lua @@ -0,0 +1,48 @@ +-- Minetest: builtin/deprecated.lua + +-- +-- Default material types +-- +function digprop_err() + minetest.log("info", debug.traceback()) + minetest.log("info", "WARNING: The minetest.digprop_* functions are obsolete and need to be replaced by item groups.") +end + +minetest.digprop_constanttime = digprop_err +minetest.digprop_stonelike = digprop_err +minetest.digprop_dirtlike = digprop_err +minetest.digprop_gravellike = digprop_err +minetest.digprop_woodlike = digprop_err +minetest.digprop_leaveslike = digprop_err +minetest.digprop_glasslike = digprop_err + +minetest.node_metadata_inventory_move_allow_all = function() + minetest.log("info", "WARNING: minetest.node_metadata_inventory_move_allow_all is obsolete and does nothing.") +end + +minetest.add_to_creative_inventory = function(itemstring) + minetest.log('info', "WARNING: minetest.add_to_creative_inventory: This function is deprecated and does nothing.") +end + +-- +-- EnvRef +-- +minetest.env = {} +local envref_deprecation_message_printed = false +setmetatable(minetest.env, { + __index = function(table, key) + if not envref_deprecation_message_printed then + minetest.log("info", "WARNING: minetest.env:[...] is deprecated and should be replaced with minetest.[...]") + envref_deprecation_message_printed = true + end + local func = minetest[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 +}) diff --git a/builtin/detached_inventory.lua b/builtin/detached_inventory.lua new file mode 100644 index 0000000..3757f13 --- /dev/null +++ b/builtin/detached_inventory.lua @@ -0,0 +1,19 @@ +-- Minetest: builtin/detached_inventory.lua + +minetest.detached_inventories = {} + +function minetest.create_detached_inventory(name, callbacks) + 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 + minetest.detached_inventories[name] = stuff + return minetest.create_detached_inventory_raw(name) +end + diff --git a/builtin/falling.lua b/builtin/falling.lua new file mode 100644 index 0000000..c8f3bc5 --- /dev/null +++ b/builtin/falling.lua @@ -0,0 +1,217 @@ +-- Minetest: builtin/item.lua + +-- +-- Falling stuff +-- + +minetest.register_entity("__builtin:falling_node", { + initial_properties = { + physical = true, + collide_with_objects = false, + collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5}, + visual = "wielditem", + textures = {}, + visual_size = {x=0.667, y=0.667}, + }, + + node = {}, + + set_node = function(self, node) + self.node = node + local stack = ItemStack(node.name) + 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 minetest.registered_items[itemname] then + item_texture = minetest.registered_items[itemname].inventory_image + item_type = minetest.registered_items[itemname].type + end + prop = { + is_visible = true, + textures = {node.name}, + } + self.object:set_properties(prop) + end, + + get_staticdata = function(self) + return self.node.name + end, + + on_activate = function(self, staticdata) + self.object:set_armor_groups({immortal=1}) + --self.object:setacceleration({x=0, y=-10, z=0}) + self:set_node({name=staticdata}) + end, + + on_step = function(self, dtime) + -- Set gravity + self.object:setacceleration({x=0, y=-10, z=0}) + -- Turn to actual sand when collides to ground or just move + local pos = self.object:getpos() + local bcp = {x=pos.x, y=pos.y-0.7, z=pos.z} -- Position of bottom center point + local bcn = minetest.get_node(bcp) + local bcd = minetest.registered_nodes[bcn.name] + -- Note: walkable is in the node definition, not in item groups + if not bcd or + (bcd.walkable or + (minetest.get_node_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 addlevel == nil or addlevel <= 0 then + addlevel = bcd.leveled + end + if minetest.add_node_level(bcp, addlevel) == 0 then + self.object:remove() + return + end + elseif bcd and bcd.buildable_to and + (minetest.get_node_group(self.node.name, "float") == 0 or + bcd.liquidtype == "none") then + minetest.remove_node(bcp) + return + end + local np = {x=bcp.x, y=bcp.y+1, z=bcp.z} + -- Check what's here + local n2 = minetest.get_node(np) + -- If it's not air or liquid, remove node and replace it with + -- it's drops + if n2.name ~= "air" and (not minetest.registered_nodes[n2.name] or + minetest.registered_nodes[n2.name].liquidtype == "none") then + local drops = minetest.get_node_drops(n2.name, "") + minetest.remove_node(np) + -- Add dropped items + local _, dropped_item + for _, dropped_item in ipairs(drops) do + minetest.add_item(np, dropped_item) + end + -- Run script hook + local _, callback + for _, callback in ipairs(minetest.registered_on_dignodes) do + callback(np, n2, nil) + end + end + -- Create node and remove entity + minetest.add_node(np, self.node) + self.object:remove() + nodeupdate(np) + else + -- Do nothing + end + end +}) + +function spawn_falling_node(p, node) + obj = minetest.add_entity(p, "__builtin:falling_node") + obj:get_luaentity():set_node(node) +end + +function drop_attached_node(p) + local nn = minetest.get_node(p).name + minetest.remove_node(p) + for _,item in ipairs(minetest.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, + } + minetest.add_item(pos, item) + end +end + +function check_attached_node(p, n) + local def = minetest.registered_nodes[n.name] + local d = {x=0, y=0, z=0} + if def.paramtype2 == "wallmounted" then + if n.param2 == 0 then + d.y = 1 + elseif n.param2 == 1 then + d.y = -1 + elseif n.param2 == 2 then + d.x = 1 + elseif n.param2 == 3 then + d.x = -1 + elseif n.param2 == 4 then + d.z = 1 + elseif n.param2 == 5 then + d.z = -1 + end + else + d.y = -1 + end + local p2 = {x=p.x+d.x, y=p.y+d.y, z=p.z+d.z} + local nn = minetest.get_node(p2).name + local def2 = minetest.registered_nodes[nn] + if def2 and not def2.walkable then + return false + end + return true +end + +-- +-- Some common functions +-- + +function nodeupdate_single(p, delay) + n = minetest.get_node(p) + if minetest.get_node_group(n.name, "falling_node") ~= 0 then + p_bottom = {x=p.x, y=p.y-1, z=p.z} + n_bottom = minetest.get_node(p_bottom) + -- Note: walkable is in the node definition, not in item groups + if minetest.registered_nodes[n_bottom.name] and + (minetest.get_node_group(n.name, "float") == 0 or minetest.registered_nodes[n_bottom.name].liquidtype == "none") and + (n.name ~= n_bottom.name or (minetest.registered_nodes[n_bottom.name].leveled and minetest.env:get_node_level(p_bottom) < minetest.env:get_node_max_level(p_bottom))) and + (not minetest.registered_nodes[n_bottom.name].walkable or + minetest.registered_nodes[n_bottom.name].buildable_to) then + if delay then + minetest.after(0.1, nodeupdate_single, {x=p.x, y=p.y, z=p.z}, false) + else + n.level = minetest.env:get_node_level(p) + minetest.remove_node(p) + spawn_falling_node(p, n) + nodeupdate(p) + end + end + end + + if minetest.get_node_group(n.name, "attached_node") ~= 0 then + if not check_attached_node(p, n) then + drop_attached_node(p) + nodeupdate(p) + end + end +end + +function nodeupdate(p, delay) + -- Round p to prevent falling entities to get stuck + p.x = math.floor(p.x+0.5) + p.y = math.floor(p.y+0.5) + p.z = math.floor(p.z+0.5) + + for x = -1,1 do + for y = -1,1 do + for z = -1,1 do + nodeupdate_single({x=p.x+x, y=p.y+y, z=p.z+z}, delay or not (x==0 and y==0 and z==0)) + end + end + end +end + +-- +-- Global callbacks +-- + +function on_placenode(p, node) + nodeupdate(p) +end +minetest.register_on_placenode(on_placenode) + +function on_dignode(p, node) + nodeupdate(p) +end +minetest.register_on_dignode(on_dignode) diff --git a/builtin/features.lua b/builtin/features.lua new file mode 100644 index 0000000..f3de3ba --- /dev/null +++ b/builtin/features.lua @@ -0,0 +1,29 @@ +-- Minetest: builtin/features.lua + +minetest.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, +} + +function minetest.has_feature(arg) + if type(arg) == "table" then + missing_features = {} + result = true + for ft, _ in pairs(arg) do + if not minetest.features[ftr] then + missing_features[ftr] = true + result = false + end + end + return result, missing_features + elseif type(arg) == "string" then + if not minetest.features[arg] then + return false, {[arg]=true} + end + return true, {} + end +end diff --git a/builtin/filterlist.lua b/builtin/filterlist.lua new file mode 100644 index 0000000..1c2ceb0 --- /dev/null +++ b/builtin/filterlist.lua @@ -0,0 +1,287 @@ +--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. + +-------------------------------------------------------------------------------- +-- Generic implementation of a filter/sortable list -- +-------------------------------------------------------------------------------- +filterlist = {} + +-------------------------------------------------------------------------------- +function filterlist.refresh(this) + this.m_raw_list = this.m_raw_list_fct(this.m_fetch_param) + filterlist.process(this) +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 this = {} + + this.m_raw_list_fct = raw_fct + this.m_compare_fct = compare_fct + this.m_filter_fct = filter_fct + this.m_uid_match_fct = uid_match_fct + + this.m_filtercriteria = nil + this.m_fetch_param = fetch_param + + this.m_sortmode = "none" + this.m_sort_list = {} + + this.m_processed_list = nil + this.m_raw_list = this.m_raw_list_fct(this.m_fetch_param) + + filterlist.process(this) + + return this +end + +-------------------------------------------------------------------------------- +function filterlist.add_sort_mechanism(this,name,fct) + this.m_sort_list[name] = fct +end + +-------------------------------------------------------------------------------- +function filterlist.set_filtercriteria(this,criteria) + if criteria == this.m_filtercriteria and + type(criteria) ~= "table" then + return + end + this.m_filtercriteria = criteria + filterlist.process(this) +end + +-------------------------------------------------------------------------------- +function filterlist.get_filtercriteria(this) + return this.m_filtercriteria +end + +-------------------------------------------------------------------------------- +--supported sort mode "alphabetic|none" +function filterlist.set_sortmode(this,mode) + if (mode == this.m_sortmode) then + return + end + this.m_sortmode = mode + filterlist.process(this) +end + +-------------------------------------------------------------------------------- +function filterlist.get_list(this) + return this.m_processed_list +end + +-------------------------------------------------------------------------------- +function filterlist.get_raw_list(this) + return this.m_raw_list +end + +-------------------------------------------------------------------------------- +function filterlist.get_raw_element(this,idx) + if type(idx) ~= "number" then + idx = tonumber(idx) + end + + if idx ~= nil and idx > 0 and idx < #this.m_raw_list then + return this.m_raw_list[idx] + end + + return nil +end + +-------------------------------------------------------------------------------- +function filterlist.get_raw_index(this,listindex) + assert(this.m_processed_list ~= nil) + + if listindex ~= nil and listindex > 0 and + listindex <= #this.m_processed_list then + local entry = this.m_processed_list[listindex] + + for i,v in ipairs(this.m_raw_list) do + + if this.m_compare_fct(v,entry) then + return i + end + end + end + + return 0 +end + +-------------------------------------------------------------------------------- +function filterlist.get_current_index(this,listindex) + assert(this.m_processed_list ~= nil) + + if listindex ~= nil and listindex > 0 and + listindex <= #this.m_raw_list then + local entry = this.m_raw_list[listindex] + + for i,v in ipairs(this.m_processed_list) do + + if this.m_compare_fct(v,entry) then + return i + end + end + end + + return 0 +end + +-------------------------------------------------------------------------------- +function filterlist.process(this) + assert(this.m_raw_list ~= nil) + + if this.m_sortmode == "none" and + this.m_filtercriteria == nil then + this.m_processed_list = this.m_raw_list + return + end + + this.m_processed_list = {} + + for k,v in pairs(this.m_raw_list) do + if this.m_filtercriteria == nil or + this.m_filter_fct(v,this.m_filtercriteria) then + table.insert(this.m_processed_list,v) + end + end + + if this.m_sortmode == "none" then + return + end + + if this.m_sort_list[this.m_sortmode] ~= nil and + type(this.m_sort_list[this.m_sortmode]) == "function" then + + this.m_sort_list[this.m_sortmode](this) + end +end + +-------------------------------------------------------------------------------- +function filterlist.size(this) + if this.m_processed_list == nil then + return 0 + end + + return #this.m_processed_list +end + +-------------------------------------------------------------------------------- +function filterlist.uid_exists_raw(this,uid) + for i,v in ipairs(this.m_raw_list) do + if this.m_uid_match_fct(v,uid) then + return true + end + end + return false +end + +-------------------------------------------------------------------------------- +function filterlist.raw_index_by_uid(this, uid) + local elementcount = 0 + local elementidx = 0 + for i,v in ipairs(this.m_raw_list) do + if this.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. This 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(this) + + table.sort(this.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(this) + + table.sort(this.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 diff --git a/builtin/gamemgr.lua b/builtin/gamemgr.lua new file mode 100644 index 0000000..7a5e979 --- /dev/null +++ b/builtin/gamemgr.lua @@ -0,0 +1,322 @@ +--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.dialog_new_game() + local retval = + "label[2,2;" .. fgettext("Game Name") .. "]".. + "field[4.5,2.4;6,0.5;te_game_name;;]" .. + "button[5,4.2;2.6,0.5;new_game_confirm;" .. fgettext("Create") .. "]" .. + "button[7.5,4.2;2.8,0.5;new_game_cancel;" .. fgettext("Cancel") .. "]" + + return retval +end + +-------------------------------------------------------------------------------- +function gamemgr.handle_games_buttons(fields) + if fields["gamelist"] ~= nil then + local event = explode_textlist_event(fields["gamelist"]) + gamemgr.selected_game = event.index + end + + if fields["btn_game_mgr_edit_game"] ~= nil then + return { + is_dialog = true, + show_buttons = false, + current_tab = "dialog_edit_game" + } + end + + if fields["btn_game_mgr_new_game"] ~= nil then + return { + is_dialog = true, + show_buttons = false, + current_tab = "dialog_new_game" + } + end + + return nil +end + +-------------------------------------------------------------------------------- +function gamemgr.handle_new_game_buttons(fields) + + if fields["new_game_confirm"] and + fields["te_game_name"] ~= nil and + fields["te_game_name"] ~= "" then + local gamepath = engine.get_gamepath() + + if gamepath ~= nil and + gamepath ~= "" then + local gamefolder = cleanup_path(fields["te_game_name"]) + + --TODO check for already existing first + engine.create_dir(gamepath .. DIR_DELIM .. gamefolder) + engine.create_dir(gamepath .. DIR_DELIM .. gamefolder .. DIR_DELIM .. "mods") + engine.create_dir(gamepath .. DIR_DELIM .. gamefolder .. DIR_DELIM .. "menu") + + local gameconf = + io.open(gamepath .. DIR_DELIM .. gamefolder .. DIR_DELIM .. "game.conf","w") + + if gameconf then + gameconf:write("name = " .. fields["te_game_name"]) + gameconf:close() + end + end + end + + return { + is_dialog = false, + show_buttons = true, + current_tab = engine.setting_get("main_menu_tab") + } +end + +-------------------------------------------------------------------------------- +function gamemgr.handle_edit_game_buttons(fields) + local current_game = gamemgr.get_game(gamemgr.selected_game) + + if fields["btn_close_edit_game"] ~= nil or + current_game == nil then + return { + is_dialog = false, + show_buttons = true, + current_tab = engine.setting_get("main_menu_tab") + } + end + + if fields["btn_remove_mod_from_game"] ~= nil then + gamemgr.delete_mod(current_game,engine.get_textlist_index("mods_current")) + end + + if fields["btn_add_mod_to_game"] ~= nil then + local modindex = engine.get_textlist_index("mods_available") + + local mod = modmgr.get_global_mod(modindex) + if mod ~= nil then + + local sourcepath = mod.path + + if not gamemgr.add_mod(current_game,sourcepath) then + gamedata.errormessage = + fgettext("Gamemgr: Unable to copy mod \"$1\" to game \"$2\"", mod.name, current_game.id) + end + end + end + + return nil +end + +-------------------------------------------------------------------------------- +function gamemgr.add_mod(gamespec,sourcepath) + if gamespec.gamemods_path ~= nil and + gamespec.gamemods_path ~= "" then + + local modname = get_last_folder(sourcepath) + + return engine.copy_dir(sourcepath,gamespec.gamemods_path .. DIR_DELIM .. modname); + end + + return false +end + +-------------------------------------------------------------------------------- +function gamemgr.delete_mod(gamespec,modindex) + if gamespec.gamemods_path ~= nil and + gamespec.gamemods_path ~= "" then + local game_mods = {} + get_mods(gamespec.gamemods_path,game_mods) + + if modindex > 0 and + #game_mods >= modindex then + + if game_mods[modindex].path:sub(0,gamespec.gamemods_path:len()) + == gamespec.gamemods_path then + engine.delete_dir(game_mods[modindex].path) + end + end + end +end + +-------------------------------------------------------------------------------- +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.gettab(name) + local retval = "" + + if name == "dialog_edit_game" then + retval = retval .. gamemgr.dialog_edit_game() + end + + if name == "dialog_new_game" then + retval = retval .. gamemgr.dialog_new_game() + end + + if name == "game_mgr" then + retval = retval .. gamemgr.tab() + end + + return retval +end + +-------------------------------------------------------------------------------- +function gamemgr.tab() + if gamemgr.selected_game == nil then + gamemgr.selected_game = 1 + end + + local retval = + "vertlabel[0,-0.25;" .. fgettext("GAMES") .. "]" .. + "label[1,-0.25;" .. fgettext("Games") .. ":]" .. + "textlist[1,0.25;4.5,4.4;gamelist;" .. + gamemgr.gamelist() .. + ";" .. gamemgr.selected_game .. "]" + + local current_game = gamemgr.get_game(gamemgr.selected_game) + + if current_game ~= nil then + if current_game.menuicon_path ~= nil and + current_game.menuicon_path ~= "" then + retval = retval .. + "image[5.8,-0.25;2,2;" .. + engine.formspec_escape(current_game.menuicon_path) .. "]" + end + + retval = retval .. + "field[8,-0.25;6,2;;" .. current_game.name .. ";]".. + "label[6,1.4;" .. fgettext("Mods:") .."]" .. + "button[9.7,1.5;2,0.2;btn_game_mgr_edit_game;" .. fgettext("edit game") .. "]" .. + "textlist[6,2;5.5,3.3;game_mgr_modlist;" + .. gamemgr.get_game_modlist(current_game) ..";0]" .. + "button[1,4.75;3.2,0.5;btn_game_mgr_new_game;" .. fgettext("new game") .. "]" + end + return retval +end + +-------------------------------------------------------------------------------- +function gamemgr.dialog_edit_game() + local current_game = gamemgr.get_game(gamemgr.selected_game) + if current_game ~= nil then + local retval = + "vertlabel[0,-0.25;" .. fgettext("EDIT GAME") .."]" .. + "label[0,-0.25;" .. current_game.name .. "]" .. + "button[11.55,-0.2;0.75,0.5;btn_close_edit_game;x]" + + if current_game.menuicon_path ~= nil and + current_game.menuicon_path ~= "" then + retval = retval .. + "image[5.25,0;2,2;" .. + engine.formspec_escape(current_game.menuicon_path) .. "]" + end + + retval = retval .. + "textlist[0.5,0.5;4.5,4.3;mods_current;" + .. gamemgr.get_game_modlist(current_game) ..";0]" + + + retval = retval .. + "textlist[7,0.5;4.5,4.3;mods_available;" + .. modmgr.render_modlist() .. ";0]" + + retval = retval .. + "button[0.55,4.95;4.7,0.5;btn_remove_mod_from_game;" .. fgettext("Remove selected mod") .."]" + + retval = retval .. + "button[7.05,4.95;4.7,0.5;btn_add_mod_to_game;" .. fgettext("<<-- Add mod") .."]" + + return retval + end +end + +-------------------------------------------------------------------------------- +function gamemgr.handle_buttons(tab,fields) + local retval = nil + + if tab == "dialog_edit_game" then + retval = gamemgr.handle_edit_game_buttons(fields) + end + + if tab == "dialog_new_game" then + retval = gamemgr.handle_new_game_buttons(fields) + end + + if tab == "game_mgr" then + retval = gamemgr.handle_games_buttons(fields) + 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 = engine.get_games() +end + +-------------------------------------------------------------------------------- +function gamemgr.gamelist() + local retval = "" + if #gamemgr.games > 0 then + retval = retval .. gamemgr.games[1].id + + for i=2,#gamemgr.games,1 do + retval = retval .. "," .. gamemgr.games[i].name + end + end + return retval +end diff --git a/builtin/item.lua b/builtin/item.lua new file mode 100644 index 0000000..b3ab9bc --- /dev/null +++ b/builtin/item.lua @@ -0,0 +1,576 @@ +-- Minetest: builtin/item.lua + +-- +-- Item definition helpers +-- + +function minetest.inventorycube(img1, img2, img3) + img2 = img2 or img1 + img3 = img3 or img1 + return "[inventorycube" + .. "{" .. img1:gsub("%^", "&") + .. "{" .. img2:gsub("%^", "&") + .. "{" .. img3:gsub("%^", "&") +end + +function minetest.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 + else + -- The position where a node would be dug + return pointed_thing.under + end + elseif pointed_thing.type == "object" then + obj = pointed_thing.ref + if obj ~= nil then + return obj:getpos() + else + return nil + end + else + return nil + end +end + +function minetest.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 + +function minetest.facedir_to_dir(facedir) + --a table of possible dirs + return ({{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}}) + + --indexed into by a table of correlating facedirs + [({[0]=1, 2, 3, 4, + 5, 4, 6, 2, + 6, 2, 5, 4, + 1, 5, 3, 6, + 1, 6, 3, 5, + 1, 4, 3, 2}) + + --indexed into by the facedir in question + [facedir]] +end + +function minetest.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 + +function minetest.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 minetest.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 = minetest.get_node_or_nil(under) + local above = pointed_thing.above + local oldnode_above = minetest.get_node_or_nil(above) + + if not oldnode_under or not oldnode_above then + minetest.log("info", placer:get_player_name() .. " tried to place" + .. " node in unloaded position " .. minetest.pos_to_string(above)) + return itemstack, false + end + + local olddef_under = ItemStack({name=oldnode_under.name}):get_definition() + olddef_under = olddef_under or minetest.nodedef_default + local olddef_above = ItemStack({name=oldnode_above.name}):get_definition() + olddef_above = olddef_above or minetest.nodedef_default + + if not olddef_above.buildable_to and not olddef_under.buildable_to then + minetest.log("info", placer:get_player_name() .. " tried to place" + .. " node in invalid position " .. minetest.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 + minetest.log("info", "node under is buildable to") + place_to = {x = under.x, y = under.y, z = under.z} + end + + if minetest.is_protected(place_to, placer:get_player_name()) then + minetest.log("action", placer:get_player_name() + .. " tried to place " .. def.name + .. " at protected position " + .. minetest.pos_to_string(place_to)) + minetest.record_protection_violation(place_to, placer:get_player_name()) + return itemstack + end + + minetest.log("action", placer:get_player_name() .. " places node " + .. def.name .. " at " .. minetest.pos_to_string(place_to)) + + local oldnode = minetest.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.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 = minetest.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 = minetest.dir_to_facedir(dir) + minetest.log("action", "facedir: " .. newnode.param2) + end + end + + -- Check if the node is attached and if it can be placed there + if minetest.get_item_group(def.name, "attached_node") ~= 0 and + not check_attached_node(place_to, newnode) then + minetest.log("action", "attached node " .. def.name .. + " can not be placed at " .. minetest.pos_to_string(place_to)) + return itemstack, false + end + + -- Add node and update + minetest.add_node(place_to, newnode) + + local take_item = true + + -- Run callback + if def.after_place_node then + -- Copy place_to because callback can modify it + local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z} + if def.after_place_node(place_to_copy, placer, itemstack) then + take_item = false + end + end + + -- Run script hook + local _, callback + for _, callback in ipairs(minetest.registered_on_placenodes) do + -- Copy pos and node 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} + if callback(place_to_copy, newnode_copy, placer, oldnode_copy, itemstack) then + take_item = false + end + end + + if take_item then + itemstack:take_item() + end + return itemstack, true +end + +function minetest.item_place_object(itemstack, placer, pointed_thing) + local pos = minetest.get_pointed_thing_position(pointed_thing, true) + if pos ~= nil then + local item = itemstack:take_item() + minetest.add_item(pos, item) + end + return itemstack +end + +function minetest.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 = minetest.get_node(pointed_thing.under) + local nn = n.name + if minetest.registered_nodes[nn] and minetest.registered_nodes[nn].on_rightclick then + return minetest.registered_nodes[nn].on_rightclick(pointed_thing.under, n, placer, itemstack) or itemstack, false + end + end + + if itemstack:get_definition().type == "node" then + return minetest.item_place_node(itemstack, placer, pointed_thing, param2) + end + return itemstack +end + +function minetest.item_drop(itemstack, dropper, pos) + if dropper.get_player_name then + local v = dropper:get_look_dir() + local p = {x=pos.x+v.x, y=pos.y+1.5+v.y, z=pos.z+v.z} + local obj = minetest.add_item(p, itemstack) + if obj then + v.x = v.x*2 + v.y = v.y*2 + 1 + v.z = v.z*2 + obj:setvelocity(v) + end + else + minetest.add_item(pos, itemstack) + end + return ItemStack("") +end + +function minetest.item_eat(hp_change, replace_with_item) + return function(itemstack, user, pointed_thing) -- closure + if itemstack:take_item() ~= nil then + user:set_hp(user:get_hp() + hp_change) + itemstack:add_item(replace_with_item) -- note: replace_with_item is optional + end + return itemstack + end +end + +function minetest.node_punch(pos, node, puncher) + -- Run script hook + local _, callback + for _, callback in ipairs(minetest.registered_on_punchnodes) do + -- 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, puncher) + end +end + +function minetest.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, + } + minetest.add_item(p, left) + end + end + end +end + +function minetest.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 + minetest.log("info", digger:get_player_name() .. " tried to dig " + .. node.name .. " which is not diggable " + .. minetest.pos_to_string(pos)) + return + end + + if minetest.is_protected(pos, digger:get_player_name()) then + minetest.log("action", digger:get_player_name() + .. " tried to dig " .. node.name + .. " at protected position " + .. minetest.pos_to_string(pos)) + minetest.record_protection_violation(pos, digger:get_player_name()) + return + end + + minetest.log('action', digger:get_player_name() .. " digs " + .. node.name .. " at " .. minetest.pos_to_string(pos)) + + local wielded = digger:get_wielded_item() + local drops = minetest.get_node_drops(node.name, wielded:get_name()) + + local wdef = wielded:get_definition() + local tp = wielded:get_tool_capabilities() + local dp = minetest.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 minetest.setting_getbool("creative_mode") then + wielded:add_wear(dp.wear) + end + end + digger:set_wielded_item(wielded) + + -- Handle drops + minetest.handle_node_drops(pos, drops, digger) + + local oldmetadata = nil + if def.after_dig_node then + oldmetadata = minetest.get_meta(pos):to_table() + end + + -- Remove node and update + minetest.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(minetest.registered_on_dignodes) do + -- 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 minetest.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 +-- + +minetest.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(minetest, 'item_place'), -- minetest.item_place + on_drop = redef_wrapper(minetest, 'item_drop'), -- minetest.item_drop + on_use = nil, + can_dig = nil, + + on_punch = redef_wrapper(minetest, 'node_punch'), -- minetest.node_punch + on_rightclick = nil, + on_dig = redef_wrapper(minetest, 'node_dig'), -- minetest.node_dig + + on_receive_fields = nil, + + on_metadata_inventory_move = minetest.node_metadata_inventory_move_allow_all, + on_metadata_inventory_offer = minetest.node_metadata_inventory_offer_allow_all, + on_metadata_inventory_take = minetest.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 = false, + sunlight_propagates = false, + walkable = true, + pointable = true, + diggable = true, + climbable = false, + buildable_to = 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, +} + +minetest.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(minetest, 'item_place'), -- minetest.item_place + on_drop = redef_wrapper(minetest, 'item_drop'), -- minetest.item_drop + on_use = nil, +} + +minetest.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(minetest, 'item_place'), -- minetest.item_place + on_drop = redef_wrapper(minetest, 'item_drop'), -- minetest.item_drop + on_use = nil, +} + +minetest.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(minetest, 'item_place'), + on_drop = nil, + on_use = nil, +} + diff --git a/builtin/item_entity.lua b/builtin/item_entity.lua new file mode 100644 index 0000000..95affe3 --- /dev/null +++ b/builtin/item_entity.lua @@ -0,0 +1,122 @@ +-- Minetest: builtin/item_entity.lua + +function minetest.spawn_item(pos, item) + -- Take item in any format + local stack = ItemStack(item) + local obj = minetest.add_entity(pos, "__builtin:item") + obj:get_luaentity():set_item(stack:to_string()) + return obj +end + +minetest.register_entity("__builtin:item", { + initial_properties = { + hp_max = 1, + physical = true, + collide_with_objects = false, + collisionbox = {-0.17,-0.17,-0.17, 0.17,0.17,0.17}, + visual = "sprite", + visual_size = {x=0.5, y=0.5}, + textures = {""}, + spritediv = {x=1, y=1}, + initial_sprite_basepos = {x=0, y=0}, + is_visible = false, + }, + + itemstring = '', + physical_state = true, + + set_item = function(self, itemstring) + self.itemstring = itemstring + local stack = ItemStack(itemstring) + 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 minetest.registered_items[itemname] then + item_texture = minetest.registered_items[itemname].inventory_image + item_type = minetest.registered_items[itemname].type + end + prop = { + is_visible = true, + visual = "sprite", + textures = {"unknown_item.png"} + } + if item_texture and item_texture ~= "" then + prop.visual = "sprite" + prop.textures = {item_texture} + prop.visual_size = {x=0.50, y=0.50} + else + prop.visual = "wielditem" + prop.textures = {itemname} + prop.visual_size = {x=0.20, y=0.20} + prop.automatic_rotate = math.pi * 0.25 + end + self.object:set_properties(prop) + end, + + get_staticdata = function(self) + --return self.itemstring + return minetest.serialize({ + itemstring = self.itemstring, + always_collect = self.always_collect, + }) + end, + + on_activate = function(self, staticdata) + if string.sub(staticdata, 1, string.len("return")) == "return" then + local data = minetest.deserialize(staticdata) + if data and type(data) == "table" then + self.itemstring = data.itemstring + self.always_collect = data.always_collect + 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, + + on_step = function(self, dtime) + local p = self.object:getpos() + p.y = p.y - 0.3 + local nn = minetest.get_node(p).name + -- If node is not registered or node is walkably solid and resting on nodebox + local v = self.object:getvelocity() + if not minetest.registered_nodes[nn] or minetest.registered_nodes[nn].walkable and v.y == 0 then + if self.physical_state then + 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) + if self.itemstring ~= '' then + local left = hitter:get_inventory():add_item("main", self.itemstring) + if not left:is_empty() then + self.itemstring = left:to_string() + return + end + end + self.object:remove() + end, +}) + diff --git a/builtin/mainmenu.lua b/builtin/mainmenu.lua new file mode 100644 index 0000000..c09b9cb --- /dev/null +++ b/builtin/mainmenu.lua @@ -0,0 +1,1227 @@ +print = engine.debug +math.randomseed(os.time()) +os.setlocale("C", "numeric") + +local errorfct = error +error = function(text) + print(debug.traceback("")) + errorfct(text) +end + +local scriptpath = engine.get_scriptdir() + +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! + +dofile(scriptpath .. DIR_DELIM .. "misc_helpers.lua") +dofile(scriptpath .. DIR_DELIM .. "filterlist.lua") +dofile(scriptpath .. DIR_DELIM .. "modmgr.lua") +dofile(scriptpath .. DIR_DELIM .. "modstore.lua") +dofile(scriptpath .. DIR_DELIM .. "gamemgr.lua") +dofile(scriptpath .. DIR_DELIM .. "mm_textures.lua") +dofile(scriptpath .. DIR_DELIM .. "mm_menubar.lua") + +menu = {} +local tabbuilder = {} +local worldlist = nil + +-------------------------------------------------------------------------------- +local function filter_texture_pack_list(list) + retval = {"None"} + for _,i in ipairs(list) do + if i~="base" then + table.insert(retval, i) + end + end + return retval +end + +-------------------------------------------------------------------------------- +function menu.render_favorite(spec,render_details) + local text = "" + + if spec.name ~= nil then + text = text .. engine.formspec_escape(spec.name:trim()) + +-- if spec.description ~= nil and +-- engine.formspec_escape(spec.description):trim() ~= "" then +-- text = text .. " (" .. engine.formspec_escape(spec.description) .. ")" +-- end + else + if spec.address ~= nil then + text = text .. spec.address:trim() + + if spec.port ~= nil then + text = text .. ":" .. spec.port + end + end + end + + if not render_details then + return text + end + + local details = "" + if spec.password == true then + details = details .. "*" + else + details = details .. "_" + end + + if spec.creative then + details = details .. "C" + else + details = details .. "_" + end + + if spec.damage then + details = details .. "D" + else + details = details .. "_" + end + + if spec.pvp then + details = details .. "P" + else + details = details .. "_" + end + details = details .. " " + + local playercount = "" + + if spec.clients ~= nil and + spec.clients_max ~= nil then + playercount = string.format("%03d",spec.clients) .. "/" .. + string.format("%03d",spec.clients_max) .. " " + end + + return playercount .. engine.formspec_escape(details) .. text +end + +-------------------------------------------------------------------------------- +os.tempfolder = function() + 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 init_globals() + --init gamedata + gamedata.worldindex = 0 + + worldlist = filterlist.create( + engine.get_worlds, + compare_worlds, + function(element,uid) + if element.name == uid then + return true + end + return false + end, --unique id compare fct + function(element,gameid) + if element.gameid == gameid then + return true + end + return false + end --filter fct + ) + + filterlist.add_sort_mechanism(worldlist,"alphabetic",sort_worlds_alphabetic) + filterlist.set_sortmode(worldlist,"alphabetic") +end + +-------------------------------------------------------------------------------- +function update_menu() + + local formspec + + -- handle errors + if gamedata.errormessage ~= nil then + formspec = "size[12,5.2]" .. + "field[1,2;10,2;;ERROR: " .. + gamedata.errormessage .. + ";]".. + "button[4.5,4.2;3,0.5;btn_error_confirm;" .. fgettext("Ok") .. "]" + else + formspec = tabbuilder.gettab() + end + + engine.update_formspec(formspec) +end + +-------------------------------------------------------------------------------- +function menu.render_world_list() + local retval = "" + + local current_worldlist = filterlist.get_list(worldlist) + + for i,v in ipairs(current_worldlist) do + if retval ~= "" then + retval = retval .."," + end + + retval = retval .. engine.formspec_escape(v.name) .. + " \\[" .. engine.formspec_escape(v.gameid) .. "\\]" + end + + return retval +end + +-------------------------------------------------------------------------------- +function menu.render_texture_pack_list(list) + local retval = "" + + for i,v in ipairs(list) do + if retval ~= "" then + retval = retval .."," + end + + retval = retval .. v + end + + return retval +end + +-------------------------------------------------------------------------------- +function menu.init() + --init menu data + gamemgr.update_gamelist() + + menu.last_game = tonumber(engine.setting_get("main_menu_last_game_idx")) + + if type(menu.last_game) ~= "number" then + menu.last_game = 1 + end + + if engine.setting_getbool("public_serverlist") then + menu.favorites = engine.get_favorites("online") + else + menu.favorites = engine.get_favorites("local") + end + + menu.defaulttexturedir = engine.get_texturepath() .. DIR_DELIM .. "base" .. + DIR_DELIM .. "pack" .. DIR_DELIM +end + +-------------------------------------------------------------------------------- +function menu.lastgame() + if menu.last_game > 0 and menu.last_game <= #gamemgr.games then + return gamemgr.games[menu.last_game] + end + + if #gamemgr.games >= 1 then + menu.last_game = 1 + return gamemgr.games[menu.last_game] + end + + --error case!! + return nil +end + +-------------------------------------------------------------------------------- +function menu.update_last_game() + + local current_world = filterlist.get_raw_element(worldlist, + engine.setting_get("mainmenu_last_selected_world") + ) + + if current_world == nil then + return + end + + local gamespec, i = gamemgr.find_by_gameid(current_world.gameid) + if i ~= nil then + menu.last_game = i + engine.setting_set("main_menu_last_game_idx",menu.last_game) + end +end + +-------------------------------------------------------------------------------- +function menu.handle_key_up_down(fields,textlist,settingname) + + if fields["key_up"] then + local oldidx = engine.get_textlist_index(textlist) + + if oldidx > 1 then + local newidx = oldidx -1 + engine.setting_set(settingname, + filterlist.get_raw_index(worldlist,newidx)) + end + end + + if fields["key_down"] then + local oldidx = engine.get_textlist_index(textlist) + + if oldidx < filterlist.size(worldlist) then + local newidx = oldidx + 1 + engine.setting_set(settingname, + filterlist.get_raw_index(worldlist,newidx)) + end + end +end + +-------------------------------------------------------------------------------- +function tabbuilder.dialog_create_world() + local mapgens = {"v6", "v7", "indev", "singlenode", "math"} + + local current_seed = engine.setting_get("fixed_map_seed") or "" + local current_mg = engine.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 retval = + "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() .. + ";" .. menu.last_game .. ";true]" .. + + "button[5,5.5;2.6,0.5;world_create_confirm;" .. fgettext("Create") .. "]" .. + "button[7.5,5.5;2.8,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]" + + return retval +end + +-------------------------------------------------------------------------------- +function tabbuilder.dialog_delete_world() + return "label[2,2;" .. + fgettext("Delete World \"$1\"?", filterlist.get_raw_list(worldlist)[menu.world_to_del].name) .. "]".. + "button[3.5,4.2;2.6,0.5;world_delete_confirm;" .. fgettext("Yes").. "]" .. + "button[6,4.2;2.8,0.5;world_delete_cancel;" .. fgettext("No") .. "]" +end + +-------------------------------------------------------------------------------- + +function tabbuilder.gettab() + local tsize = tabbuilder.tabsizes[tabbuilder.current_tab] or {width=12, height=5.2} + local retval = "size[" .. tsize.width .. "," .. tsize.height .. "]" + + if tabbuilder.show_buttons then + retval = retval .. tabbuilder.tab_header() + end + + local buildfunc = tabbuilder.tabfuncs[tabbuilder.current_tab] + if buildfunc ~= nil then + retval = retval .. buildfunc() + end + + retval = retval .. modmgr.gettab(tabbuilder.current_tab) + retval = retval .. gamemgr.gettab(tabbuilder.current_tab) + retval = retval .. modstore.gettab(tabbuilder.current_tab) + + return retval +end + +-------------------------------------------------------------------------------- +function tabbuilder.handle_create_world_buttons(fields) + + if fields["world_create_confirm"] or + fields["key_enter"] then + + local worldname = fields["te_world_name"] + local gameindex = engine.get_textlist_index("games") + + if gameindex > 0 and + worldname ~= "" then + + local message = nil + + if not filterlist.uid_exists_raw(worldlist,worldname) then + engine.setting_set("mg_name",fields["dd_mapgen"]) + message = engine.create_world(worldname,gameindex) + else + message = fgettext("A world named \"$1\" already exists", worldname) + end + + engine.setting_set("fixed_map_seed", fields["te_seed"]) + + if message ~= nil then + gamedata.errormessage = message + else + menu.last_game = gameindex + engine.setting_set("main_menu_last_game_idx",gameindex) + + filterlist.refresh(worldlist) + engine.setting_set("mainmenu_last_selected_world", + filterlist.raw_index_by_uid(worldlist,worldname)) + end + else + gamedata.errormessage = + fgettext("No worldname given or no game selected") + end + end + + if fields["games"] then + tabbuilder.skipformupdate = true + return + end + + --close dialog + tabbuilder.is_dialog = false + tabbuilder.show_buttons = true + tabbuilder.current_tab = engine.setting_get("main_menu_tab") +end + +-------------------------------------------------------------------------------- +function tabbuilder.handle_delete_world_buttons(fields) + + if fields["world_delete_confirm"] then + if menu.world_to_del > 0 and + menu.world_to_del <= #filterlist.get_raw_list(worldlist) then + engine.delete_world(menu.world_to_del) + menu.world_to_del = 0 + filterlist.refresh(worldlist) + end + end + + tabbuilder.is_dialog = false + tabbuilder.show_buttons = true + tabbuilder.current_tab = engine.setting_get("main_menu_tab") +end + +-------------------------------------------------------------------------------- +function tabbuilder.handle_multiplayer_buttons(fields) + + if fields["te_name"] ~= nil then + gamedata.playername = fields["te_name"] + engine.setting_set("name", fields["te_name"]) + end + + if fields["favourites"] ~= nil then + local event = explode_textlist_event(fields["favourites"]) + if event.typ == "DCL" then + gamedata.address = menu.favorites[event.index].address + gamedata.port = menu.favorites[event.index].port + gamedata.playername = fields["te_name"] + if fields["te_pwd"] ~= nil then + gamedata.password = fields["te_pwd"] + end + gamedata.selected_world = 0 + + if menu.favorites ~= nil then + gamedata.servername = menu.favorites[event.index].name + gamedata.serverdescription = menu.favorites[event.index].description + end + + if gamedata.address ~= nil and + gamedata.port ~= nil then + + engine.start() + end + end + + if event.typ == "CHG" then + local address = menu.favorites[event.index].address + local port = menu.favorites[event.index].port + + if address ~= nil and + port ~= nil then + engine.setting_set("address",address) + engine.setting_set("port",port) + end + + menu.fav_selected = event.index + end + return + end + + if fields["key_up"] ~= nil or + fields["key_down"] ~= nil then + + local fav_idx = engine.get_textlist_index("favourites") + + if fields["key_up"] ~= nil and fav_idx > 1 then + fav_idx = fav_idx -1 + else if fields["key_down"] and fav_idx < #menu.favorites then + fav_idx = fav_idx +1 + end end + + local address = menu.favorites[fav_idx].address + local port = menu.favorites[fav_idx].port + + if address ~= nil and + port ~= nil then + engine.setting_set("address",address) + engine.setting_set("port",port) + end + + menu.fav_selected = fav_idx + return + end + + if fields["cb_public_serverlist"] ~= nil then + engine.setting_set("public_serverlist", fields["cb_public_serverlist"]) + + if engine.setting_getbool("public_serverlist") then + menu.favorites = engine.get_favorites("online") + else + menu.favorites = engine.get_favorites("local") + end + menu.fav_selected = nil + return + end + + if fields["btn_delete_favorite"] ~= nil then + local current_favourite = engine.get_textlist_index("favourites") + engine.delete_favorite(current_favourite) + menu.favorites = engine.get_favorites() + menu.fav_selected = nil + + engine.setting_set("address","") + engine.setting_get("port","") + + return + end + + if fields["btn_mp_connect"] ~= nil 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 = engine.get_textlist_index("favourites") + + if fav_idx > 0 and fav_idx <= #menu.favorites and + menu.favorites[fav_idx].address == fields["te_address"] and + menu.favorites[fav_idx].port == fields["te_port"] then + + gamedata.servername = menu.favorites[fav_idx].name + gamedata.serverdescription = menu.favorites[fav_idx].description + else + gamedata.servername = "" + gamedata.serverdescription = "" + end + + gamedata.selected_world = 0 + + engine.start() + return + end +end + +-------------------------------------------------------------------------------- +function tabbuilder.handle_server_buttons(fields) + + local world_doubleclick = false + + if fields["srv_worlds"] ~= nil then + local event = explode_textlist_event(fields["srv_worlds"]) + + if event.typ == "DCL" then + world_doubleclick = true + end + if event.typ == "CHG" then + engine.setting_set("mainmenu_last_selected_world", + filterlist.get_raw_index(worldlist,engine.get_textlist_index("srv_worlds"))) + end + end + + menu.handle_key_up_down(fields,"srv_worlds","mainmenu_last_selected_world") + + if fields["cb_creative_mode"] then + engine.setting_set("creative_mode", fields["cb_creative_mode"]) + end + + if fields["cb_enable_damage"] then + engine.setting_set("enable_damage", fields["cb_enable_damage"]) + end + + if fields["cb_server_announce"] then + engine.setting_set("server_announce", fields["cb_server_announce"]) + end + + if fields["start_server"] ~= nil or + world_doubleclick or + fields["key_enter"] then + local selected = engine.get_textlist_index("srv_worlds") + if selected > 0 then + gamedata.playername = fields["te_playername"] + gamedata.password = fields["te_passwd"] + gamedata.port = fields["te_serverport"] + gamedata.address = "" + gamedata.selected_world = filterlist.get_raw_index(worldlist,selected) + + menu.update_last_game(gamedata.selected_world) + engine.start() + end + end + + if fields["world_create"] ~= nil then + tabbuilder.current_tab = "dialog_create_world" + tabbuilder.is_dialog = true + tabbuilder.show_buttons = false + end + + if fields["world_delete"] ~= nil then + local selected = engine.get_textlist_index("srv_worlds") + if selected > 0 and + selected <= filterlist.size(worldlist) then + local world = filterlist.get_list(worldlist)[selected] + if world ~= nil and + world.name ~= nil and + world.name ~= "" then + menu.world_to_del = filterlist.get_raw_index(worldlist,selected) + tabbuilder.current_tab = "dialog_delete_world" + tabbuilder.is_dialog = true + tabbuilder.show_buttons = false + else + menu.world_to_del = 0 + end + end + end + + if fields["world_configure"] ~= nil then + selected = engine.get_textlist_index("srv_worlds") + if selected > 0 then + modmgr.world_config_selected_world = filterlist.get_raw_index(worldlist,selected) + if modmgr.init_worldconfig() then + tabbuilder.current_tab = "dialog_configure_world" + tabbuilder.is_dialog = true + tabbuilder.show_buttons = false + end + end + end +end + +-------------------------------------------------------------------------------- +function tabbuilder.handle_settings_buttons(fields) + if fields["cb_fancy_trees"] then + engine.setting_set("new_style_leaves", fields["cb_fancy_trees"]) + end + if fields["cb_smooth_lighting"] then + engine.setting_set("smooth_lighting", fields["cb_smooth_lighting"]) + end + if fields["cb_3d_clouds"] then + engine.setting_set("enable_3d_clouds", fields["cb_3d_clouds"]) + end + if fields["cb_opaque_water"] then + engine.setting_set("opaque_water", fields["cb_opaque_water"]) + end + + if fields["cb_mipmapping"] then + engine.setting_set("mip_map", fields["cb_mipmapping"]) + end + if fields["cb_anisotrophic"] then + engine.setting_set("anisotropic_filter", fields["cb_anisotrophic"]) + end + if fields["cb_bilinear"] then + engine.setting_set("bilinear_filter", fields["cb_bilinear"]) + end + if fields["cb_trilinear"] then + engine.setting_set("trilinear_filter", fields["cb_trilinear"]) + end + + if fields["cb_shaders"] then + if (engine.setting_get("video_driver") == "direct3d8" or engine.setting_get("video_driver") == "direct3d9") then + engine.setting_set("enable_shaders", "false") + gamedata.errormessage = fgettext("To enable shaders the OpenGL driver needs to be used.") + else + engine.setting_set("enable_shaders", fields["cb_shaders"]) + end + end + if fields["cb_pre_ivis"] then + engine.setting_set("preload_item_visuals", fields["cb_pre_ivis"]) + end + if fields["cb_particles"] then + engine.setting_set("enable_particles", fields["cb_particles"]) + end + if fields["cb_finite_liquid"] then + engine.setting_set("liquid_finite", fields["cb_finite_liquid"]) + end + + if fields["btn_change_keys"] ~= nil then + engine.show_keys_menu() + end +end + +-------------------------------------------------------------------------------- +function tabbuilder.handle_singleplayer_buttons(fields) + + local world_doubleclick = false + + if fields["sp_worlds"] ~= nil then + local event = explode_textlist_event(fields["sp_worlds"]) + + if event.typ == "DCL" then + world_doubleclick = true + end + + if event.typ == "CHG" then + engine.setting_set("mainmenu_last_selected_world", + filterlist.get_raw_index(worldlist,engine.get_textlist_index("sp_worlds"))) + end + end + + menu.handle_key_up_down(fields,"sp_worlds","mainmenu_last_selected_world") + + if fields["cb_creative_mode"] then + engine.setting_set("creative_mode", fields["cb_creative_mode"]) + end + + if fields["cb_enable_damage"] then + engine.setting_set("enable_damage", fields["cb_enable_damage"]) + end + + if fields["play"] ~= nil or + world_doubleclick or + fields["key_enter"] then + local selected = engine.get_textlist_index("sp_worlds") + if selected > 0 then + gamedata.selected_world = filterlist.get_raw_index(worldlist,selected) + gamedata.singleplayer = true + + menu.update_last_game(gamedata.selected_world) + + engine.start() + end + end + + if fields["world_create"] ~= nil then + tabbuilder.current_tab = "dialog_create_world" + tabbuilder.is_dialog = true + tabbuilder.show_buttons = false + end + + if fields["world_delete"] ~= nil then + local selected = engine.get_textlist_index("sp_worlds") + if selected > 0 and + selected <= filterlist.size(worldlist) then + local world = filterlist.get_list(worldlist)[selected] + if world ~= nil and + world.name ~= nil and + world.name ~= "" then + menu.world_to_del = filterlist.get_raw_index(worldlist,selected) + tabbuilder.current_tab = "dialog_delete_world" + tabbuilder.is_dialog = true + tabbuilder.show_buttons = false + else + menu.world_to_del = 0 + end + end + end + + if fields["world_configure"] ~= nil then + selected = engine.get_textlist_index("sp_worlds") + if selected > 0 then + modmgr.world_config_selected_world = filterlist.get_raw_index(worldlist,selected) + if modmgr.init_worldconfig() then + tabbuilder.current_tab = "dialog_configure_world" + tabbuilder.is_dialog = true + tabbuilder.show_buttons = false + end + end + end +end + +-------------------------------------------------------------------------------- +function tabbuilder.handle_texture_pack_buttons(fields) + if fields["TPs"] ~= nil then + local event = explode_textlist_event(fields["TPs"]) + if event.typ == "CHG" or event.typ=="DCL" then + local index = engine.get_textlist_index("TPs") + engine.setting_set("mainmenu_last_selected_TP", + index) + local list = filter_texture_pack_list(engine.get_dirlist(engine.get_texturepath(), true)) + local current_index = engine.get_textlist_index("TPs") + if #list >= current_index then + local new_path = engine.get_texturepath()..DIR_DELIM..list[current_index] + if list[current_index] == "None" then new_path = "" end + + engine.setting_set("texture_path", new_path) + end + end + end +end + +-------------------------------------------------------------------------------- +function tabbuilder.tab_header() + + if tabbuilder.last_tab_index == nil then + tabbuilder.last_tab_index = 1 + end + + local toadd = "" + + for i=1,#tabbuilder.current_buttons,1 do + + if toadd ~= "" then + toadd = toadd .. "," + end + + toadd = toadd .. tabbuilder.current_buttons[i].caption + end + return "tabheader[-0.3,-0.99;main_tab;" .. toadd ..";" .. tabbuilder.last_tab_index .. ";true;false]" +end + +-------------------------------------------------------------------------------- +function tabbuilder.handle_tab_buttons(fields) + + if fields["main_tab"] then + local index = tonumber(fields["main_tab"]) + tabbuilder.last_tab_index = index + tabbuilder.current_tab = tabbuilder.current_buttons[index].name + + engine.setting_set("main_menu_tab",tabbuilder.current_tab) + end + + --handle tab changes + if tabbuilder.current_tab ~= tabbuilder.old_tab then + if tabbuilder.current_tab ~= "singleplayer" and not tabbuilder.is_dialog then + menu.update_gametype(true) + end + end + + if tabbuilder.current_tab == "singleplayer" then + menu.update_gametype() + end + + tabbuilder.old_tab = tabbuilder.current_tab +end + +-------------------------------------------------------------------------------- +function tabbuilder.tab_multiplayer() + + local retval = + "vertlabel[0,-0.25;".. fgettext("CLIENT") .. "]" .. + "label[1,-0.25;".. fgettext("Favorites:") .. "]".. + "label[1,4.25;".. fgettext("Address/Port") .. "]".. + "label[9,2.75;".. fgettext("Name/Password") .. "]" .. + "field[1.25,5.25;5.5,0.5;te_address;;" ..engine.setting_get("address") .."]" .. + "field[6.75,5.25;2.25,0.5;te_port;;" ..engine.setting_get("port") .."]" .. + "checkbox[1,3.6;cb_public_serverlist;".. fgettext("Public Serverlist") .. ";" .. + dump(engine.setting_getbool("public_serverlist")) .. "]" + + if not engine.setting_getbool("public_serverlist") then + retval = retval .. + "button[6.45,3.95;2.25,0.5;btn_delete_favorite;".. fgettext("Delete") .. "]" + end + + retval = retval .. + "button[9,4.95;2.5,0.5;btn_mp_connect;".. fgettext("Connect") .. "]" .. + "field[9.3,3.75;2.5,0.5;te_name;;" ..engine.setting_get("name") .."]" .. + "pwdfield[9.3,4.5;2.5,0.5;te_pwd;]" .. + "textarea[9.3,0.25;2.5,2.75;;" + if menu.fav_selected ~= nil and + menu.favorites[menu.fav_selected].description ~= nil then + retval = retval .. + engine.formspec_escape(menu.favorites[menu.fav_selected].description,true) + end + + retval = retval .. + ";]" .. + "textlist[1,0.35;7.5,3.35;favourites;" + + local render_details = engine.setting_getbool("public_serverlist") + + if #menu.favorites > 0 then + retval = retval .. menu.render_favorite(menu.favorites[1],render_details) + + for i=2,#menu.favorites,1 do + retval = retval .. "," .. menu.render_favorite(menu.favorites[i],render_details) + end + end + + if menu.fav_selected ~= nil then + retval = retval .. ";" .. menu.fav_selected .. "]" + else + retval = retval .. ";0]" + end + + return retval +end + +-------------------------------------------------------------------------------- +function tabbuilder.tab_server() + + local index = filterlist.get_current_index(worldlist, + tonumber(engine.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,4.9;3.25,0.5;start_server;".. fgettext("Start Game") .. "]" .. + "label[4,-0.25;".. fgettext("Select World:") .. "]".. + "vertlabel[0,-0.25;".. fgettext("START SERVER") .. "]" .. + "checkbox[0.5,0.25;cb_creative_mode;".. fgettext("Creative Mode") .. ";" .. + dump(engine.setting_getbool("creative_mode")) .. "]".. + "checkbox[0.5,0.7;cb_enable_damage;".. fgettext("Enable Damage") .. ";" .. + dump(engine.setting_getbool("enable_damage")) .. "]".. + "checkbox[0.5,1.15;cb_server_announce;".. fgettext("Public") .. ";" .. + dump(engine.setting_getbool("server_announce")) .. "]".. + "field[0.8,3.2;3,0.5;te_playername;".. fgettext("Name") .. ";" .. + engine.setting_get("name") .. "]" .. + "pwdfield[0.8,4.2;3,0.5;te_passwd;".. fgettext("Password") .. "]" .. + "field[0.8,5.2;3,0.5;te_serverport;".. fgettext("Server Port") .. ";30000]" .. + "textlist[4,0.25;7.5,3.7;srv_worlds;" .. + menu.render_world_list() .. + ";" .. index .. "]" + + return retval +end + +-------------------------------------------------------------------------------- +function tabbuilder.tab_settings() + return "vertlabel[0,0;" .. fgettext("SETTINGS") .. "]" .. + "checkbox[1,0.75;cb_fancy_trees;".. fgettext("Fancy trees") .. ";" + .. dump(engine.setting_getbool("new_style_leaves")) .. "]".. + "checkbox[1,1.25;cb_smooth_lighting;".. fgettext("Smooth Lighting") + .. ";".. dump(engine.setting_getbool("smooth_lighting")) .. "]".. + "checkbox[1,1.75;cb_3d_clouds;".. fgettext("3D Clouds") .. ";" + .. dump(engine.setting_getbool("enable_3d_clouds")) .. "]".. + "checkbox[1,2.25;cb_opaque_water;".. fgettext("Opaque Water") .. ";" + .. dump(engine.setting_getbool("opaque_water")) .. "]".. + + "checkbox[4,0.75;cb_mipmapping;".. fgettext("Mip-Mapping") .. ";" + .. dump(engine.setting_getbool("mip_map")) .. "]".. + "checkbox[4,1.25;cb_anisotrophic;".. fgettext("Anisotropic Filtering") .. ";" + .. dump(engine.setting_getbool("anisotropic_filter")) .. "]".. + "checkbox[4,1.75;cb_bilinear;".. fgettext("Bi-Linear Filtering") .. ";" + .. dump(engine.setting_getbool("bilinear_filter")) .. "]".. + "checkbox[4,2.25;cb_trilinear;".. fgettext("Tri-Linear Filtering") .. ";" + .. dump(engine.setting_getbool("trilinear_filter")) .. "]".. + + "checkbox[7.5,0.75;cb_shaders;".. fgettext("Shaders") .. ";" + .. dump(engine.setting_getbool("enable_shaders")) .. "]".. + "checkbox[7.5,1.25;cb_pre_ivis;".. fgettext("Preload item visuals") .. ";" + .. dump(engine.setting_getbool("preload_item_visuals")) .. "]".. + "checkbox[7.5,1.75;cb_particles;".. fgettext("Enable Particles") .. ";" + .. dump(engine.setting_getbool("enable_particles")) .. "]".. + "checkbox[7.5,2.25;cb_finite_liquid;".. fgettext("Finite Liquid") .. ";" + .. dump(engine.setting_getbool("liquid_finite")) .. "]".. + + "button[1,4.25;2.25,0.5;btn_change_keys;".. fgettext("Change keys") .. "]" +end + +-------------------------------------------------------------------------------- +function tabbuilder.tab_singleplayer() + + local index = filterlist.get_current_index(worldlist, + tonumber(engine.setting_get("mainmenu_last_selected_world")) + ) + + return "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,4.95;3.25,0.5;play;".. fgettext("Play") .. "]" .. + "label[4,-0.25;".. fgettext("Select World:") .. "]".. + "vertlabel[0,-0.25;".. fgettext("SINGLE PLAYER") .. "]" .. + "checkbox[0.5,0.25;cb_creative_mode;".. fgettext("Creative Mode") .. ";" .. + dump(engine.setting_getbool("creative_mode")) .. "]".. + "checkbox[0.5,0.7;cb_enable_damage;".. fgettext("Enable Damage") .. ";" .. + dump(engine.setting_getbool("enable_damage")) .. "]".. + "textlist[4,0.25;7.5,3.7;sp_worlds;" .. + menu.render_world_list() .. + ";" .. index .. "]" .. + menubar.formspec +end + +-------------------------------------------------------------------------------- +function tabbuilder.tab_texture_packs() + local retval = "label[4,-0.25;".. fgettext("Select texture pack:") .. "]".. + "vertlabel[0,-0.25;".. fgettext("TEXTURE PACKS") .. "]" .. + "textlist[4,0.25;7.5,5.0;TPs;" + + local current_texture_path = engine.setting_get("texture_path") + local list = filter_texture_pack_list(engine.get_dirlist(engine.get_texturepath(), true)) + local index = tonumber(engine.setting_get("mainmenu_last_selected_TP")) + + if index == nil then index = 1 end + + if current_texture_path == "" then + retval = retval .. + menu.render_texture_pack_list(list) .. + ";" .. index .. "]" + return retval + end + + local infofile = current_texture_path ..DIR_DELIM.."info.txt" + local infotext = "" + local f = io.open(infofile, "r") + if f==nil 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 = nil + if not file_exists(screenfile) then + screenfile = nil + no_screenshot = engine.get_texturepath()..DIR_DELIM.. + "base"..DIR_DELIM.."pack"..DIR_DELIM.."no_screenshot.png" + end + + return retval .. + menu.render_texture_pack_list(list) .. + ";" .. index .. "]" .. + "image[0.65,0.25;4.0,3.7;"..engine.formspec_escape(screenfile or no_screenshot).."]".. + "textarea[1.0,3.25;3.7,1.5;;"..engine.formspec_escape(infotext or "")..";]" +end + +-------------------------------------------------------------------------------- +function tabbuilder.tab_credits() + local logofile = menu.defaulttexturedir .. "logo.png" + return "vertlabel[0,-0.5;CREDITS]" .. + "label[0.5,3;Minetest " .. engine.get_version() .. "]" .. + "label[0.5,3.3;http://minetest.net]" .. + "image[0.5,1;" .. engine.formspec_escape(logofile) .. "]" .. + "textlist[3.5,-0.25;8.5,5.8;list_credits;" .. + "#FFFF00" .. fgettext("Core Developers") .."," .. + "Perttu Ahola (celeron55) ,".. + "Ryan Kwolek (kwolekr) ,".. + "PilzAdam ," .. + "Ilya Zhuravlev (xyz) ,".. + "Lisa Milne (darkrose) ,".. + "Maciej Kasatkin (RealBadAngel) ,".. + "proller ,".. + "sfan5 ,".. + "kahrl ,".. + "sapier,".. + "ShadowNinja ,".. + "Nathanael Courant (Nore/Novatux) ,".. + "BlockMen,".. + ",".. + "#FFFF00" .. fgettext("Active Contributors") .. "," .. + "Vanessa Ezekowitz (VanessaE) ,".. + "Jurgen Doser (doserj) ,".. + "Jeija ,".. + "MirceaKitsune ,".. + "dannydark ,".. + "0gb.us <0gb.us@0gb.us>,".. + "," .. + "#FFFF00" .. fgettext("Previous Contributors") .. "," .. + "Guiseppe Bilotta (Oblomov) ,".. + "Jonathan Neuschafer ,".. + "Nils Dagsson Moskopp (erlehmann) ,".. + "Constantin Wenger (SpeedProg) ,".. + "matttpt ,".. + "JacobF ,".. + ";0;true]" +end + +-------------------------------------------------------------------------------- +function tabbuilder.init() + tabbuilder.tabfuncs = { + singleplayer = tabbuilder.tab_singleplayer, + multiplayer = tabbuilder.tab_multiplayer, + server = tabbuilder.tab_server, + settings = tabbuilder.tab_settings, + texture_packs = tabbuilder.tab_texture_packs, + credits = tabbuilder.tab_credits, + dialog_create_world = tabbuilder.dialog_create_world, + dialog_delete_world = tabbuilder.dialog_delete_world + } + + tabbuilder.tabsizes = { + dialog_create_world = {width=12, height=7}, + dialog_delete_world = {width=12, height=5.2} + } + + tabbuilder.current_tab = engine.setting_get("main_menu_tab") + + if tabbuilder.current_tab == nil or + tabbuilder.current_tab == "" then + tabbuilder.current_tab = "singleplayer" + engine.setting_set("main_menu_tab",tabbuilder.current_tab) + end + + --initialize tab buttons + tabbuilder.last_tab = nil + tabbuilder.show_buttons = true + + tabbuilder.current_buttons = {} + table.insert(tabbuilder.current_buttons,{name="singleplayer", caption=fgettext("Singleplayer")}) + table.insert(tabbuilder.current_buttons,{name="multiplayer", caption=fgettext("Client")}) + table.insert(tabbuilder.current_buttons,{name="server", caption=fgettext("Server")}) + table.insert(tabbuilder.current_buttons,{name="settings", caption=fgettext("Settings")}) + table.insert(tabbuilder.current_buttons,{name="texture_packs", caption=fgettext("Texture Packs")}) + + if engine.setting_getbool("main_menu_game_mgr") then + table.insert(tabbuilder.current_buttons,{name="game_mgr", caption=fgettext("Games")}) + end + + if engine.setting_getbool("main_menu_mod_mgr") then + table.insert(tabbuilder.current_buttons,{name="mod_mgr", caption=fgettext("Mods")}) + end + table.insert(tabbuilder.current_buttons,{name="credits", caption=fgettext("Credits")}) + + + for i=1,#tabbuilder.current_buttons,1 do + if tabbuilder.current_buttons[i].name == tabbuilder.current_tab then + tabbuilder.last_tab_index = i + end + end + + if tabbuilder.current_tab ~= "singleplayer" then + menu.update_gametype(true) + else + menu.update_gametype() + end +end + +-------------------------------------------------------------------------------- +function tabbuilder.checkretval(retval) + + if retval ~= nil then + if retval.current_tab ~= nil then + tabbuilder.current_tab = retval.current_tab + end + + if retval.is_dialog ~= nil then + tabbuilder.is_dialog = retval.is_dialog + end + + if retval.show_buttons ~= nil then + tabbuilder.show_buttons = retval.show_buttons + end + + if retval.skipformupdate ~= nil then + tabbuilder.skipformupdate = retval.skipformupdate + end + end +end + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +-- initialize callbacks +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +engine.button_handler = function(fields) + --print("Buttonhandler: tab: " .. tabbuilder.current_tab .. " fields: " .. dump(fields)) + + if fields["btn_error_confirm"] then + gamedata.errormessage = nil + end + + local retval = modmgr.handle_buttons(tabbuilder.current_tab,fields) + tabbuilder.checkretval(retval) + + retval = gamemgr.handle_buttons(tabbuilder.current_tab,fields) + tabbuilder.checkretval(retval) + + retval = modstore.handle_buttons(tabbuilder.current_tab,fields) + tabbuilder.checkretval(retval) + + if tabbuilder.current_tab == "dialog_create_world" then + tabbuilder.handle_create_world_buttons(fields) + end + + if tabbuilder.current_tab == "dialog_delete_world" then + tabbuilder.handle_delete_world_buttons(fields) + end + + if tabbuilder.current_tab == "singleplayer" then + tabbuilder.handle_singleplayer_buttons(fields) + end + + if tabbuilder.current_tab == "texture_packs" then + tabbuilder.handle_texture_pack_buttons(fields) + end + + if tabbuilder.current_tab == "multiplayer" then + tabbuilder.handle_multiplayer_buttons(fields) + end + + if tabbuilder.current_tab == "settings" then + tabbuilder.handle_settings_buttons(fields) + end + + if tabbuilder.current_tab == "server" then + tabbuilder.handle_server_buttons(fields) + end + + --tab buttons + tabbuilder.handle_tab_buttons(fields) + + --menubar buttons + menubar.handle_buttons(fields) + + if not tabbuilder.skipformupdate then + --update menu + update_menu() + else + tabbuilder.skipformupdate = false + end +end + +-------------------------------------------------------------------------------- +engine.event_handler = function(event) + if event == "MenuQuit" then + if tabbuilder.is_dialog then + tabbuilder.is_dialog = false + tabbuilder.show_buttons = true + tabbuilder.current_tab = engine.setting_get("main_menu_tab") + menu.update_gametype() + update_menu() + else + engine.close() + end + end +end + +-------------------------------------------------------------------------------- +function menu.update_gametype(reset) + local game = menu.lastgame() + + if reset or game == nil then + mm_texture.reset() + engine.set_topleft_text("") + filterlist.set_filtercriteria(worldlist,nil) + else + mm_texture.update(tabbuilder.current_tab,game) + engine.set_topleft_text(game.name) + filterlist.set_filtercriteria(worldlist,game.id) + end +end + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +-- menu startup +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +init_globals() +mm_texture.init() +menu.init() +tabbuilder.init() +menubar.refresh() +modstore.init() + +engine.sound_play("main_menu", true) + +update_menu() diff --git a/builtin/misc.lua b/builtin/misc.lua new file mode 100644 index 0000000..fd80aac --- /dev/null +++ b/builtin/misc.lua @@ -0,0 +1,121 @@ +-- Minetest: builtin/misc.lua + +-- +-- Misc. API functions +-- + +minetest.timers_to_add = {} +minetest.timers = {} +minetest.register_globalstep(function(dtime) + for _, timer in ipairs(minetest.timers_to_add) do + table.insert(minetest.timers, timer) + end + minetest.timers_to_add = {} + for index, timer in ipairs(minetest.timers) do + timer.time = timer.time - dtime + if timer.time <= 0 then + timer.func(unpack(timer.args or {})) + table.remove(minetest.timers,index) + end + end +end) + +function minetest.after(time, func, ...) + table.insert(minetest.timers_to_add, {time=time, func=func, args={...}}) +end + +function minetest.check_player_privs(name, privs) + local player_privs = minetest.get_player_privs(name) + local missing_privileges = {} + for priv, val in pairs(privs) do + if val then + if not player_privs[priv] then + table.insert(missing_privileges, priv) + end + end + end + if #missing_privileges > 0 then + return false, missing_privileges + end + return true, "" +end + +local player_list = {} + +minetest.register_on_joinplayer(function(player) + player_list[player:get_player_name()] = player +end) + +minetest.register_on_leaveplayer(function(player) + player_list[player:get_player_name()] = nil +end) + +function minetest.get_connected_players() + local temp_table = {} + for index, value in pairs(player_list) do + if value:is_player_connected() then + table.insert(temp_table, value) + end + end + return temp_table +end + +function minetest.hash_node_position(pos) + return (pos.z+32768)*65536*65536 + (pos.y+32768)*65536 + pos.x+32768 +end + +function minetest.get_item_group(name, group) + if not minetest.registered_items[name] or not + minetest.registered_items[name].groups[group] then + return 0 + end + return minetest.registered_items[name].groups[group] +end + +function minetest.get_node_group(name, group) + return minetest.get_item_group(name, group) +end + +function minetest.string_to_pos(value) + 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(minetest.string_to_pos("10.0, 5, -2").x == 10) +assert(minetest.string_to_pos("( 10.0, 5, -2)").z == -2) +assert(minetest.string_to_pos("asd, 5, -2)") == nil) + +function minetest.setting_get_pos(name) + local value = minetest.setting_get(name) + if not value then + return nil + end + return minetest.string_to_pos(value) +end + +-- To be overriden by protection mods +function minetest.is_protected(pos, name) + return false +end + +function minetest.record_protection_violation(pos, name) + for _, func in pairs(minetest.registered_on_protection_violation) do + func(pos, name) + end +end + diff --git a/builtin/misc_helpers.lua b/builtin/misc_helpers.lua new file mode 100644 index 0000000..55c5798 --- /dev/null +++ b/builtin/misc_helpers.lua @@ -0,0 +1,403 @@ +-- Minetest: builtin/misc_helpers.lua + +-------------------------------------------------------------------------------- +function basic_dump2(o) + if type(o) == "number" then + return tostring(o) + elseif type(o) == "string" then + return string.format("%q", o) + elseif type(o) == "boolean" then + return tostring(o) + elseif type(o) == "function" then + return "" + elseif type(o) == "userdata" then + return "" + elseif type(o) == "nil" then + return "nil" + else + error("cannot dump a " .. type(o)) + return nil + end +end + +-------------------------------------------------------------------------------- +function dump2(o, name, dumped) + name = name or "_" + dumped = dumped or {} + io.write(name, " = ") + if type(o) == "number" or type(o) == "string" or type(o) == "boolean" + or type(o) == "function" or type(o) == "nil" + or type(o) == "userdata" then + io.write(basic_dump2(o), "\n") + elseif type(o) == "table" then + if dumped[o] then + io.write(dumped[o], "\n") + else + dumped[o] = name + io.write("{}\n") -- new table + for k,v in pairs(o) do + local fieldname = string.format("%s[%s]", name, basic_dump2(k)) + dump2(v, fieldname, dumped) + end + end + else + error("cannot dump a " .. type(o)) + return nil + end +end + +-------------------------------------------------------------------------------- +function dump(o, dumped) + dumped = dumped or {} + if type(o) == "number" then + return tostring(o) + elseif type(o) == "string" then + return string.format("%q", o) + elseif type(o) == "table" then + if dumped[o] then + return "" + end + dumped[o] = true + local t = {} + for k,v in pairs(o) do + t[#t+1] = "[" .. dump(k, dumped) .. "] = " .. dump(v, dumped) + end + return "{" .. table.concat(t, ", ") .. "}" + elseif type(o) == "boolean" then + return tostring(o) + elseif type(o) == "function" then + return "" + elseif type(o) == "userdata" then + return "" + elseif type(o) == "nil" then + return "nil" + else + error("cannot dump a " .. type(o)) + return nil + end +end + +-------------------------------------------------------------------------------- +function string:split(sep) + local sep, fields = sep or ",", {} + local pattern = string.format("([^%s]+)", sep) + self:gsub(pattern, function(c) fields[#fields+1] = c end) + return fields +end + +-------------------------------------------------------------------------------- +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 explode_textlist_event(text) + + local retval = {} + retval.typ = "INV" + + local parts = text:split(":") + + if #parts == 2 then + retval.typ = parts[1]:trim() + retval.index= tonumber(parts[2]:trim()) + + if type(retval.index) ~= "number" then + retval.typ = "INV" + end + end + + return retval +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 + +local tbl = engine or minetest +function tbl.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 tbl.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 + table.insert(retval,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 + table.insert(retval,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 + table.insert(retval,last_line) + table.insert(retval,string.sub(text,current_idx)) + else + last_line = last_line .. " " .. string.sub(text,current_idx) + table.insert(retval,last_line) + end + + return retval +end + +-------------------------------------------------------------------------------- + +if minetest then + local dirs1 = { 9, 18, 7, 12 } + local dirs2 = { 20, 23, 22, 21 } + + function minetest.rotate_and_place(itemstack, placer, pointed_thing, infinitestacks, orient_flags) + orient_flags = orient_flags or {} + + local node = minetest.get_node(pointed_thing.under) + if not minetest.registered_nodes[node.name] + or not minetest.registered_nodes[node.name].on_rightclick then + + local above = pointed_thing.above + local under = pointed_thing.under + local pitch = placer:get_look_pitch() + local pname = minetest.get_node(under).name + local node = minetest.get_node(above) + local fdir = minetest.dir_to_facedir(placer:get_look_dir()) + local wield_name = itemstack:get_name() + local reg_node = minetest.registered_nodes[pname] + + if not reg_node or not reg_node.on_rightclick then + + local iswall = (above.x ~= under.x) or (above.z ~= under.z) + local isceiling = (above.x == under.x) and (above.z == under.z) + and (pitch > 0) + local pos1 = above + + if reg_node and reg_node.buildable_to then + pos1 = under + iswall = false + end + + reg_node = minetest.registered_nodes[minetest.get_node(pos1).name] + if not reg_node or not reg_node.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 + minetest.add_node(pos1, {name = wield_name, param2 = dirs1[fdir+1] }) + elseif isceiling then + if orient_flags.force_facedir then + minetest.add_node(pos1, {name = wield_name, param2 = 20 }) + else + minetest.add_node(pos1, {name = wield_name, param2 = dirs2[fdir+1] }) + end + else -- place right side up + if orient_flags.force_facedir then + minetest.add_node(pos1, {name = wield_name, param2 = 0 }) + else + minetest.add_node(pos1, {name = wield_name, param2 = fdir }) + end + end + + if not infinitestacks then + itemstack:take_item() + return itemstack + end + end + else + minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) + end + end + + +-------------------------------------------------------------------------------- +--Wrapper for rotate_and_place() to check for sneak and assume Creative mode +--implies infinite stacks when performing a 6d rotation. +-------------------------------------------------------------------------------- + + + minetest.rotate_node = function(itemstack, placer, pointed_thing) + minetest.rotate_and_place(itemstack, placer, pointed_thing, + minetest.setting_getbool("creative_mode"), + {invert_wall = placer:get_player_control().sneak}) + return itemstack + end +end + +-------------------------------------------------------------------------------- +-- mainmenu only functions +-------------------------------------------------------------------------------- +if engine ~= nil then + engine.get_game = function(index) + local games = game.get_games() + + if index > 0 and index <= #games then + return games[index] + end + + return nil + end + + function fgettext(text, ...) + text = engine.gettext(text) + local arg = {n=select('#', ...), ...} + if arg.n >= 1 then + -- Insert positional parameters ($1, $2, ...) + result = '' + pos = 1 + while pos <= text:len() do + newpos = text:find('[$]', pos) + if newpos == nil then + result = result .. text:sub(pos) + pos = text:len() + 1 + else + 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 engine.formspec_escape(text) + end +end +-------------------------------------------------------------------------------- +-- core only fct +-------------------------------------------------------------------------------- +if minetest ~= nil then + -------------------------------------------------------------------------------- + function minetest.pos_to_string(pos) + return "(" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ")" + end +end + diff --git a/builtin/misc_register.lua b/builtin/misc_register.lua new file mode 100644 index 0000000..bb2c62a --- /dev/null +++ b/builtin/misc_register.lua @@ -0,0 +1,348 @@ +-- Minetest: builtin/misc_register.lua + +-- +-- Make raw registration functions inaccessible to anyone except this file +-- + +local register_item_raw = minetest.register_item_raw +minetest.register_item_raw = nil + +local register_alias_raw = minetest.register_alias_raw +minetest.register_item_raw = nil + +-- +-- Item / entity / ABM registration functions +-- + +minetest.registered_abms = {} +minetest.registered_entities = {} +minetest.registered_items = {} +minetest.registered_nodes = {} +minetest.registered_craftitems = {} +minetest.registered_tools = {} +minetest.registered_aliases = {} + +-- For tables that are indexed by item name: +-- If table[X] does not exist, default to table[minetest.registered_aliases[X]] +local function set_alias_metatable(table) + setmetatable(table, { + __index = function(name) + return rawget(table, minetest.registered_aliases[name]) + end + }) +end +set_alias_metatable(minetest.registered_items) +set_alias_metatable(minetest.registered_nodes) +set_alias_metatable(minetest.registered_craftitems) +set_alias_metatable(minetest.registered_tools) + +-- 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 + -- Escape the modname prefix enforcement mechanism + return name:sub(2) + else + -- Modname prefix enforcement + local expected_prefix = minetest.get_current_modname() .. ":" + if name:sub(1, #expected_prefix) ~= expected_prefix then + error("Name " .. name .. " does not follow naming conventions: " .. + "\"modname:\" or \":\" prefix required") + end + local subname = name:sub(#expected_prefix+1) + if subname:find("[^abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_]") then + error("Name " .. name .. " does not follow naming conventions: " .. + "contains unallowed characters") + end + return name + end +end + +function minetest.register_abm(spec) + -- Add to minetest.registered_abms + minetest.registered_abms[#minetest.registered_abms+1] = spec +end + +function minetest.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 minetest.registered_entities + minetest.registered_entities[name] = prototype +end + +function minetest.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 + end + setmetatable(itemdef, {__index = minetest.nodedef_default}) + minetest.registered_nodes[itemdef.name] = itemdef + elseif itemdef.type == "craft" then + setmetatable(itemdef, {__index = minetest.craftitemdef_default}) + minetest.registered_craftitems[itemdef.name] = itemdef + elseif itemdef.type == "tool" then + setmetatable(itemdef, {__index = minetest.tooldef_default}) + minetest.registered_tools[itemdef.name] = itemdef + elseif itemdef.type == "none" then + setmetatable(itemdef, {__index = minetest.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 + minetest.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 + minetest.register_craft({ + type="fuel", + recipe=itemdef.name, + burntime=itemdef.furnace_burntime + }) + end + -- END Legacy stuff + + -- Disable all further modifications + getmetatable(itemdef).__newindex = {} + + --minetest.log("Registering item: " .. itemdef.name) + minetest.registered_items[itemdef.name] = itemdef + minetest.registered_aliases[itemdef.name] = nil + register_item_raw(itemdef) +end + +function minetest.register_node(name, nodedef) + nodedef.type = "node" + minetest.register_item(name, nodedef) +end + +function minetest.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 + + minetest.register_item(name, craftitemdef) +end + +function minetest.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 + + minetest.register_item(name, tooldef) +end + +function minetest.register_alias(name, convert_to) + if forbidden_item_names[name] then + error("Unable to register alias: Name is forbidden: " .. name) + end + if minetest.registered_items[name] ~= nil then + minetest.log("WARNING: Not registering alias, item with same name" .. + " is already defined: " .. name .. " -> " .. convert_to) + else + --minetest.log("Registering alias: " .. name .. " -> " .. convert_to) + minetest.registered_aliases[name] = convert_to + register_alias_raw(name, convert_to) + end +end + +local register_biome_raw = minetest.register_biome +minetest.registered_biomes = {} +function minetest.register_biome(biome) + minetest.registered_biomes[biome.name] = biome + register_biome_raw(biome) +end + +function minetest.on_craft(itemstack, player, old_craft_list, craft_inv) + for _, func in ipairs(minetest.registered_on_crafts) do + itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack + end + return itemstack +end + +function minetest.craft_predict(itemstack, player, old_craft_list, craft_inv) + for _, func in ipairs(minetest.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 + minetest.registered_aliases[name] = "" + register_alias_raw(name, "") +end + + +-- Deprecated: +-- Aliases for minetest.register_alias (how ironic...) +--minetest.alias_node = minetest.register_alias +--minetest.alias_tool = minetest.register_alias +--minetest.alias_craftitem = minetest.register_alias + +-- +-- Built-in node definitions. Also defined in C. +-- + +minetest.register_item(":unknown", { + type = "none", + description = "Unknown Item", + inventory_image = "unknown_item.png", + on_place = minetest.item_place, + on_drop = minetest.item_drop, + groups = {not_in_creative_inventory=1}, + diggable = true, +}) + +minetest.register_node(":air", { + description = "Air (you hacker you!)", + inventory_image = "unknown_node.png", + wield_image = "unknown_node.png", + drawtype = "airlike", + paramtype = "light", + sunlight_propagates = true, + walkable = false, + pointable = false, + diggable = false, + buildable_to = true, + air_equivalent = true, + drop = "", + groups = {not_in_creative_inventory=1}, +}) + +minetest.register_node(":ignore", { + description = "Ignore (you hacker you!)", + inventory_image = "unknown_node.png", + wield_image = "unknown_node.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) +minetest.register_item(":", { + type = "none", + groups = {not_in_creative_inventory=1}, +}) + +-- +-- Callback registration +-- + +local function make_registration() + local t = {} + local registerfunc = function(func) table.insert(t, func) end + return t, registerfunc +end + +local function make_registration_reverse() + local t = {} + local registerfunc = function(func) table.insert(t, 1, func) end + return t, registerfunc +end + +minetest.registered_on_chat_messages, minetest.register_on_chat_message = make_registration() +minetest.registered_globalsteps, minetest.register_globalstep = make_registration() +minetest.registered_on_mapgen_inits, minetest.register_on_mapgen_init = make_registration() +minetest.registered_on_shutdown, minetest.register_on_shutdown = make_registration() +minetest.registered_on_punchnodes, minetest.register_on_punchnode = make_registration() +minetest.registered_on_placenodes, minetest.register_on_placenode = make_registration() +minetest.registered_on_dignodes, minetest.register_on_dignode = make_registration() +minetest.registered_on_generateds, minetest.register_on_generated = make_registration() +minetest.registered_on_newplayers, minetest.register_on_newplayer = make_registration() +minetest.registered_on_dieplayers, minetest.register_on_dieplayer = make_registration() +minetest.registered_on_respawnplayers, minetest.register_on_respawnplayer = make_registration() +minetest.registered_on_joinplayers, minetest.register_on_joinplayer = make_registration() +minetest.registered_on_leaveplayers, minetest.register_on_leaveplayer = make_registration() +minetest.registered_on_player_receive_fields, minetest.register_on_player_receive_fields = make_registration_reverse() +minetest.registered_on_cheats, minetest.register_on_cheat = make_registration() +minetest.registered_on_crafts, minetest.register_on_craft = make_registration() +minetest.registered_craft_predicts, minetest.register_craft_predict = make_registration() +minetest.registered_on_protection_violation, minetest.register_on_protection_violation = make_registration() + diff --git a/builtin/mm_menubar.lua b/builtin/mm_menubar.lua new file mode 100644 index 0000000..2e4d5f8 --- /dev/null +++ b/builtin/mm_menubar.lua @@ -0,0 +1,80 @@ +--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. + +menubar = {} + +-------------------------------------------------------------------------------- +function menubar.handle_buttons(fields) + for i=1,#menubar.buttons,1 do + if fields[menubar.buttons[i].btn_name] ~= nil then + menu.last_game = menubar.buttons[i].index + engine.setting_set("main_menu_last_game_idx",menu.last_game) + menu.update_gametype() + end + end +end + +-------------------------------------------------------------------------------- +function menubar.refresh() + + menubar.formspec = "box[-0.3,5.625;12.4,1.2;#000000]" .. + "box[-0.3,5.6;12.4,0.05;#FFFFFF]" + menubar.buttons = {} + + local button_base = -0.08 + + local maxbuttons = #gamemgr.games + + if maxbuttons > 11 then + maxbuttons = 11 + end + + for i=1,maxbuttons,1 do + + local btn_name = "menubar_btn_" .. gamemgr.games[i].id + local buttonpos = button_base + (i-1) * 1.1 + if gamemgr.games[i].menuicon_path ~= nil and + gamemgr.games[i].menuicon_path ~= "" then + + menubar.formspec = menubar.formspec .. + "image_button[" .. buttonpos .. ",5.72;1.165,1.175;" .. + engine.formspec_escape(gamemgr.games[i].menuicon_path) .. ";" .. + btn_name .. ";;true;false]" + 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) + + local text = part1 .. "\n" .. part2 + if part3 ~= nil and + part3 ~= "" then + text = text .. "\n" .. part3 + end + menubar.formspec = menubar.formspec .. + "image_button[" .. buttonpos .. ",5.72;1.165,1.175;;" ..btn_name .. + ";" .. text .. ";true;true]" + end + + local toadd = { + btn_name = btn_name, + index = i, + } + + table.insert(menubar.buttons,toadd) + end +end diff --git a/builtin/mm_textures.lua b/builtin/mm_textures.lua new file mode 100644 index 0000000..998fc21 --- /dev/null +++ b/builtin/mm_textures.lua @@ -0,0 +1,146 @@ +--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 = engine.get_texturepath() .. DIR_DELIM .. "base" .. + DIR_DELIM .. "pack" .. DIR_DELIM + mm_texture.basetexturedir = mm_texture.defaulttexturedir + + mm_texture.texturepack = engine.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") + engine.set_clouds(false) + + mm_texture.set_generic("footer") + mm_texture.set_generic("header") + + if not have_bg and + engine.setting_getbool("enable_clouds") then + engine.set_clouds(true) + 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") + engine.set_clouds(false) + + if not have_bg and + engine.setting_getbool("enable_clouds") then + engine.set_clouds(true) + end + + mm_texture.set_game("footer",gamedetails) + mm_texture.set_game("header",gamedetails) + + mm_texture.gameid = gamedetails.id +end + +-------------------------------------------------------------------------------- +function mm_texture.clear(identifier) + engine.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 engine.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 engine.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 engine.set_background(identifier,path) then + return true + end + end + + local path = gamedetails.path .. DIR_DELIM .."menu" .. + DIR_DELIM .. identifier .. ".png" + if engine.set_background(identifier,path) then + return true + end + + return false +end diff --git a/builtin/modmgr.lua b/builtin/modmgr.lua new file mode 100644 index 0000000..1f19ac6 --- /dev/null +++ b/builtin/modmgr.lua @@ -0,0 +1,1128 @@ +--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 = engine.get_dirlist(path,true) + for i=1,#mods,1 do + local toadd = {} + local modpackfile = nil + + toadd.name = mods[i] + toadd.path = path .. DIR_DELIM .. mods[i] .. DIR_DELIM + if modpack ~= nil and + modpack ~= "" then + toadd.modpack = modpack + else + local filename = path .. DIR_DELIM .. mods[i] .. DIR_DELIM .. "modpack.txt" + local error = nil + modpackfile,error = io.open(filename,"r") + end + + if modpackfile ~= nil then + modpackfile:close() + toadd.is_modpack = true + table.insert(retval,toadd) + get_mods(path .. DIR_DELIM .. mods[i],retval,mods[i]) + else + table.insert(retval,toadd) + end + end +end + +--modmanager implementation +modmgr = {} + +-------------------------------------------------------------------------------- +function modmgr.extract(modfile) + if modfile.type == "zip" then + local tempfolder = os.tempfolder() + + if tempfolder ~= nil and + tempfodler ~= "" then + engine.create_dir(tempfolder) + engine.extract_zip(modfile.name,tempfolder) + return tempfolder + end + end +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 = engine.get_dirlist(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.tab() + + if modmgr.global_mods == nil then + modmgr.refresh_globals() + end + + if modmgr.selected_mod == nil then + modmgr.selected_mod = 1 + end + + local retval = + "vertlabel[0,-0.25;".. fgettext("MODS") .. "]" .. + "label[0.8,-0.25;".. fgettext("Installed Mods:") .. "]" .. + "textlist[0.75,0.25;4.5,4;modlist;" .. + modmgr.render_modlist(modmgr.global_mods) .. + ";" .. modmgr.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") .. "]" .. + "button[2.45,4.85;3.05,0.5;btn_mod_mgr_download;".. fgettext("Online mod repository") .. "]" + + local selected_mod = nil + + if filterlist.size(modmgr.global_mods) >= modmgr.selected_mod then + selected_mod = filterlist.get_list(modmgr.global_mods)[modmgr.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 + screenshotfile,error = io.open(screenshotfilename,"r") + if error == nil then + screenshotfile:close() + modscreenshot = screenshotfilename + end + + if modscreenshot == nil then + modscreenshot = modstore.basetexturedir .. "no_screenshot.png" + end + + retval = retval + .. "image[5.5,0;3,2;" .. engine.formspec_escape(modscreenshot) .. "]" + .. "label[8.25,0.6;" .. selected_mod.name .. "]" + + local descriptionlines = nil + error = nil + local descriptionfilename = selected_mod.path .. "description.txt" + descriptionfile,error = io.open(descriptionfilename,"r") + if error == nil then + descriptiontext = descriptionfile:read("*all") + + descriptionlines = engine.splittext(descriptiontext,42) + descriptionfile:close() + else + descriptionlines = {} + table.insert(descriptionlines,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 .. engine.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 + + retval = retval .. ",Depends:," + + toadd = modmgr.get_dependencies(selected_mod.path) + + retval = retval .. toadd .. ";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 + +-------------------------------------------------------------------------------- +function modmgr.dialog_rename_modpack() + + local mod = filterlist.get_list(modmgr.global_mods)[modmgr.selected_mod] + + local retval = + "label[1.75,1;".. fgettext("Rename Modpack:") .. "]".. + "field[4.5,1.4;6,0.5;te_modpack_name;;" .. + mod.name .. + "]" .. + "button[5,4.2;2.6,0.5;dlg_rename_modpack_confirm;".. + fgettext("Accept") .. "]" .. + "button[7.5,4.2;2.8,0.5;dlg_rename_modpack_cancel;".. + fgettext("Cancel") .. "]" + + return retval +end + +-------------------------------------------------------------------------------- +function modmgr.precheck() + + if modmgr.world_config_selected_world == nil then + modmgr.world_config_selected_world = 1 + end + + if modmgr.world_config_selected_mod == nil then + modmgr.world_config_selected_mod = 1 + end + + if modmgr.hide_gamemods == nil then + modmgr.hide_gamemods = true + end + + if modmgr.hide_modpackcontents == nil then + modmgr.hide_modpackcontents = true + end +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 = filterlist.get_list(render_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 = filterlist.get_raw_list(render_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.dialog_configure_world() + modmgr.precheck() + + local worldspec = engine.get_worlds()[modmgr.world_config_selected_world] + local mod = filterlist.get_list(modmgr.modlist)[modmgr.world_config_selected_mod] + + local retval = + "size[11,6.5]" .. + "label[0.5,-0.25;" .. fgettext("World:") .. "]" .. + "label[1.75,-0.25;" .. worldspec.name .. "]" + + if modmgr.hide_gamemods then + retval = retval .. "checkbox[0,5.75;cb_hide_gamemods;" .. fgettext("Hide Game") .. ";true]" + else + retval = retval .. "checkbox[0,5.75;cb_hide_gamemods;" .. fgettext("Hide Game") .. ";false]" + end + + if modmgr.hide_modpackcontents then + retval = retval .. "checkbox[2,5.75;cb_hide_mpcontent;" .. fgettext("Hide mp content") .. ";true]" + else + retval = retval .. "checkbox[2,5.75;cb_hide_mpcontent;" .. fgettext("Hide mp content") .. ";false]" + end + + if mod == nil then + mod = {name=""} + end + retval = retval .. + "label[0,0.45;" .. fgettext("Mod:") .. "]" .. + "label[0.75,0.45;" .. mod.name .. "]" .. + "label[0,1;" .. fgettext("Depends:") .. "]" .. + "textlist[0,1.5;5,4.25;world_config_depends;" .. + modmgr.get_dependencies(mod.path) .. ";0]" .. + "button[9.25,6.35;2,0.5;btn_config_world_save;" .. fgettext("Save") .. "]" .. + "button[7.4,6.35;2,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 = filterlist.get_raw_list(modmgr.modlist) + + 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,0.5;btn_mp_enable;" .. fgettext("Enable MP") .. "]" + else + retval = retval .. "button[5.5,-0.125;2,0.5;btn_mp_disable;" .. fgettext("Disable MP") .. "]" + end + else + if mod.enabled then + retval = retval .. "checkbox[5.5,-0.375;cb_mod_enable;" .. fgettext("enabled") .. ";true]" + else + retval = retval .. "checkbox[5.5,-0.375;cb_mod_enable;" .. fgettext("enabled") .. ";false]" + end + end + end + + retval = retval .. + "button[8.5,-0.125;2.5,0.5;btn_all_mods;" .. fgettext("Enable all") .. "]" .. + "textlist[5.5,0.5;5.5,5.75;world_config_modlist;" + + retval = retval .. modmgr.render_modlist(modmgr.modlist) + + retval = retval .. ";" .. modmgr.world_config_selected_mod .."]" + + return retval +end + +-------------------------------------------------------------------------------- +function modmgr.handle_buttons(tab,fields) + + local retval = nil + + if tab == "mod_mgr" then + retval = modmgr.handle_modmgr_buttons(fields) + end + + if tab == "dialog_rename_modpack" then + retval = modmgr.handle_rename_modpack_buttons(fields) + end + + if tab == "dialog_delete_mod" then + retval = modmgr.handle_delete_mod_buttons(fields) + end + + if tab == "dialog_configure_world" then + retval = modmgr.handle_configure_world_buttons(fields) + end + + return retval +end + +-------------------------------------------------------------------------------- +function modmgr.get_dependencies(modfolder) + local toadd = "" + if modfolder ~= nil then + local filename = modfolder .. + DIR_DELIM .. "depends.txt" + + local dependencyfile = io.open(filename,"r") + + if dependencyfile then + local dependency = dependencyfile:read("*l") + while dependency do + if toadd ~= "" then + toadd = toadd .. "," + end + toadd = toadd .. dependency + dependency = dependencyfile:read() + end + dependencyfile:close() + end + end + + return toadd +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 + else + worldconfig.global_mods[key] = engine.is_yes(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.handle_modmgr_buttons(fields) + local retval = { + tab = nil, + is_dialog = nil, + show_buttons = nil, + } + + if fields["modlist"] ~= nil then + local event = explode_textlist_event(fields["modlist"]) + modmgr.selected_mod = event.index + end + + if fields["btn_mod_mgr_install_local"] ~= nil then + engine.show_file_open_dialog("mod_mgt_open_dlg",fgettext("Select Mod File:")) + end + + if fields["btn_mod_mgr_download"] ~= nil then + modstore.update_modlist() + retval.current_tab = "dialog_modstore_unsorted" + retval.is_dialog = true + retval.show_buttons = false + return retval + end + + if fields["btn_mod_mgr_rename_modpack"] ~= nil then + retval.current_tab = "dialog_rename_modpack" + retval.is_dialog = true + retval.show_buttons = false + return retval + end + + if fields["btn_mod_mgr_delete_mod"] ~= nil then + retval.current_tab = "dialog_delete_mod" + retval.is_dialog = true + retval.show_buttons = false + return retval + 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) + end + + return nil; +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\"", 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 = engine.get_modpath() .. DIR_DELIM .. clean_path + if not engine.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 = engine.get_modpath() .. DIR_DELIM .. targetfolder + engine.copy_dir(basefolder.path,targetpath) + else + gamedata.errormessage = fgettext("Install Mod: unable to find real modname for: $1", modfilename) + end + end + + engine.delete_dir(modpath) + + modmgr.refresh_globals() + +end + +-------------------------------------------------------------------------------- +function modmgr.handle_rename_modpack_buttons(fields) + + if fields["dlg_rename_modpack_confirm"] ~= nil then + local mod = filterlist.get_list(modmgr.global_mods)[modmgr.selected_mod] + local oldpath = engine.get_modpath() .. DIR_DELIM .. mod.name + local targetpath = engine.get_modpath() .. DIR_DELIM .. fields["te_modpack_name"] + engine.copy_dir(oldpath,targetpath,false) + modmgr.refresh_globals() + modmgr.selected_mod = filterlist.get_current_index(modmgr.global_mods, + filterlist.raw_index_by_uid(modmgr.global_mods, fields["te_modpack_name"])) + end + + return { + is_dialog = false, + show_buttons = true, + current_tab = engine.setting_get("main_menu_tab") + } +end +-------------------------------------------------------------------------------- +function modmgr.handle_configure_world_buttons(fields) + if fields["world_config_modlist"] ~= nil then + local event = explode_textlist_event(fields["world_config_modlist"]) + modmgr.world_config_selected_mod = event.index + + if event.typ == "DCL" then + modmgr.world_config_enable_mod(nil) + end + end + + if fields["key_enter"] ~= nil then + modmgr.world_config_enable_mod(nil) + end + + if fields["cb_mod_enable"] ~= nil then + local toset = engine.is_yes(fields["cb_mod_enable"]) + modmgr.world_config_enable_mod(toset) + end + + if fields["btn_mp_enable"] ~= nil or + fields["btn_mp_disable"] then + local toset = (fields["btn_mp_enable"] ~= nil) + modmgr.world_config_enable_mod(toset) + end + + if fields["cb_hide_gamemods"] ~= nil then + local current = filterlist.get_filtercriteria(modmgr.modlist) + + if current == nil then + current = {} + end + + if engine.is_yes(fields["cb_hide_gamemods"]) then + current.hide_game = true + modmgr.hide_gamemods = true + else + current.hide_game = false + modmgr.hide_gamemods = false + end + + filterlist.set_filtercriteria(modmgr.modlist,current) + end + + if fields["cb_hide_mpcontent"] ~= nil then + local current = filterlist.get_filtercriteria(modmgr.modlist) + + if current == nil then + current = {} + end + + if engine.is_yes(fields["cb_hide_mpcontent"]) then + current.hide_modpackcontents = true + modmgr.hide_modpackcontents = true + else + current.hide_modpackcontents = false + modmgr.hide_modpackcontents = false + end + + filterlist.set_filtercriteria(modmgr.modlist,current) + end + + if fields["btn_config_world_save"] then + local worldspec = engine.get_worlds()[modmgr.world_config_selected_world] + + local filename = worldspec.path .. + DIR_DELIM .. "world.mt" + + local worldfile = Settings(filename) + local mods = worldfile:to_table() + + local rawlist = filterlist.get_raw_list(modmgr.modlist) + + local i,mod + for i,mod in ipairs(rawlist) do + if not mod.is_modpack and + mod.typ ~= "game_mod" then + if mod.enabled then + worldfile:set("load_mod_"..mod.name, "true") + else + worldfile:set("load_mod_"..mod.name, "false") + 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 + engine.log("error", "Failed to write world config file") + end + + modmgr.modlist = nil + modmgr.worldconfig = nil + + return { + is_dialog = false, + show_buttons = true, + current_tab = engine.setting_get("main_menu_tab") + } + end + + if fields["btn_config_world_cancel"] then + + modmgr.worldconfig = nil + + return { + is_dialog = false, + show_buttons = true, + current_tab = engine.setting_get("main_menu_tab") + } + end + + if fields["btn_all_mods"] then + local list = filterlist.get_raw_list(modmgr.modlist) + + for i=1,#list,1 do + if list[i].typ ~= "game_mod" and + not list[i].is_modpack then + list[i].enabled = true + end + end + end + + + + return nil +end +-------------------------------------------------------------------------------- +function modmgr.world_config_enable_mod(toset) + local mod = filterlist.get_list(modmgr.modlist) + [engine.get_textlist_index("world_config_modlist")] + + 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 = filterlist.get_raw_list(modmgr.modlist) + 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 +-------------------------------------------------------------------------------- +function modmgr.handle_delete_mod_buttons(fields) + local mod = filterlist.get_list(modmgr.global_mods)[modmgr.selected_mod] + + if fields["dlg_delete_mod_confirm"] ~= nil then + + if mod.path ~= nil and + mod.path ~= "" and + mod.path ~= engine.get_modpath() then + if not engine.delete_dir(mod.path) then + gamedata.errormessage = fgettext("Modmgr: failed to delete \"$1\"", mod.path) + end + modmgr.refresh_globals() + else + gamedata.errormessage = fgettext("Modmgr: invalid modpath \"$1\"", mod.path) + end + end + + return { + is_dialog = false, + show_buttons = true, + current_tab = engine.setting_get("main_menu_tab") + } +end + +-------------------------------------------------------------------------------- +function modmgr.dialog_delete_mod() + + local mod = filterlist.get_list(modmgr.global_mods)[modmgr.selected_mod] + + local retval = + "field[1.75,1;10,3;;" .. fgettext("Are you sure you want to delete \"$1\"?", mod.name) .. ";]".. + "button[4,4.2;1,0.5;dlg_delete_mod_confirm;" .. fgettext("Yes") .. "]" .. + "button[6.5,4.2;3,0.5;dlg_delete_mod_cancel;" .. fgettext("No of course not!") .. "]" + + return retval +end + +-------------------------------------------------------------------------------- +function modmgr.preparemodlist(data) + local retval = {} + + local global_mods = {} + local game_mods = {} + + --read global mods + local modpath = engine.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" + table.insert(retval,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" + table.insert(retval,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 then + element = retval[i] + break + end + end + if element ~= nil then + element.enabled = engine.is_yes(value) + else + engine.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found") + end + end + end + + return retval +end + +-------------------------------------------------------------------------------- +function modmgr.init_worldconfig() + modmgr.precheck() + local worldspec = engine.get_worlds()[modmgr.world_config_selected_world] + + if worldspec ~= nil then + --read worldconfig + modmgr.worldconfig = modmgr.get_worldconfig(worldspec.path) + + if modmgr.worldconfig.id == nil or + modmgr.worldconfig.id == "" then + modmgr.worldconfig = nil + return false + end + + modmgr.modlist = 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= worldspec.path, + gameid = worldspec.gameid } + ) + + filterlist.set_filtercriteria(modmgr.modlist, { + hide_game=modmgr.hide_gamemods, + hide_modpackcontents= modmgr.hide_modpackcontents + }) + filterlist.add_sort_mechanism(modmgr.modlist, "alphabetic", sort_mod_list) + filterlist.set_sortmode(modmgr.modlist, "alphabetic") + + return true + end + + return false +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.gettab(name) + local retval = "" + + if name == "mod_mgr" then + retval = retval .. modmgr.tab() + end + + if name == "dialog_rename_modpack" then + retval = retval .. modmgr.dialog_rename_modpack() + end + + if name == "dialog_delete_mod" then + retval = retval .. modmgr.dialog_delete_mod() + end + + if name == "dialog_configure_world" then + retval = retval .. modmgr.dialog_configure_world() + end + + return retval +end + +-------------------------------------------------------------------------------- +function modmgr.mod_exists(basename) + + if modmgr.global_mods == nil then + modmgr.refresh_globals() + end + + if filterlist.raw_index_by_uid(modmgr.global_mods,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 < 1 or idx > filterlist.size(modmgr.global_mods) then + return nil + end + + return filterlist.get_list(modmgr.global_mods)[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 + {} + ) + filterlist.add_sort_mechanism(modmgr.global_mods, "alphabetic", sort_mod_list) + filterlist.set_sortmode(modmgr.global_mods, "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 diff --git a/builtin/modstore.lua b/builtin/modstore.lua new file mode 100644 index 0000000..b364ce6 --- /dev/null +++ b/builtin/modstore.lua @@ -0,0 +1,274 @@ +--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 modstore.init() + modstore.tabnames = {} + + table.insert(modstore.tabnames,"dialog_modstore_unsorted") + table.insert(modstore.tabnames,"dialog_modstore_search") + + modstore.modsperpage = 5 + + modstore.basetexturedir = engine.get_texturepath() .. DIR_DELIM .. "base" .. + DIR_DELIM .. "pack" .. DIR_DELIM + + modstore.current_list = nil + + modstore.details_cache = {} +end +-------------------------------------------------------------------------------- +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 modstore.gettab(tabname) + local retval = "" + + local is_modstore_tab = false + + if tabname == "dialog_modstore_unsorted" then + retval = modstore.getmodlist(modstore.modlist_unsorted) + is_modstore_tab = true + end + + if tabname == "dialog_modstore_search" then + + + is_modstore_tab = true + end + + if is_modstore_tab then + return modstore.tabheader(tabname) .. retval + end + + if tabname == "modstore_mod_installed" then + return "size[6,2]label[0.25,0.25;Mod: " .. modstore.lastmodtitle .. + " installed successfully]" .. + "button[2.5,1.5;1,0.5;btn_confirm_mod_successfull;ok]" + end + + return "" +end + +-------------------------------------------------------------------------------- +function modstore.tabheader(tabname) + local retval = "size[12,9.25]" + retval = retval .. "tabheader[-0.3,-0.99;modstore_tab;" .. + "Unsorted,Search;" .. + modstore.nametoindex(tabname) .. ";true;false]" + + return retval +end + +-------------------------------------------------------------------------------- +function modstore.handle_buttons(current_tab,fields) + + modstore.lastmodtitle = "" + + if fields["modstore_tab"] then + local index = tonumber(fields["modstore_tab"]) + + if index > 0 and + index <= #modstore.tabnames then + return { + current_tab = modstore.tabnames[index], + is_dialog = true, + show_buttons = false + } + end + + modstore.modlist_page = 0 + end + + 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 + end + + if fields["btn_modstore_page_down"] then + if modstore.current_list ~= nil and + modstore.current_list.page #list.data) then + endmod = #list.data + end + + for i=(list.page * modstore.modsperpage) +1, endmod, 1 do + --getmoddetails + local details = modstore.get_details(list.data[i].id) + + if details ~= nil then + local screenshot_ypos = (i-1 - (list.page * modstore.modsperpage))*1.9 +0.2 + + retval = retval .. "box[0," .. screenshot_ypos .. ";11.4,1.75;#FFFFFF]" + + --screenshot + if details.screenshot_url ~= nil and + details.screenshot_url ~= "" then + if list.data[i].texturename == nil then + local fullurl = engine.setting_get("modstore_download_url") .. + details.screenshot_url + local filename = os.tempfolder() + + if engine.download_file(fullurl,filename) then + list.data[i].texturename = filename + end + end + end + + if list.data[i].texturename == nil then + list.data[i].texturename = modstore.basetexturedir .. "no_screenshot.png" + end + + retval = retval .. "image[0,".. screenshot_ypos .. ";3,2;" .. + engine.formspec_escape(list.data[i].texturename) .. "]" + + --title + author + retval = retval .."label[2.75," .. screenshot_ypos .. ";" .. + engine.formspec_escape(details.title) .. " (" .. details.author .. ")]" + + --description + local descriptiony = screenshot_ypos + 0.5 + retval = retval .. "textarea[3," .. descriptiony .. ";6.5,1.55;;" .. + engine.formspec_escape(details.description) .. ";]" + --rating + local ratingy = screenshot_ypos + 0.6 + retval = retval .."label[10.1," .. ratingy .. ";" .. + fgettext("Rating") .. ": " .. details.rating .."]" + + --install button + local buttony = screenshot_ypos + 1.2 + local buttonnumber = (i - (list.page * modstore.modsperpage)) + retval = retval .."button[9.6," .. buttony .. ";2,0.5;btn_install_mod_" .. buttonnumber .. ";" + + if modmgr.mod_exists(details.basename) then + retval = retval .. fgettext("re-Install") .."]" + else + retval = retval .. fgettext("Install") .."]" + end + end + end + + modstore.current_list = list + + return retval +end + +-------------------------------------------------------------------------------- +function modstore.get_details(modid) + + if modstore.details_cache[modid] ~= nil then + return modstore.details_cache[modid] + end + + local retval = engine.get_modstore_details(tostring(modid)) + modstore.details_cache[modid] = retval + return retval +end + diff --git a/builtin/privileges.lua b/builtin/privileges.lua new file mode 100644 index 0000000..8dd06b2 --- /dev/null +++ b/builtin/privileges.lua @@ -0,0 +1,52 @@ +-- Minetest: builtin/privileges.lua + +-- +-- Privileges +-- + +minetest.registered_privileges = {} + +function minetest.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) + minetest.registered_privileges[name] = def +end + +minetest.register_privilege("interact", "Can interact with things and modify the world") +minetest.register_privilege("teleport", "Can use /teleport command") +minetest.register_privilege("bring", "Can teleport other players") +minetest.register_privilege("settime", "Can use /time") +minetest.register_privilege("privs", "Can modify privileges") +minetest.register_privilege("basic_privs", "Can modify 'shout' and 'interact' privileges") +minetest.register_privilege("server", "Can do server maintenance stuff") +minetest.register_privilege("shout", "Can speak in chat") +minetest.register_privilege("ban", "Can ban and unban players") +minetest.register_privilege("give", "Can use /give and /giveme") +minetest.register_privilege("password", "Can use /setpassword and /clearpassword") +minetest.register_privilege("fly", { + description = "Can fly using the free_move mode", + give_to_singleplayer = false, +}) +minetest.register_privilege("fast", { + description = "Can walk fast using the fast_move mode", + give_to_singleplayer = false, +}) +minetest.register_privilege("noclip", { + description = "Can fly through walls", + give_to_singleplayer = false, +}) +minetest.register_privilege("rollback", "Can use the rollback functionality") + diff --git a/builtin/serialize.lua b/builtin/serialize.lua new file mode 100644 index 0000000..165cc67 --- /dev/null +++ b/builtin/serialize.lua @@ -0,0 +1,207 @@ +-- Minetest: builtin/serialize.lua + +-- https://github.com/fab13n/metalua/blob/no-dll/src/lib/serialize.lua +-- Copyright (c) 2006-2997 Fabien Fleutot +-- License: MIT +-------------------------------------------------------------------------------- +-- 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: +-- * strings, numbers, booleans, nil +-- * tables thereof. Tables can have shared part, but can't be recursive yet. +-- Caveat: metatables and environments aren't saved. +-------------------------------------------------------------------------------- + +local no_identity = { number=1, boolean=1, string=1, ['nil']=1 } + +function minetest.serialize(x) + + local gensym_max = 0 -- index of the gensym() symbol generator + local seen_once = { } -- element->true set of elements seen exactly once in the table + local multiple = { } -- element->varname set of elements seen more than once + local nested = { } -- transient, set of elements currently being traversed + local nest_points = { } + local nest_patches = { } + + local function gensym() + gensym_max = gensym_max + 1 ; return gensym_max + end + + ----------------------------------------------------------------------------- + -- 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 created by mark_nest_point: + -- * nest_points [parent] associates all keys and values in table parent which + -- create a nest_point with boolean `true' + -- * nest_patches contain a list of { parent, key, value } tuples creating + -- a nest point. They're all dumped after all the other table operations + -- have been performed. + -- + -- mark_nest_point (p, k, v) fills tables nest_points and nest_patches with + -- informations required to remember that key/value (k,v) create a nest point + -- in table parent. It also marks `parent' as occuring multiple times, since + -- several references to it will be required in order to patch the nest + -- points. + ----------------------------------------------------------------------------- + local function mark_nest_point (parent, k, v) + local nk, nv = nested[k], nested[v] + assert (not nk or seen_once[k] or multiple[k]) + assert (not nv or seen_once[v] or multiple[v]) + local mode = (nk and nv and "kv") or (nk and "k") or ("v") + local parent_np = nest_points [parent] + local pair = { k, v } + if not parent_np then parent_np = { }; nest_points [parent] = parent_np end + parent_np [k], parent_np [v] = nk, nv + table.insert (nest_patches, { parent, k, v }) + seen_once [parent], multiple [parent] = nil, true + end + + ----------------------------------------------------------------------------- + -- First pass, list the tables and functions which appear more than once in x + ----------------------------------------------------------------------------- + local function mark_multiple_occurences (x) + if no_identity [type(x)] then return end + if seen_once [x] then seen_once [x], multiple [x] = nil, true + elseif multiple [x] then -- pass + else seen_once [x] = true end + + if type (x) == '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 = { } -- multiply occuring values already dumped in localdefs + local localdefs = { } -- already dumped local definitions as source code lines + + -- mutually recursive functions: + local dump_val, dump_or_ref_val + + -------------------------------------------------------------------- + -- if x occurs multiple times, dump the local var rather than the + -- value. If it's the first time it's dumped, also dump the content + -- in localdefs. + -------------------------------------------------------------------- + function dump_or_ref_val (x) + if nested[x] then return 'false' end -- placeholder for recursive reference + if not multiple[x] then return dump_val (x) end + local var = dumped [x] + if var then return "_[" .. var .. "]" end -- already referenced + local val = dump_val(x) -- first occurence, create and register reference + var = gensym() + table.insert(localdefs, "_["..var.."]="..val) + dumped [x] = var + return "_[" .. var .. "]" + end + + ----------------------------------------------------------------------------- + -- Second pass, dump the object; subparts occuring multiple times are dumped + -- in local variables which can be referenced multiple times; + -- care is taken to dump locla vars in asensible order. + ----------------------------------------------------------------------------- + function dump_val(x) + local t = type(x) + if x==nil then return 'nil' + elseif t=="number" then return tostring(x) + elseif t=="string" then return string.format("%q", x) + elseif t=="boolean" then return x and "true" or "false" + elseif t=="table" then + local acc = { } + local idx_dumped = { } + local np = nest_points [x] + for i, v in ipairs(x) do + if np and np[v] then + table.insert (acc, 'false') -- placeholder + else + table.insert (acc, dump_or_ref_val(v)) + end + idx_dumped[i] = true + end + for k, v in pairs(x) do + if np and (np[k] or np[v]) then + --check_multiple(k); check_multiple(v) -- force dumps in localdefs + elseif not idx_dumped[k] then + table.insert (acc, "[" .. dump_or_ref_val(k) .. "] = " .. dump_or_ref_val(v)) + end + end + return "{ "..table.concat(acc,", ").." }" + else + error ("Can't serialize data of type "..t) + end + end + + local function dump_nest_patches() + for _, entry in ipairs(nest_patches) do + local p, k, v = unpack (entry) + assert (multiple[p]) + local set = dump_or_ref_val (p) .. "[" .. dump_or_ref_val (k) .. "] = " .. + dump_or_ref_val (v) .. " -- rec " + table.insert (localdefs, set) + end + end + + mark_multiple_occurences (x) + local toplevel = dump_or_ref_val (x) + dump_nest_patches() + + if next (localdefs) then + return "local _={ }\n" .. + table.concat (localdefs, "\n") .. + "\nreturn " .. toplevel + else + return "return " .. toplevel + end +end + +-- Deserialization. +-- http://stackoverflow.com/questions/5958818/loading-serialized-data-into-a-table +-- + +local function stringtotable(sdata) + if sdata:byte(1) == 27 then return nil, "binary bytecode prohibited" end + local f, message = assert(loadstring(sdata)) + if not f then return nil, message end + setfenv(f, table) + return f() +end + +function minetest.deserialize(sdata) + local table = {} + local okay,results = pcall(stringtotable, sdata) + if okay then + return results + end + minetest.log('error', 'minetest.deserialize(): '.. results) + return nil +end + +-- Run some unit tests +local function unit_test() + function unitTest(name, success) + if not success then + error(name .. ': failed') + end + end + + unittest_input = {cat={sound="nyan", speed=400}, dog={sound="woof"}} + unittest_output = minetest.deserialize(minetest.serialize(unittest_input)) + + unitTest("test 1a", unittest_input.cat.sound == unittest_output.cat.sound) + unitTest("test 1b", unittest_input.cat.speed == unittest_output.cat.speed) + unitTest("test 1c", unittest_input.dog.sound == unittest_output.dog.sound) + + unittest_input = {escapechars="\n\r\t\v\\\"\'", noneuropean="θשׁ٩∂"} + unittest_output = minetest.deserialize(minetest.serialize(unittest_input)) + unitTest("test 3a", unittest_input.escapechars == unittest_output.escapechars) + unitTest("test 3b", unittest_input.noneuropean == unittest_output.noneuropean) +end +unit_test() -- Run it +unit_test = nil -- Hide it + diff --git a/builtin/static_spawn.lua b/builtin/static_spawn.lua new file mode 100644 index 0000000..e8c107d --- /dev/null +++ b/builtin/static_spawn.lua @@ -0,0 +1,33 @@ +-- Minetest: builtin/static_spawn.lua + +local function warn_invalid_static_spawnpoint() + if minetest.setting_get("static_spawnpoint") and + not minetest.setting_get_pos("static_spawnpoint") then + minetest.log('error', "The static_spawnpoint setting is invalid: \"".. + minetest.setting_get("static_spawnpoint").."\"") + end +end + +warn_invalid_static_spawnpoint() + +local function put_player_in_spawn(obj) + warn_invalid_static_spawnpoint() + local static_spawnpoint = minetest.setting_get_pos("static_spawnpoint") + if not static_spawnpoint then + return false + end + minetest.log('action', "Moving "..obj:get_player_name().. + " to static spawnpoint at ".. + minetest.pos_to_string(static_spawnpoint)) + obj:setpos(static_spawnpoint) + return true +end + +minetest.register_on_newplayer(function(obj) + put_player_in_spawn(obj) +end) + +minetest.register_on_respawnplayer(function(obj) + return put_player_in_spawn(obj) +end) + diff --git a/builtin/vector.lua b/builtin/vector.lua new file mode 100644 index 0000000..77944b6 --- /dev/null +++ b/builtin/vector.lua @@ -0,0 +1,146 @@ + +vector = {} + +local function assert_vector(v) + assert(type(v) == "table" and v.x and v.y and v.z, "Invalid vector") +end + +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) + assert_vector(a) + assert_vector(b) + return a.x == b.x and + a.y == b.y and + a.z == b.z +end + +function vector.length(v) + assert_vector(v) + return math.hypot(v.x, math.hypot(v.y, v.z)) +end + +function vector.normalize(v) + assert_vector(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.round(v) + assert_vector(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.distance(a, b) + assert_vector(a) + assert_vector(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) + assert_vector(pos1) + assert_vector(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) + assert_vector(a) + if type(b) == "table" then + assert_vector(b) + 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) + assert_vector(a) + if type(b) == "table" then + assert_vector(b) + 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) + assert_vector(a) + if type(b) == "table" then + assert_vector(b) + 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) + assert_vector(a) + if type(b) == "table" then + assert_vector(b) + 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 + diff --git a/builtin/voxelarea.lua b/builtin/voxelarea.lua new file mode 100644 index 0000000..93bbf73 --- /dev/null +++ b/builtin/voxelarea.lua @@ -0,0 +1,103 @@ +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() + return { + x = self.MaxEdge.x - self.MinEdge.x + 1, + y = self.MaxEdge.y - self.MinEdge.y + 1, + z = self.MaxEdge.z - self.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 i = (z - self.MinEdge.z) * self.zstride + + (y - self.MinEdge.y) * self.ystride + + (x - self.MinEdge.x) + 1 + return math.floor(i) +end + +function VoxelArea:indexp(p) + local i = (p.z - self.MinEdge.z) * self.zstride + + (p.y - self.MinEdge.y) * self.ystride + + (p.x - self.MinEdge.x) + 1 + return math.floor(i) +end + +function VoxelArea:position(i) + local p = {} + + i = i - 1 + + p.z = math.floor(i / self.zstride) + self.MinEdge.z + i = i % self.zstride + + p.y = math.floor(i / self.ystride) + self.MinEdge.y + i = i % self.ystride + + p.x = math.floor(i) + self.MinEdge.x + + return p +end + +function VoxelArea:contains(x, y, z) + return (x >= self.MinEdge.x) and (x <= self.MaxEdge.x) and + (y >= self.MinEdge.y) and (y <= self.MaxEdge.y) and + (z >= self.MinEdge.z) and (z <= self.MaxEdge.z) +end + +function VoxelArea:containsp(p) + return (p.x >= self.MinEdge.x) and (p.x <= self.MaxEdge.x) and + (p.y >= self.MinEdge.y) and (p.y <= self.MaxEdge.y) and + (p.z >= self.MinEdge.z) and (p.z <= self.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 last = self:index(maxx, maxy, maxz) + local ystride = self.ystride + local zstride = self.zstride + local yoff = (last+1) % ystride + local zoff = (last+1) % zstride + local ystridediff = (i - last) % ystride + local zstridediff = (i - last) % zstride + return function() + i = i + 1 + if i % zstride == zoff then + i = i + zstridediff + elseif i % ystride == yoff then + i = i + ystridediff + end + if i <= last then + return i + end + end +end + +function VoxelArea:iterp(minp, maxp) + return self:iter(minp.x, minp.y, minp.z, maxp.x, maxp.y, maxp.z) +end diff --git a/client/serverlist/.gitignore b/client/serverlist/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/client/serverlist/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/client/shaders/bumpmaps_liquids/base.txt b/client/shaders/bumpmaps_liquids/base.txt new file mode 100644 index 0000000..1c26471 --- /dev/null +++ b/client/shaders/bumpmaps_liquids/base.txt @@ -0,0 +1 @@ +trans_alphach diff --git a/client/shaders/bumpmaps_liquids/opengl_fragment.glsl b/client/shaders/bumpmaps_liquids/opengl_fragment.glsl new file mode 100644 index 0000000..bf6bbf0 --- /dev/null +++ b/client/shaders/bumpmaps_liquids/opengl_fragment.glsl @@ -0,0 +1,46 @@ + +uniform sampler2D myTexture; +uniform sampler2D normalTexture; + +uniform vec4 skyBgColor; +uniform float fogDistance; + +varying vec3 vPosition; + +varying vec3 viewVec; + +void main (void) +{ + vec4 col = texture2D(myTexture, vec2(gl_TexCoord[0])); + float alpha = col.a; + vec2 uv = gl_TexCoord[0].st; + vec4 base = texture2D(myTexture, uv); + vec4 final_color = vec4(0.2, 0.2, 0.2, 1.0) * base; + vec3 vVec = normalize(viewVec); + vec3 bump = normalize(texture2D(normalTexture, uv).xyz * 2.0 - 1.0); + vec3 R = reflect(-vVec, bump); + vec3 lVec = normalize(vec3(0.0, -0.4, 0.5)); + float diffuse = max(dot(lVec, bump), 0.0); + + vec3 color = diffuse * texture2D(myTexture, gl_TexCoord[0].st).rgb; + + + float specular = pow(clamp(dot(R, lVec), 0.0, 1.0),1.0); + color += vec3(0.2*specular*diffuse); + + + col = vec4(color.r, color.g, color.b, alpha); + col *= gl_Color; + col = col * col; // SRGB -> Linear + col *= 1.8; + col.r = 1.0 - exp(1.0 - col.r) / exp(1.0); + col.g = 1.0 - exp(1.0 - col.g) / exp(1.0); + col.b = 1.0 - exp(1.0 - col.b) / exp(1.0); + col = sqrt(col); // Linear -> SRGB + if(fogDistance != 0.0){ + float d = max(0.0, min(vPosition.z / fogDistance * 1.5 - 0.6, 1.0)); + alpha = mix(alpha, 0.0, d); + } + + gl_FragColor = vec4(col.r, col.g, col.b, alpha); +} diff --git a/client/shaders/bumpmaps_liquids/opengl_vertex.glsl b/client/shaders/bumpmaps_liquids/opengl_vertex.glsl new file mode 100644 index 0000000..99c208b --- /dev/null +++ b/client/shaders/bumpmaps_liquids/opengl_vertex.glsl @@ -0,0 +1,98 @@ + +uniform mat4 mWorldViewProj; +uniform mat4 mInvWorld; +uniform mat4 mTransWorld; +uniform float dayNightRatio; + +varying vec3 vPosition; +varying vec3 viewVec; + +void main(void) +{ + gl_Position = mWorldViewProj * gl_Vertex; + + vPosition = (mWorldViewProj * gl_Vertex).xyz; + + vec3 tangent; + vec3 binormal; + + vec3 c1 = cross( gl_Normal, vec3(0.0, 0.0, 1.0) ); + vec3 c2 = cross( gl_Normal, vec3(0.0, 1.0, 0.0) ); + + if( length(c1)>length(c2) ) + { + tangent = c1; + } + else + { + tangent = c2; + } + + tangent = normalize(tangent); + +//binormal = cross(gl_Normal, tangent); +//binormal = normalize(binormal); + + vec4 color; + //color = vec4(1.0, 1.0, 1.0, 1.0); + + float day = gl_Color.r; + float night = gl_Color.g; + float light_source = gl_Color.b; + + /*color.r = mix(night, day, dayNightRatio); + color.g = color.r; + color.b = color.r;*/ + + float rg = mix(night, day, dayNightRatio); + rg += light_source * 1.5; // Make light sources brighter + float b = rg; + + // Moonlight is blue + b += (day - night) / 13.0; + rg -= (day - night) / 13.0; + + // Emphase blue a bit in darker places + // See C++ implementation in mapblock_mesh.cpp finalColorBlend() + b += max(0.0, (1.0 - abs(b - 0.13)/0.17) * 0.025); + + // Artificial light is yellow-ish + // See C++ implementation in mapblock_mesh.cpp finalColorBlend() + rg += max(0.0, (1.0 - abs(rg - 0.85)/0.15) * 0.065); + + color.r = rg; + color.g = rg; + color.b = b; + + // Make sides and bottom darker than the top + color = color * color; // SRGB -> Linear + if(gl_Normal.y <= 0.5) + color *= 0.6; + //color *= 0.7; + color = sqrt(color); // Linear -> SRGB + + color.a = gl_Color.a; + + gl_FrontColor = gl_BackColor = color; + + gl_TexCoord[0] = gl_MultiTexCoord0; + + vec3 n1 = normalize(gl_NormalMatrix * gl_Normal); + vec4 tangent1 = vec4(tangent.x, tangent.y, tangent.z, 0); + //vec3 t1 = normalize(gl_NormalMatrix * tangent1); + //vec3 b1 = cross(n1, t1); + + vec3 v; + vec3 vVertex = vec3(gl_ModelViewMatrix * gl_Vertex); + vec3 vVec = -vVertex; + //v.x = dot(vVec, t1); + //v.y = dot(vVec, b1); + //v.z = dot(vVec, n1); + //viewVec = vVec; + viewVec = normalize(vec3(0.0, -0.4, 0.5)); + //Vector representing the 0th texture coordinate passed to fragment shader +//gl_TexCoord[0] = vec2(gl_MultiTexCoord0); + +// Transform the current vertex +//gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; +} diff --git a/client/shaders/bumpmaps_solids/base.txt b/client/shaders/bumpmaps_solids/base.txt new file mode 100644 index 0000000..080df30 --- /dev/null +++ b/client/shaders/bumpmaps_solids/base.txt @@ -0,0 +1 @@ +trans_alphach_ref diff --git a/client/shaders/bumpmaps_solids/opengl_fragment.glsl b/client/shaders/bumpmaps_solids/opengl_fragment.glsl new file mode 100644 index 0000000..c72e8cb --- /dev/null +++ b/client/shaders/bumpmaps_solids/opengl_fragment.glsl @@ -0,0 +1,45 @@ + +uniform sampler2D myTexture; +uniform sampler2D normalTexture; + +uniform vec4 skyBgColor; +uniform float fogDistance; + +varying vec3 vPosition; + +varying vec3 viewVec; + +void main (void) +{ + vec4 col = texture2D(myTexture, vec2(gl_TexCoord[0])); + float alpha = col.a; + vec2 uv = gl_TexCoord[0].st; + vec4 base = texture2D(myTexture, uv); + vec4 final_color = vec4(0.2, 0.2, 0.2, 1.0) * base; + vec3 vVec = normalize(viewVec); + vec3 bump = normalize(texture2D(normalTexture, uv).xyz * 2.0 - 1.0); + vec3 R = reflect(-vVec, bump); + vec3 lVec = normalize(vec3(0.0, -0.4, 0.5)); + float diffuse = max(dot(lVec, bump), 0.0); + + vec3 color = diffuse * texture2D(myTexture, gl_TexCoord[0].st).rgb; + + + float specular = pow(clamp(dot(R, lVec), 0.0, 1.0),1.0); + color += vec3(0.2*specular*diffuse); + + + col = vec4(color.r, color.g, color.b, alpha); + col *= gl_Color; + col = col * col; // SRGB -> Linear + col *= 1.8; + col.r = 1.0 - exp(1.0 - col.r) / exp(1.0); + col.g = 1.0 - exp(1.0 - col.g) / exp(1.0); + col.b = 1.0 - exp(1.0 - col.b) / exp(1.0); + col = sqrt(col); // Linear -> SRGB + if(fogDistance != 0.0){ + float d = max(0.0, min(vPosition.z / fogDistance * 1.5 - 0.6, 1.0)); + col = mix(col, skyBgColor, d); + } + gl_FragColor = vec4(col.r, col.g, col.b, alpha); +} diff --git a/client/shaders/bumpmaps_solids/opengl_vertex.glsl b/client/shaders/bumpmaps_solids/opengl_vertex.glsl new file mode 100644 index 0000000..99c208b --- /dev/null +++ b/client/shaders/bumpmaps_solids/opengl_vertex.glsl @@ -0,0 +1,98 @@ + +uniform mat4 mWorldViewProj; +uniform mat4 mInvWorld; +uniform mat4 mTransWorld; +uniform float dayNightRatio; + +varying vec3 vPosition; +varying vec3 viewVec; + +void main(void) +{ + gl_Position = mWorldViewProj * gl_Vertex; + + vPosition = (mWorldViewProj * gl_Vertex).xyz; + + vec3 tangent; + vec3 binormal; + + vec3 c1 = cross( gl_Normal, vec3(0.0, 0.0, 1.0) ); + vec3 c2 = cross( gl_Normal, vec3(0.0, 1.0, 0.0) ); + + if( length(c1)>length(c2) ) + { + tangent = c1; + } + else + { + tangent = c2; + } + + tangent = normalize(tangent); + +//binormal = cross(gl_Normal, tangent); +//binormal = normalize(binormal); + + vec4 color; + //color = vec4(1.0, 1.0, 1.0, 1.0); + + float day = gl_Color.r; + float night = gl_Color.g; + float light_source = gl_Color.b; + + /*color.r = mix(night, day, dayNightRatio); + color.g = color.r; + color.b = color.r;*/ + + float rg = mix(night, day, dayNightRatio); + rg += light_source * 1.5; // Make light sources brighter + float b = rg; + + // Moonlight is blue + b += (day - night) / 13.0; + rg -= (day - night) / 13.0; + + // Emphase blue a bit in darker places + // See C++ implementation in mapblock_mesh.cpp finalColorBlend() + b += max(0.0, (1.0 - abs(b - 0.13)/0.17) * 0.025); + + // Artificial light is yellow-ish + // See C++ implementation in mapblock_mesh.cpp finalColorBlend() + rg += max(0.0, (1.0 - abs(rg - 0.85)/0.15) * 0.065); + + color.r = rg; + color.g = rg; + color.b = b; + + // Make sides and bottom darker than the top + color = color * color; // SRGB -> Linear + if(gl_Normal.y <= 0.5) + color *= 0.6; + //color *= 0.7; + color = sqrt(color); // Linear -> SRGB + + color.a = gl_Color.a; + + gl_FrontColor = gl_BackColor = color; + + gl_TexCoord[0] = gl_MultiTexCoord0; + + vec3 n1 = normalize(gl_NormalMatrix * gl_Normal); + vec4 tangent1 = vec4(tangent.x, tangent.y, tangent.z, 0); + //vec3 t1 = normalize(gl_NormalMatrix * tangent1); + //vec3 b1 = cross(n1, t1); + + vec3 v; + vec3 vVertex = vec3(gl_ModelViewMatrix * gl_Vertex); + vec3 vVec = -vVertex; + //v.x = dot(vVec, t1); + //v.y = dot(vVec, b1); + //v.z = dot(vVec, n1); + //viewVec = vVec; + viewVec = normalize(vec3(0.0, -0.4, 0.5)); + //Vector representing the 0th texture coordinate passed to fragment shader +//gl_TexCoord[0] = vec2(gl_MultiTexCoord0); + +// Transform the current vertex +//gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; +} diff --git a/client/shaders/test_shader_1/base.txt b/client/shaders/test_shader_1/base.txt new file mode 100644 index 0000000..080df30 --- /dev/null +++ b/client/shaders/test_shader_1/base.txt @@ -0,0 +1 @@ +trans_alphach_ref diff --git a/client/shaders/test_shader_1/opengl_fragment.glsl b/client/shaders/test_shader_1/opengl_fragment.glsl new file mode 100644 index 0000000..ebf943c --- /dev/null +++ b/client/shaders/test_shader_1/opengl_fragment.glsl @@ -0,0 +1,25 @@ + +uniform sampler2D myTexture; +uniform vec4 skyBgColor; +uniform float fogDistance; + +varying vec3 vPosition; + +void main (void) +{ + //vec4 col = vec4(1.0, 0.0, 0.0, 1.0); + vec4 col = texture2D(myTexture, vec2(gl_TexCoord[0])); + float a = col.a; + col *= gl_Color; + col = col * col; // SRGB -> Linear + col *= 1.8; + col.r = 1.0 - exp(1.0 - col.r) / exp(1.0); + col.g = 1.0 - exp(1.0 - col.g) / exp(1.0); + col.b = 1.0 - exp(1.0 - col.b) / exp(1.0); + col = sqrt(col); // Linear -> SRGB + if(fogDistance != 0.0){ + float d = max(0.0, min(vPosition.z / fogDistance * 1.5 - 0.6, 1.0)); + col = mix(col, skyBgColor, d); + } + gl_FragColor = vec4(col.r, col.g, col.b, a); +} diff --git a/client/shaders/test_shader_1/opengl_vertex.glsl b/client/shaders/test_shader_1/opengl_vertex.glsl new file mode 100644 index 0000000..3cf1f12 --- /dev/null +++ b/client/shaders/test_shader_1/opengl_vertex.glsl @@ -0,0 +1,58 @@ + +uniform mat4 mWorldViewProj; +uniform mat4 mInvWorld; +uniform mat4 mTransWorld; +uniform float dayNightRatio; + +varying vec3 vPosition; + +void main(void) +{ + gl_Position = mWorldViewProj * gl_Vertex; + + vPosition = (mWorldViewProj * gl_Vertex).xyz; + + vec4 color; + //color = vec4(1.0, 1.0, 1.0, 1.0); + + float day = gl_Color.r; + float night = gl_Color.g; + float light_source = gl_Color.b; + + /*color.r = mix(night, day, dayNightRatio); + color.g = color.r; + color.b = color.r;*/ + + float rg = mix(night, day, dayNightRatio); + rg += light_source * 1.0; // Make light sources brighter + float b = rg; + + // Moonlight is blue + b += (day - night) / 13.0; + rg -= (day - night) / 13.0; + + // Emphase blue a bit in darker places + // See C++ implementation in mapblock_mesh.cpp finalColorBlend() + b += max(0.0, (1.0 - abs(b - 0.13)/0.17) * 0.025); + + // Artificial light is yellow-ish + // See C++ implementation in mapblock_mesh.cpp finalColorBlend() + rg += max(0.0, (1.0 - abs(rg - 0.85)/0.15) * 0.065); + + color.r = rg; + color.g = rg; + color.b = b; + + // Make sides and bottom darker than the top + color = color * color; // SRGB -> Linear + if(gl_Normal.y <= 0.5) + color *= 0.6; + //color *= 0.7; + color = sqrt(color); // Linear -> SRGB + + color.a = gl_Color.a; + + gl_FrontColor = gl_BackColor = color; + + gl_TexCoord[0] = gl_MultiTexCoord0; +} diff --git a/client/shaders/test_shader_2/base.txt b/client/shaders/test_shader_2/base.txt new file mode 100644 index 0000000..1c26471 --- /dev/null +++ b/client/shaders/test_shader_2/base.txt @@ -0,0 +1 @@ +trans_alphach diff --git a/client/shaders/test_shader_2/opengl_fragment.glsl b/client/shaders/test_shader_2/opengl_fragment.glsl new file mode 100644 index 0000000..38bc943 --- /dev/null +++ b/client/shaders/test_shader_2/opengl_fragment.glsl @@ -0,0 +1,23 @@ + +uniform sampler2D myTexture; +uniform float fogDistance; + +varying vec3 vPosition; + +void main (void) +{ + vec4 col = texture2D(myTexture, vec2(gl_TexCoord[0])); + col *= gl_Color; + float a = gl_Color.a; + col = col * col; // SRGB -> Linear + col *= 1.8; + col.r = 1.0 - exp(1.0 - col.r) / exp(1.0); + col.g = 1.0 - exp(1.0 - col.g) / exp(1.0); + col.b = 1.0 - exp(1.0 - col.b) / exp(1.0); + col = sqrt(col); // Linear -> SRGB + if(fogDistance != 0.0){ + float d = max(0.0, min(vPosition.z / fogDistance * 1.5 - 0.6, 1.0)); + a = mix(a, 0.0, d); + } + gl_FragColor = vec4(col.r, col.g, col.b, a); +} diff --git a/client/shaders/test_shader_2/opengl_vertex.glsl b/client/shaders/test_shader_2/opengl_vertex.glsl new file mode 100644 index 0000000..2881bad --- /dev/null +++ b/client/shaders/test_shader_2/opengl_vertex.glsl @@ -0,0 +1,51 @@ + +uniform mat4 mWorldViewProj; +uniform mat4 mInvWorld; +uniform mat4 mTransWorld; +uniform float dayNightRatio; + +varying vec3 vPosition; + +void main(void) +{ + gl_Position = mWorldViewProj * gl_Vertex; + + vPosition = (mWorldViewProj * gl_Vertex).xyz; + + vec4 color; + //color = vec4(1.0, 1.0, 1.0, 1.0); + + float day = gl_Color.r; + float night = gl_Color.g; + float light_source = gl_Color.b; + + /*color.r = mix(night, day, dayNightRatio); + color.g = color.r; + color.b = color.r;*/ + + float rg = mix(night, day, dayNightRatio); + rg += light_source * 1.0; // Make light sources brighter + float b = rg; + + // Moonlight is blue + b += (day - night) / 13.0; + rg -= (day - night) / 13.0; + + // Emphase blue a bit in darker places + // See C++ implementation in mapblock_mesh.cpp finalColorBlend() + b += max(0.0, (1.0 - abs(b - 0.13)/0.17) * 0.025); + + // Artificial light is yellow-ish + // See C++ implementation in mapblock_mesh.cpp finalColorBlend() + rg += max(0.0, (1.0 - abs(rg - 0.85)/0.15) * 0.065); + + color.r = rg; + color.g = rg; + color.b = b; + + color.a = gl_Color.a; + + gl_FrontColor = gl_BackColor = color; + + gl_TexCoord[0] = gl_MultiTexCoord0; +} diff --git a/client/shaders/test_shader_3/base.txt b/client/shaders/test_shader_3/base.txt new file mode 100644 index 0000000..1c26471 --- /dev/null +++ b/client/shaders/test_shader_3/base.txt @@ -0,0 +1 @@ +trans_alphach diff --git a/client/shaders/test_shader_3/opengl_fragment.glsl b/client/shaders/test_shader_3/opengl_fragment.glsl new file mode 100644 index 0000000..535774c --- /dev/null +++ b/client/shaders/test_shader_3/opengl_fragment.glsl @@ -0,0 +1,25 @@ + +uniform sampler2D myTexture; +uniform float fogDistance; + +varying vec3 vPosition; + +void main (void) +{ + vec4 col = texture2D(myTexture, vec2(gl_TexCoord[0])); + col *= gl_Color; + float a = col.a; + col = col * col; // SRGB -> Linear + col *= 1.8; + col.r = 1.0 - exp(1.0 - col.r) / exp(1.0); + col.g = 1.0 - exp(1.0 - col.g) / exp(1.0); + col.b = 1.0 - exp(1.0 - col.b) / exp(1.0); + col = sqrt(col); // Linear -> SRGB + + if(fogDistance != 0.0){ + float d = max(0.0, min(vPosition.z / fogDistance * 1.5 - 0.6, 1.0)); + a = mix(a, 0.0, d); + } + + gl_FragColor = vec4(col.r, col.g, col.b, a); +} diff --git a/client/shaders/test_shader_3/opengl_vertex.glsl b/client/shaders/test_shader_3/opengl_vertex.glsl new file mode 100644 index 0000000..2881bad --- /dev/null +++ b/client/shaders/test_shader_3/opengl_vertex.glsl @@ -0,0 +1,51 @@ + +uniform mat4 mWorldViewProj; +uniform mat4 mInvWorld; +uniform mat4 mTransWorld; +uniform float dayNightRatio; + +varying vec3 vPosition; + +void main(void) +{ + gl_Position = mWorldViewProj * gl_Vertex; + + vPosition = (mWorldViewProj * gl_Vertex).xyz; + + vec4 color; + //color = vec4(1.0, 1.0, 1.0, 1.0); + + float day = gl_Color.r; + float night = gl_Color.g; + float light_source = gl_Color.b; + + /*color.r = mix(night, day, dayNightRatio); + color.g = color.r; + color.b = color.r;*/ + + float rg = mix(night, day, dayNightRatio); + rg += light_source * 1.0; // Make light sources brighter + float b = rg; + + // Moonlight is blue + b += (day - night) / 13.0; + rg -= (day - night) / 13.0; + + // Emphase blue a bit in darker places + // See C++ implementation in mapblock_mesh.cpp finalColorBlend() + b += max(0.0, (1.0 - abs(b - 0.13)/0.17) * 0.025); + + // Artificial light is yellow-ish + // See C++ implementation in mapblock_mesh.cpp finalColorBlend() + rg += max(0.0, (1.0 - abs(rg - 0.85)/0.15) * 0.065); + + color.r = rg; + color.g = rg; + color.b = b; + + color.a = gl_Color.a; + + gl_FrontColor = gl_BackColor = color; + + gl_TexCoord[0] = gl_MultiTexCoord0; +} diff --git a/cmake/Modules/FindCURL.cmake b/cmake/Modules/FindCURL.cmake new file mode 100644 index 0000000..975b808 --- /dev/null +++ b/cmake/Modules/FindCURL.cmake @@ -0,0 +1,47 @@ +# - Find curl +# Find the native CURL headers and libraries. +# +# CURL_INCLUDE_DIR - where to find curl/curl.h, etc. +# CURL_LIBRARY - List of libraries when using curl. +# CURL_FOUND - True if curl found. + +if( UNIX ) + FIND_PATH(CURL_INCLUDE_DIR NAMES curl.h + PATHS + /usr/local/include/curl + /usr/include/curl + ) + + FIND_LIBRARY(CURL_LIBRARY NAMES curl + PATHS + /usr/local/lib + /usr/lib + ) +else( UNIX ) + FIND_PATH(CURL_INCLUDE_DIR NAMES curl/curl.h) # Look for the header file. + FIND_LIBRARY(CURL_LIBRARY NAMES curl) # Look for the library. + FIND_FILE(CURL_DLL NAMES libcurl.dll + PATHS + "c:/windows/system32" + DOC "Path of the cURL dll (for installation)") + INCLUDE(FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set CURL_FOUND to TRUE if + FIND_PACKAGE_HANDLE_STANDARD_ARGS(CURL DEFAULT_MSG CURL_LIBRARY CURL_INCLUDE_DIR) # all listed variables are TRUE +endif( UNIX ) + +if( WIN32 ) + if( CURL_LIBRARY AND CURL_INCLUDE_DIR AND CURL_DLL ) # libcurl.dll is required on Windows + SET(CURL_FOUND TRUE) + else( CURL_LIBRARY AND CURL_INCLUDE_DIR AND CURL_DLL ) + SET(CURL_FOUND FALSE) + endif( CURL_LIBRARY AND CURL_INCLUDE_DIR AND CURL_DLL ) +else ( WIN32 ) + if( CURL_LIBRARY AND CURL_INCLUDE_DIR ) + SET(CURL_FOUND TRUE) + else( CURL_LIBRARY AND CURL_INCLUDE_DIR ) + SET(CURL_FOUND FALSE) + endif( CURL_LIBRARY AND CURL_INCLUDE_DIR ) +endif ( WIN32 ) + +MESSAGE(STATUS "CURL_INCLUDE_DIR = ${CURL_INCLUDE_DIR}") +MESSAGE(STATUS "CURL_LIBRARY = ${CURL_LIBRARY}") +MESSAGE(STATUS "CURL_DLL = ${CURL_DLL}") diff --git a/cmake/Modules/FindGettextLib.cmake b/cmake/Modules/FindGettextLib.cmake new file mode 100644 index 0000000..bc7ea36 --- /dev/null +++ b/cmake/Modules/FindGettextLib.cmake @@ -0,0 +1,72 @@ +# Package finder for gettext libs and include files + +SET(CUSTOM_GETTEXT_PATH "${PROJECT_SOURCE_DIR}/../../gettext" + CACHE FILEPATH "path to custom gettext") + +# by default +SET(GETTEXT_FOUND FALSE) + +FIND_PATH(GETTEXT_INCLUDE_DIR + NAMES libintl.h + PATHS "${CUSTOM_GETTEXT_PATH}/include" + DOC "gettext include directory") + +FIND_PROGRAM(GETTEXT_MSGFMT + NAMES msgfmt + PATHS "${CUSTOM_GETTEXT_PATH}/bin" + DOC "path to msgfmt") + +# modern Linux, as well as Mac, seem to not need require special linking +# they do not because gettext is part of glibc +# TODO check the requirements on other BSDs and older Linux +IF (WIN32) + IF(MSVC) + SET(GETTEXT_LIB_NAMES + libintl.lib intl.lib libintl3.lib intl3.lib) + ELSE() + SET(GETTEXT_LIB_NAMES + libintl.dll.a intl.dll.a libintl3.dll.a intl3.dll.a) + ENDIF() + FIND_LIBRARY(GETTEXT_LIBRARY + NAMES ${GETTEXT_LIB_NAMES} + PATHS "${CUSTOM_GETTEXT_PATH}/lib" + DOC "gettext *intl*.lib") + FIND_FILE(GETTEXT_DLL + NAMES libintl.dll intl.dll libintl3.dll intl3.dll + PATHS "${CUSTOM_GETTEXT_PATH}/bin" "${CUSTOM_GETTEXT_PATH}/lib" + DOC "gettext *intl*.dll") + FIND_FILE(GETTEXT_ICONV_DLL + NAMES libiconv2.dll + PATHS "${CUSTOM_GETTEXT_PATH}/bin" "${CUSTOM_GETTEXT_PATH}/lib" + DOC "gettext *iconv*.lib") +ENDIF(WIN32) + +IF(GETTEXT_INCLUDE_DIR AND GETTEXT_MSGFMT) + IF (WIN32) + # in the Win32 case check also for the extra linking requirements + IF(GETTEXT_LIBRARY AND GETTEXT_DLL AND GETTEXT_ICONV_DLL) + SET(GETTEXT_FOUND TRUE) + ENDIF() + ELSE(WIN32) + # *BSD variants require special linkage as they don't use glibc + IF(${CMAKE_SYSTEM_NAME} MATCHES "BSD") + SET(GETTEXT_LIBRARY "intl") + ENDIF(${CMAKE_SYSTEM_NAME} MATCHES "BSD") + SET(GETTEXT_FOUND TRUE) + ENDIF(WIN32) +ENDIF() + +IF(GETTEXT_FOUND) + SET(GETTEXT_PO_PATH ${CMAKE_SOURCE_DIR}/po) + SET(GETTEXT_MO_BUILD_PATH ${CMAKE_BINARY_DIR}/locale//LC_MESSAGES) + SET(GETTEXT_MO_DEST_PATH ${LOCALEDIR}//LC_MESSAGES) + FILE(GLOB GETTEXT_AVAILABLE_LOCALES RELATIVE ${GETTEXT_PO_PATH} "${GETTEXT_PO_PATH}/*") + LIST(REMOVE_ITEM GETTEXT_AVAILABLE_LOCALES minetest.pot) + MACRO(SET_MO_PATHS _buildvar _destvar _locale) + STRING(REPLACE "" ${_locale} ${_buildvar} ${GETTEXT_MO_BUILD_PATH}) + STRING(REPLACE "" ${_locale} ${_destvar} ${GETTEXT_MO_DEST_PATH}) + ENDMACRO(SET_MO_PATHS) +ELSE() + SET(GETTEXT_INCLUDE_DIR "") + SET(GETTEXT_LIBRARY "") +ENDIF() diff --git a/cmake/Modules/FindIrrlicht.cmake b/cmake/Modules/FindIrrlicht.cmake new file mode 100644 index 0000000..bd00422 --- /dev/null +++ b/cmake/Modules/FindIrrlicht.cmake @@ -0,0 +1,94 @@ +#FindIrrlicht.cmake + +set(IRRLICHT_SOURCE_DIR "" CACHE PATH "Path to irrlicht source directory (optional)") + +if( UNIX ) + # Unix +else( UNIX ) + # Windows +endif( UNIX ) + +# Find include directory + +if(NOT IRRLICHT_SOURCE_DIR STREQUAL "") + set(IRRLICHT_SOURCE_DIR_INCLUDE + "${IRRLICHT_SOURCE_DIR}/include" + ) + + set(IRRLICHT_LIBRARY_NAMES libIrrlicht.a Irrlicht Irrlicht.lib) + + if(WIN32) + if(MSVC) + set(IRRLICHT_SOURCE_DIR_LIBS "${IRRLICHT_SOURCE_DIR}/lib/Win32-visualstudio") + set(IRRLICHT_LIBRARY_NAMES Irrlicht.lib) + else() + set(IRRLICHT_SOURCE_DIR_LIBS "${IRRLICHT_SOURCE_DIR}/lib/Win32-gcc") + set(IRRLICHT_LIBRARY_NAMES libIrrlicht.a libIrrlicht.dll.a) + endif() + else() + set(IRRLICHT_SOURCE_DIR_LIBS "${IRRLICHT_SOURCE_DIR}/lib/Linux") + set(IRRLICHT_LIBRARY_NAMES libIrrlicht.a) + endif() + + FIND_PATH(IRRLICHT_INCLUDE_DIR NAMES irrlicht.h + PATHS + ${IRRLICHT_SOURCE_DIR_INCLUDE} + NO_DEFAULT_PATH + ) + + FIND_LIBRARY(IRRLICHT_LIBRARY NAMES ${IRRLICHT_LIBRARY_NAMES} + PATHS + ${IRRLICHT_SOURCE_DIR_LIBS} + NO_DEFAULT_PATH + ) + +else() + + FIND_PATH(IRRLICHT_INCLUDE_DIR NAMES irrlicht.h + PATHS + /usr/local/include/irrlicht + /usr/include/irrlicht + ) + + FIND_LIBRARY(IRRLICHT_LIBRARY NAMES libIrrlicht.a Irrlicht + PATHS + /usr/local/lib + /usr/lib + ) +endif() + +MESSAGE(STATUS "IRRLICHT_SOURCE_DIR = ${IRRLICHT_SOURCE_DIR}") +MESSAGE(STATUS "IRRLICHT_INCLUDE_DIR = ${IRRLICHT_INCLUDE_DIR}") +MESSAGE(STATUS "IRRLICHT_LIBRARY = ${IRRLICHT_LIBRARY}") + +# On windows, find the dll for installation +if(WIN32) + if(MSVC) + FIND_FILE(IRRLICHT_DLL NAMES Irrlicht.dll + PATHS + "${IRRLICHT_SOURCE_DIR}/bin/Win32-VisualStudio" + DOC "Path of the Irrlicht dll (for installation)" + ) + else() + FIND_FILE(IRRLICHT_DLL NAMES Irrlicht.dll + PATHS + "${IRRLICHT_SOURCE_DIR}/bin/Win32-gcc" + DOC "Path of the Irrlicht dll (for installation)" + ) + endif() + MESSAGE(STATUS "IRRLICHT_DLL = ${IRRLICHT_DLL}") +endif(WIN32) + +# handle the QUIETLY and REQUIRED arguments and set IRRLICHT_FOUND to TRUE if +# all listed variables are TRUE +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(IRRLICHT DEFAULT_MSG IRRLICHT_LIBRARY IRRLICHT_INCLUDE_DIR) + +IF(IRRLICHT_FOUND) + SET(IRRLICHT_LIBRARIES ${IRRLICHT_LIBRARY}) +ELSE(IRRLICHT_FOUND) + SET(IRRLICHT_LIBRARIES) +ENDIF(IRRLICHT_FOUND) + +MARK_AS_ADVANCED(IRRLICHT_LIBRARY IRRLICHT_INCLUDE_DIR IRRLICHT_DLL) + diff --git a/cmake/Modules/FindJson.cmake b/cmake/Modules/FindJson.cmake new file mode 100644 index 0000000..a9178a2 --- /dev/null +++ b/cmake/Modules/FindJson.cmake @@ -0,0 +1,18 @@ +# Look for json, use our own if not found + +#FIND_PATH(JSON_INCLUDE_DIR json.h) + +#FIND_LIBRARY(JSON_LIBRARY NAMES jsoncpp) + +#IF(JSON_LIBRARY AND JSON_INCLUDE_DIR) +# SET( JSON_FOUND TRUE ) +#ENDIF(JSON_LIBRARY AND JSON_INCLUDE_DIR) + +#IF(JSON_FOUND) +# MESSAGE(STATUS "Found system jsoncpp header file in ${JSON_INCLUDE_DIR}") +# MESSAGE(STATUS "Found system jsoncpp library ${JSON_LIBRARY}") +#ELSE(JSON_FOUND) + SET(JSON_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/json) + SET(JSON_LIBRARY jsoncpp) + MESSAGE(STATUS "Using project jsoncpp library") +#ENDIF(JSON_FOUND) diff --git a/cmake/Modules/FindOpenGLES2.cmake b/cmake/Modules/FindOpenGLES2.cmake new file mode 100644 index 0000000..42d31c8 --- /dev/null +++ b/cmake/Modules/FindOpenGLES2.cmake @@ -0,0 +1,130 @@ +#------------------------------------------------------------------- +# This file is stolen from part of the CMake build system for OGRE (Object-oriented Graphics Rendering Engine) http://www.ogre3d.org/ +# +# The contents of this file are placed in the public domain. Feel +# free to make use of it in any way you like. +#------------------------------------------------------------------- + +# - Try to find OpenGLES and EGL +# Once done this will define +# +# OPENGLES2_FOUND - system has OpenGLES +# OPENGLES2_INCLUDE_DIR - the GL include directory +# OPENGLES2_LIBRARIES - Link these to use OpenGLES +# +# EGL_FOUND - system has EGL +# EGL_INCLUDE_DIR - the EGL include directory +# EGL_LIBRARIES - Link these to use EGL + +# win32, apple, android NOT TESED +# linux tested and works + +IF (WIN32) + IF (CYGWIN) + + FIND_PATH(OPENGLES2_INCLUDE_DIR GLES2/gl2.h ) + + FIND_LIBRARY(OPENGLES2_gl_LIBRARY libGLESv2 ) + + ELSE (CYGWIN) + + IF(BORLAND) + SET (OPENGLES2_gl_LIBRARY import32 CACHE STRING "OpenGL ES 2.x library for win32") + ELSE(BORLAND) + # todo + # SET (OPENGLES_gl_LIBRARY ${SOURCE_DIR}/Dependencies/lib/release/libGLESv2.lib CACHE STRING "OpenGL ES 2.x library for win32" + ENDIF(BORLAND) + + ENDIF (CYGWIN) + +ELSE (WIN32) + + IF (APPLE) + + create_search_paths(/Developer/Platforms) + findpkg_framework(OpenGLES2) + set(OPENGLES2_gl_LIBRARY "-framework OpenGLES") + + ELSE(APPLE) + + FIND_PATH(OPENGLES2_INCLUDE_DIR GLES2/gl2.h + /usr/openwin/share/include + /opt/graphics/OpenGL/include /usr/X11R6/include + /usr/include + ) + + FIND_LIBRARY(OPENGLES2_gl_LIBRARY + NAMES GLESv2 + PATHS /opt/graphics/OpenGL/lib + /usr/openwin/lib + /usr/shlib /usr/X11R6/lib + /usr/lib + ) + + IF (NOT BUILD_ANDROID) + FIND_PATH(EGL_INCLUDE_DIR EGL/egl.h + /usr/openwin/share/include + /opt/graphics/OpenGL/include /usr/X11R6/include + /usr/include + ) + + FIND_LIBRARY(EGL_egl_LIBRARY + NAMES EGL + PATHS /opt/graphics/OpenGL/lib + /usr/openwin/lib + /usr/shlib /usr/X11R6/lib + /usr/lib + ) + + # On Unix OpenGL most certainly always requires X11. + # Feel free to tighten up these conditions if you don't + # think this is always true. + # It's not true on OSX. + + IF (OPENGLES2_gl_LIBRARY) + IF(NOT X11_FOUND) + INCLUDE(FindX11) + ENDIF(NOT X11_FOUND) + IF (X11_FOUND) + IF (NOT APPLE) + SET (OPENGLES2_LIBRARIES ${X11_LIBRARIES}) + ENDIF (NOT APPLE) + ENDIF (X11_FOUND) + ENDIF (OPENGLES2_gl_LIBRARY) + ENDIF () + + ENDIF(APPLE) +ENDIF (WIN32) + +#SET( OPENGLES2_LIBRARIES ${OPENGLES2_gl_LIBRARY} ${OPENGLES2_LIBRARIES}) + +IF (BUILD_ANDROID) + IF(OPENGLES2_gl_LIBRARY) + SET( OPENGLES2_LIBRARIES ${OPENGLES2_gl_LIBRARY} ${OPENGLES2_LIBRARIES}) + SET( EGL_LIBRARIES) + SET( OPENGLES2_FOUND "YES" ) + ENDIF(OPENGLES2_gl_LIBRARY) +ELSE () + + SET( OPENGLES2_LIBRARIES ${OPENGLES2_gl_LIBRARY} ${OPENGLES2_LIBRARIES}) + + IF(OPENGLES2_gl_LIBRARY AND EGL_egl_LIBRARY) + SET( OPENGLES2_LIBRARIES ${OPENGLES2_gl_LIBRARY} ${OPENGLES2_LIBRARIES}) + SET( EGL_LIBRARIES ${EGL_egl_LIBRARY} ${EGL_LIBRARIES}) + SET( OPENGLES2_FOUND "YES" ) + ENDIF(OPENGLES2_gl_LIBRARY AND EGL_egl_LIBRARY) + +ENDIF () + +MARK_AS_ADVANCED( + OPENGLES2_INCLUDE_DIR + OPENGLES2_gl_LIBRARY + EGL_INCLUDE_DIR + EGL_egl_LIBRARY +) + +IF(OPENGLES2_FOUND) + MESSAGE(STATUS "Found system opengles2 library ${OPENGLES2_LIBRARIES}") +ELSE () + SET(OPENGLES2_LIBRARIES "") +ENDIF () diff --git a/cmake/Modules/FindSqlite3.cmake b/cmake/Modules/FindSqlite3.cmake new file mode 100644 index 0000000..ecce6e3 --- /dev/null +++ b/cmake/Modules/FindSqlite3.cmake @@ -0,0 +1,18 @@ +# Look for sqlite3, use our own if not found + +FIND_PATH(SQLITE3_INCLUDE_DIR sqlite3.h) + +FIND_LIBRARY(SQLITE3_LIBRARY NAMES sqlite3) + +IF(SQLITE3_LIBRARY AND SQLITE3_INCLUDE_DIR) + SET( SQLITE3_FOUND TRUE ) +ENDIF(SQLITE3_LIBRARY AND SQLITE3_INCLUDE_DIR) + +IF(SQLITE3_FOUND) + MESSAGE(STATUS "Found system sqlite3 header file in ${SQLITE3_INCLUDE_DIR}") + MESSAGE(STATUS "Found system sqlite3 library ${SQLITE3_LIBRARY}") +ELSE(SQLITE3_FOUND) + SET(SQLITE3_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/sqlite) + SET(SQLITE3_LIBRARY sqlite3) + MESSAGE(STATUS "Using project sqlite3 library") +ENDIF(SQLITE3_FOUND) diff --git a/cmake/Modules/FindVorbis.cmake b/cmake/Modules/FindVorbis.cmake new file mode 100644 index 0000000..8f38136 --- /dev/null +++ b/cmake/Modules/FindVorbis.cmake @@ -0,0 +1,46 @@ +# - Find vorbis +# Find the native vorbis includes and libraries +# +# VORBIS_INCLUDE_DIR - where to find vorbis.h, etc. +# OGG_INCLUDE_DIR - where to find ogg/ogg.h, etc. +# VORBIS_LIBRARIES - List of libraries when using vorbis(file). +# VORBIS_FOUND - True if vorbis found. + +if(NOT GP2XWIZ) + if(VORBIS_INCLUDE_DIR) + # Already in cache, be silent + set(VORBIS_FIND_QUIETLY TRUE) + endif(VORBIS_INCLUDE_DIR) + find_path(OGG_INCLUDE_DIR ogg/ogg.h) + find_path(VORBIS_INCLUDE_DIR vorbis/vorbisfile.h) + # MSVC built ogg/vorbis may be named ogg_static and vorbis_static + find_library(OGG_LIBRARY NAMES ogg ogg_static) + find_library(VORBIS_LIBRARY NAMES vorbis vorbis_static) + find_library(VORBISFILE_LIBRARY NAMES vorbisfile vorbisfile_static) + # Handle the QUIETLY and REQUIRED arguments and set VORBIS_FOUND + # to TRUE if all listed variables are TRUE. + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(VORBIS DEFAULT_MSG + OGG_INCLUDE_DIR VORBIS_INCLUDE_DIR + OGG_LIBRARY VORBIS_LIBRARY VORBISFILE_LIBRARY) +else(NOT GP2XWIZ) + find_path(VORBIS_INCLUDE_DIR tremor/ivorbisfile.h) + find_library(VORBIS_LIBRARY NAMES vorbis_dec) + find_package_handle_standard_args(VORBIS DEFAULT_MSG + VORBIS_INCLUDE_DIR VORBIS_LIBRARY) +endif(NOT GP2XWIZ) + +if(VORBIS_FOUND) + if(NOT GP2XWIZ) + set(VORBIS_LIBRARIES ${VORBISFILE_LIBRARY} ${VORBIS_LIBRARY} + ${OGG_LIBRARY}) + else(NOT GP2XWIZ) + set(VORBIS_LIBRARIES ${VORBIS_LIBRARY}) + endif(NOT GP2XWIZ) +else(VORBIS_FOUND) + set(VORBIS_LIBRARIES) +endif(VORBIS_FOUND) + +mark_as_advanced(OGG_INCLUDE_DIR VORBIS_INCLUDE_DIR) +mark_as_advanced(OGG_LIBRARY VORBIS_LIBRARY VORBISFILE_LIBRARY) + diff --git a/cmake/Modules/GenerateVersion.cmake b/cmake/Modules/GenerateVersion.cmake new file mode 100644 index 0000000..4a7f183 --- /dev/null +++ b/cmake/Modules/GenerateVersion.cmake @@ -0,0 +1,20 @@ +# Always run during 'make' + +if(VERSION_EXTRA) + set(VERSION_GITHASH "${VERSION_STRING}") +else(VERSION_EXTRA) + execute_process(COMMAND git describe --always --tag --dirty + WORKING_DIRECTORY "${GENERATE_VERSION_SOURCE_DIR}" + OUTPUT_VARIABLE VERSION_GITHASH OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + + if(VERSION_GITHASH) + message(STATUS "*** Detected git version ${VERSION_GITHASH} ***") + else() + set(VERSION_GITHASH "${VERSION_STRING}") + endif() +endif() + +configure_file( + ${GENERATE_VERSION_SOURCE_DIR}/cmake_config_githash.h.in + ${GENERATE_VERSION_BINARY_DIR}/cmake_config_githash.h) diff --git a/cmake/Modules/misc.cmake b/cmake/Modules/misc.cmake new file mode 100644 index 0000000..0bd2e3f --- /dev/null +++ b/cmake/Modules/misc.cmake @@ -0,0 +1,21 @@ +# +# Random macros +# + +# Not used ATM + +MACRO (GETDATETIME RESULT) + IF (WIN32) + EXECUTE_PROCESS(COMMAND "cmd" /C echo %date% %time% OUTPUT_VARIABLE ${RESULT}) + string(REGEX REPLACE "\n" "" ${RESULT} "${${RESULT}}") + ELSEIF(UNIX) + EXECUTE_PROCESS(COMMAND "date" "+%Y-%m-%d_%H:%M:%S" OUTPUT_VARIABLE ${RESULT}) + string(REGEX REPLACE "\n" "" ${RESULT} "${${RESULT}}") + ELSE (WIN32) + MESSAGE(SEND_ERROR "date not implemented") + SET(${RESULT} "Unknown") + ENDIF (WIN32) + + string(REGEX REPLACE " " "_" ${RESULT} "${${RESULT}}") +ENDMACRO (GETDATETIME) + diff --git a/doc/lgpl-2.1.txt b/doc/lgpl-2.1.txt new file mode 100644 index 0000000..4362b49 --- /dev/null +++ b/doc/lgpl-2.1.txt @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library 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 library 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 library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/doc/lua_api.txt b/doc/lua_api.txt new file mode 100644 index 0000000..d0090a8 --- /dev/null +++ b/doc/lua_api.txt @@ -0,0 +1,2397 @@ +Minetest Lua Modding API Reference 0.4.8 +======================================== +More information at http://www.minetest.net/ +Developer Wiki: http://dev.minetest.net/ + +Introduction +------------- +Content and functionality can be added to Minetest 0.4 by using Lua +scripting in run-time loaded mods. + +A mod is a self-contained bunch of scripts, textures and other related +things that is loaded by and interfaces with Minetest. + +Mods are contained and ran solely on the server side. Definitions and media +files are automatically transferred to the client. + +If you see a deficiency in the API, feel free to attempt to add the +functionality in the engine and API. You can send such improvements as +source code patches to . + +Programming in Lua +------------------- +If you have any difficulty in understanding this, please read: + http://www.lua.org/pil/ + +Startup +-------- +Mods are loaded during server startup from the mod load paths by running +the init.lua scripts in a shared environment. + +Paths +----- +RUN_IN_PLACE=1: (Windows release, local build) + $path_user: Linux: + Windows: + $path_share: Linux: + Windows: + +RUN_IN_PLACE=0: (Linux release) + $path_share: Linux: /usr/share/minetest + Windows: /minetest-0.4.x + $path_user: Linux: ~/.minetest + Windows: C:/users//AppData/minetest (maybe) + +Games +----- +Games are looked up from: + $path_share/games/gameid/ + $path_user/games/gameid/ +where gameid is unique to each game. + +The game directory contains the file game.conf, which contains these fields: + name = +eg. + name = Minetest + +The game directory can contain the file minetest.conf, which will be used +to set default settings when running the particular game. + +Mod load path +------------- +Generic: + $path_share/games/gameid/mods/ + $path_share/mods/ + $path_user/games/gameid/mods/ + $path_user/mods/ <-- User-installed mods + $worldpath/worldmods/ + +In a run-in-place version (eg. the distributed windows version): + minetest-0.4.x/games/gameid/mods/ + minetest-0.4.x/mods/gameid/ <-- User-installed mods + minetest-0.4.x/worlds/worldname/worldmods/ + +On an installed version on linux: + /usr/share/minetest/games/gameid/mods/ + ~/.minetest/mods/gameid/ <-- User-installed mods + ~/.minetest/worlds/worldname/worldmods + +Mod load path for world-specific games +-------------------------------------- +It is possible to include a game in a world; in this case, no mods or +games are loaded or checked from anywhere else. + +This is useful for eg. adventure worlds. + +This happens if the following directory exists: + $world/game/ + +Mods should be then be placed in: + $world/game/mods/ + +Modpack support +---------------- +Mods can be put in a subdirectory, if the parent directory, which otherwise +should be a mod, contains a file named modpack.txt. This file shall be +empty, except for lines starting with #, which are comments. + +Mod directory structure +------------------------ +mods +|-- modname +| |-- depends.txt +| |-- screenshot.png +| |-- description.txt +| |-- init.lua +| |-- textures +| | |-- modname_stuff.png +| | `-- modname_something_else.png +| |-- sounds +| |-- media +| `-- +`-- another + +modname: + The location of this directory can be fetched by using + minetest.get_modpath(modname) + +depends.txt: + List of mods that have to be loaded before loading this mod. + A single line contains a single modname. + + Optional dependencies can be defined by appending a question mark + to a single modname. Their meaning is that if the specified mod + is missing, that does not prevent this mod from being loaded. + +screenshot.png: + A screenshot shown in modmanager within mainmenu. + +description.txt: + File containing desctiption to be shown within mainmenu. + +optdepends.txt: + An alternative way of specifying optional dependencies. + Like depends.txt, a single line contains a single modname. + + NOTE: This file exists for compatibility purposes only and + support for it will be removed from the engine by the end of 2013. + +init.lua: + The main Lua script. Running this script should register everything it + wants to register. Subsequent execution depends on minetest calling the + registered callbacks. + + minetest.setting_get(name) and minetest.setting_getbool(name) can be used + to read custom or existing settings at load time, if necessary. + +textures, sounds, media: + Media files (textures, sounds, whatever) that will be transferred to the + client and will be available for use by the mod. + +Naming convention for registered textual names +---------------------------------------------- +Registered names should generally be in this format: + "modname:" ( can have characters a-zA-Z0-9_) + +This is to prevent conflicting names from corrupting maps and is +enforced by the mod loader. + +Example: mod "experimental", ideal item/node/entity name "tnt": + -> the name should be "experimental:tnt". + +Enforcement can be overridden by prefixing the name with ":". This can +be used for overriding the registrations of some other mod. + +Example: Any mod can redefine experimental:tnt by using the name + ":experimental:tnt" when registering it. +(also that mod is required to have "experimental" as a dependency) + +The ":" prefix can also be used for maintaining backwards compatibility. + +Aliases +------- +Aliases can be added by using minetest.register_alias(name, convert_to) + +This will make Minetest to convert things called name to things called +convert_to. + +This can be used for maintaining backwards compatibility. + +This can be also used for setting quick access names for things, eg. if +you have an item called epiclylongmodname:stuff, you could do + minetest.register_alias("stuff", "epiclylongmodname:stuff") +and be able to use "/giveme stuff". + +Textures +-------- +Mods should generally prefix their textures with modname_, eg. given +the mod name "foomod", a texture could be called + "foomod_foothing.png" + +Textures are referred to by their complete name, or alternatively by +stripping out the file extension: + eg. foomod_foothing.png + eg. foomod_foothing + +Sounds +------- +Only OGG files are supported. + +For positional playing of sounds, only single-channel (mono) files are +supported. Otherwise OpenAL will play them non-positionally. + +Mods should generally prefix their sounds with modname_, eg. given +the mod name "foomod", a sound could be called + "foomod_foosound.ogg" + +Sounds are referred to by their name with a dot, a single digit and the +file extension stripped out. When a sound is played, the actual sound file +is chosen randomly from the matching sounds. + +When playing the sound "foomod_foosound", the sound is chosen randomly +from the available ones of the following files: + foomod_foosound.ogg + foomod_foosound.0.ogg + foomod_foosound.1.ogg + ... + foomod_foosound.9.ogg + +Examples of sound parameter tables: +-- Play locationless on all clients +{ + gain = 1.0, -- default +} +-- Play locationless to a player +{ + to_player = name, + gain = 1.0, -- default +} +-- Play in a location +{ + pos = {x=1,y=2,z=3}, + gain = 1.0, -- default + max_hear_distance = 32, -- default +} +-- Play connected to an object, looped +{ + object = , + gain = 1.0, -- default + max_hear_distance = 32, -- default + loop = true, -- only sounds connected to objects can be looped +} + +SimpleSoundSpec: +eg. "" +eg. "default_place_node" +eg. {} +eg. {name="default_place_node"} +eg. {name="default_place_node", gain=1.0} + +Registered definitions of stuff +-------------------------------- +Anything added using certain minetest.register_* functions get added to +the global minetest.registered_* tables. + +minetest.register_entity(name, prototype table) + -> minetest.registered_entities[name] + +minetest.register_node(name, node definition) + -> minetest.registered_items[name] + -> minetest.registered_nodes[name] + +minetest.register_tool(name, item definition) + -> minetest.registered_items[name] + +minetest.register_craftitem(name, item definition) + -> minetest.registered_items[name] + +Note that in some cases you will stumble upon things that are not contained +in these tables (eg. when a mod has been removed). Always check for +existence before trying to access the fields. + +Example: If you want to check the drawtype of a node, you could do: + +local function get_nodedef_field(nodename, fieldname) + if not minetest.registered_nodes[nodename] then + return nil + end + return minetest.registered_nodes[nodename][fieldname] +end +local drawtype = get_nodedef_field(nodename, "drawtype") + +Example: minetest.get_item_group(name, group) has been implemented as: + +function minetest.get_item_group(name, group) + if not minetest.registered_items[name] or not + minetest.registered_items[name].groups[group] then + return 0 + end + return minetest.registered_items[name].groups[group] +end + +Nodes +------ +Nodes are the bulk data of the world: cubes and other things that take the +space of a cube. Huge amounts of them are handled efficiently, but they +are quite static. + +The definition of a node is stored and can be accessed by name in + minetest.registered_nodes[node.name] +See "Registered definitions of stuff". + +Nodes are passed by value between Lua and the engine. +They are represented by a table: + {name="name", param1=num, param2=num} + +param1 and param2 are 8 bit integers. The engine uses them for certain +automated functions. If you don't use these functions, you can use them to +store arbitrary values. + +The functions of param1 and param2 are determined by certain fields in the +node definition: +param1 is reserved for the engine when paramtype != "none": + paramtype = "light" + ^ The value stores light with and without sun in it's + upper and lower 4 bits. +param2 is reserved for the engine when any of these are used: + liquidtype == "flowing" + ^ The level and some flags of the liquid is stored in param2 + drawtype == "flowingliquid" + ^ The drawn liquid level is read from param2 + drawtype == "torchlike" + drawtype == "signlike" + paramtype2 == "wallmounted" + ^ The rotation of the node is stored in param2. You can make this value + by using minetest.dir_to_wallmounted(). + paramtype2 == "facedir" + ^ The rotation of the node is stored in param2. Furnaces and chests are + rotated this way. Can be made by using minetest.dir_to_facedir(). + Values range 0 - 23 + facedir modulo 4 = axisdir + 0 = y+ 1 = z+ 2 = z- 3 = x+ 4 = x- 5 = y- + facedir's two less significant bits are rotation around the axis + paramtype2 == "leveled" + ^ The drawn node level is read from param2, like flowingliquid + +Nodes can also contain extra data. See "Node Metadata". + +Node drawtypes +--------------- +There are a bunch of different looking node types. These are mostly just +copied from Minetest 0.3; more may be made in the future. + +Look for examples in games/minimal or games/minetest_game. + +- normal +- airlike +- liquid +- flowingliquid +- glasslike +- glasslike_framed +- allfaces +- allfaces_optional +- torchlike +- signlike +- plantlike +- fencelike +- raillike +- nodebox -- See below. EXPERIMENTAL + +Node boxes +----------- +Node selection boxes are defined using "node boxes" + +The "nodebox" node drawtype allows defining visual of nodes consisting of +arbitrary number of boxes. It allows defining stuff like stairs. Only the +"fixed" and "leveled" box type is supported for these. +^ Please note that this is still experimental, and may be incompatibly + changed in the future. + +A nodebox is defined as any of: +{ + -- A normal cube; the default in most things + type = "regular" +} +{ + -- A fixed box (facedir param2 is used, if applicable) + type = "fixed", + fixed = box OR {box1, box2, ...} +} +{ + -- A box like the selection box for torches + -- (wallmounted param2 is used, if applicable) + type = "wallmounted", + wall_top = box, + wall_bottom = box, + wall_side = box +} + +A box is defined as: + {x1, y1, z1, x2, y2, z2} +A box of a regular node would look like: + {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}, + +type = "leveled" is same as "fixed", but y2 will be automaticaly setted to level from param2 + +Ore types +--------------- +These tell in what manner the ore is generated. +All default ores are of the uniformly-distributed scatter type. + +- scatter + Randomly chooses a location and generates a cluster of ore. + If noise_params is specified, the ore will be placed if the 3d perlin noise at + that point is greater than the noise_threshhold, giving the ability to create a non-equal + distribution of ore. +- sheet + Creates a sheet of ore in a blob shape according to the 2d perlin noise described by noise_params. + The relative height of the sheet can be controlled by the same perlin noise as well, by specifying + a non-zero 'scale' parameter in noise_params. IMPORTANT: The noise is not transformed by offset or + scale when comparing against the noise threshhold, but scale is used to determine relative height. + The height of the blob is randomly scattered, with a maximum height of clust_size. + clust_scarcity and clust_num_ores are ignored. + This is essentially an improved version of the so-called "stratus" ore seen in some unofficial mods. +- claylike - NOT YET IMPLEMENTED + Places ore if there are no more than clust_scarcity number of specified nodes within a Von Neumann + neighborhood of clust_size radius. + +Ore attributes +------------------- +Currently supported flags: absheight + - absheight + Also produce this same ore between the height range of -height_max and -height_min. + Useful for having ore in sky realms without having to duplicate ore entries. + +Decoration types +------------------- +The varying types of decorations that can be placed. +The default value is simple, and is currently the only type supported. + +- simple + Creates a 1xHx1 column of a specified node (or a random node from a list, if a decoration + list is specified). Can specify a certain node it must spawn next to, such as water or lava, + for example. Can also generate a decoration of random height between a specified lower and + upper bound. This type of decoration is intended for placement of grass, flowers, cacti, + papyrus, and so on. +- schematic + Copies a box of MapNodes from a specified schematic file (or raw description). Can specify a + probability of a node randomly appearing when placed. This decoration type is intended to be used + for multi-node sized discrete structures, such as trees, cave spikes, rocks, and so on. + +Schematic specifier +-------------------- + A schematic specifier identifies a schematic by either a filename to a Minetest Schematic file (.mts) +or through raw data supplied through Lua, in the form of a table. This table must specify two fields: + - The 'size' field is a 3d vector containing the dimensions of the provided schematic. + - The 'data' field is a flat table of MapNodes making up the schematic, in the order of [z [y [x]]]. +Important: The default value for param1 in MapNodes here is 255, which represents "always place". + +In the bulk MapNode data, param1, instead of the typical light values, instead represents the +probability of that node appearing in the structure. +When passed to minetest.create_schematic, probability is an integer value ranging from 0 to 255: + - A probability value of 0 means that node will never appear (0% chance). + - A probability value of 255 means the node will always appear (100% chance). + - If the probability value p is greater than 0, then there is a (p / 256 * 100)% chance that node + will appear when the schematic is placed on the map. + +Important note: Node aliases cannot be used for a raw schematic provided when registering as a decoration. + +Schematic attributes +--------------------- +Currently supported flags: place_center_x, place_center_y, place_center_z + - place_center_x + Placement of this decoration is centered along the X axis. + - place_center_y + Placement of this decoration is centered along the Y axis. + - place_center_z + Placement of this decoration is centered along the Z axis. + +HUD element types +------------------- +The position field is used for all element types. +To account for differing resolutions, the position coordinates are the percentage of the screen, +ranging in value from 0 to 1. +The name field is not yet used, but should contain a description of what the HUD element represents. +The direction field is the direction in which something is drawn. +0 draws from left to right, 1 draws from right to left, 2 draws from top to bottom, and 3 draws from bottom to top. +The alignment field specifies how the item will be aligned. It ranges from -1 to 1, +with 0 being the center, -1 is moved to the left/up, and 1 is to the right/down. Fractional +values can be used. +The offset field specifies a pixel offset from the position. Contrary to position, +the offset is not scaled to screen size. This allows for some precisely-positioned +items in the HUD. +Below are the specific uses for fields in each type; fields not listed for that type are ignored. + +Note: Future revisions to the HUD API may be incompatible; the HUD API is still in the experimental stages. + +- image + Displays an image on the HUD. + - scale: The scale of the image, with 1 being the original texture size. + Only the X coordinate scale is used (positive values) + Negative values represent that percentage of the screen it + should take; e.g. x=-100 means 100% (width) + - text: The name of the texture that is displayed. + - alignment: The alignment of the image. + - offset: offset in pixels from position. +- text + Displays text on the HUD. + - scale: Defines the bounding rectangle of the text. + A value such as {x=100, y=100} should work. + - text: The text to be displayed in the HUD element. + - number: An integer containing the RGB value of the color used to draw the text. + Specify 0xFFFFFF for white text, 0xFF0000 for red, and so on. + - alignment: The alignment of the text. + - offset: offset in pixels from position. +- statbar + Displays a horizontal bar made up of half-images. + - text: The name of the texture that is used. + - number: The number of half-textures that are displayed. + If odd, will end with a vertically center-split texture. + - direction + - offset: offset in pixels from position. +- inventory + - text: The name of the inventory list to be displayed. + - number: Number of items in the inventory to be displayed. + - item: Position of item that is selected. + - direction + +Representations of simple things +-------------------------------- +Position/vector: + {x=num, y=num, z=num} +For helper functions see "Vector helpers". + +pointed_thing: + {type="nothing"} + {type="node", under=pos, above=pos} + {type="object", ref=ObjectRef} + +Items +------ +Node (register_node): + A node from the world +Tool (register_tool): + A tool/weapon that can dig and damage things according to tool_capabilities +Craftitem (register_craftitem): + A miscellaneous item + +Items and item stacks can exist in three formats: + +Serialized; This is called stackstring or itemstring: +eg. 'default:dirt 5' +eg. 'default:pick_wood 21323' +eg. 'default:apple' + +Table format: +eg. {name="default:dirt", count=5, wear=0, metadata=""} + ^ 5 dirt nodes +eg. {name="default:pick_wood", count=1, wear=21323, metadata=""} + ^ a wooden pick about 1/3 weared out +eg. {name="default:apple", count=1, wear=0, metadata=""} + ^ an apple. + +ItemStack: +C++ native format with many helper methods. Useful for converting between +formats. See the Class reference section for details. + +When an item must be passed to a function, it can usually be in any of +these formats. + +Groups +------- +In a number of places, there is a group table. Groups define the +properties of a thing (item, node, armor of entity, capabilities of +tool) in such a way that the engine and other mods can can interact with +the thing without actually knowing what the thing is. + +Usage: +- Groups are stored in a table, having the group names with keys and the + group ratings as values. For example: + groups = {crumbly=3, soil=1} + ^ Default dirt + groups = {crumbly=2, soil=1, level=2, outerspace=1} + ^ A more special dirt-kind of thing +- Groups always have a rating associated with them. If there is no + useful meaning for a rating for an enabled group, it shall be 1. +- When not defined, the rating of a group defaults to 0. Thus when you + read groups, you must interpret nil and 0 as the same value, 0. + +You can read the rating of a group for an item or a node by using + minetest.get_item_group(itemname, groupname) + +Groups of items +---------------- +Groups of items can define what kind of an item it is (eg. wool). + +Groups of nodes +---------------- +In addition to the general item things, groups are used to define whether +a node is destroyable and how long it takes to destroy by a tool. + +Groups of entities +------------------- +For entities, groups are, as of now, used only for calculating damage. +The rating is the percentage of damage caused by tools with this damage group. +See "Entity damage mechanism". + +object.get_armor_groups() -> a group-rating table (eg. {fleshy=100}) +object.set_armor_groups({fleshy=30, cracky=80}) + +Groups of tools +---------------- +Groups in tools define which groups of nodes and entities they are +effective towards. + +Groups in crafting recipes +--------------------------- +An example: Make meat soup from any meat, any water and any bowl +{ + output = 'food:meat_soup_raw', + recipe = { + {'group:meat'}, + {'group:water'}, + {'group:bowl'}, + }, + -- preserve = {'group:bowl'}, -- Not implemented yet (TODO) +} +An another example: Make red wool from white wool and red dye +{ + type = 'shapeless', + output = 'wool:red', + recipe = {'wool:white', 'group:dye,basecolor_red'}, +} + +Special groups +--------------- +- immortal: Disables the group damage system for an entity +- level: Can be used to give an additional sense of progression in the game. + - A larger level will cause eg. a weapon of a lower level make much less + damage, and get weared out much faster, or not be able to get drops + from destroyed nodes. + - 0 is something that is directly accessible at the start of gameplay + - There is no upper limit +- dig_immediate: (player can always pick up node without tool wear) + - 2: node is removed without tool wear after 0.5 seconds or so + (rail, sign) + - 3: node is removed without tool wear immediately (torch) +- disable_jump: Player (and possibly other things) cannot jump from node +- fall_damage_add_percent: damage speed = speed * (1 + value/100) +- bouncy: value is bounce speed in percent +- falling_node: if there is no walkable block under the node it will fall +- attached_node: if the node under it is not a walkable block the node will be + dropped as an item. If the node is wallmounted the + wallmounted direction is checked. +- soil: saplings will grow on nodes in this group +- connect_to_raillike: makes nodes of raillike drawtype connect to + other group members with same drawtype + +Known damage and digging time defining groups +---------------------------------------------- +- crumbly: dirt, sand +- cracky: tough but crackable stuff like stone. +- snappy: something that can be cut using fine tools; eg. leaves, small + plants, wire, sheets of metal +- choppy: something that can be cut using force; eg. trees, wooden planks +- fleshy: Living things like animals and the player. This could imply + some blood effects when hitting. +- explody: Especially prone to explosions +- oddly_breakable_by_hand: + Can be added to nodes that shouldn't logically be breakable by the + hand but are. Somewhat similar to dig_immediate, but times are more + like {[1]=3.50,[2]=2.00,[3]=0.70} and this does not override the + speed of a tool if the tool can dig at a faster speed than this + suggests for the hand. + +Examples of custom groups +-------------------------- +Item groups are often used for defining, well, //groups of items//. +- meat: any meat-kind of a thing (rating might define the size or healing + ability or be irrelevant - it is not defined as of yet) +- eatable: anything that can be eaten. Rating might define HP gain in half + hearts. +- flammable: can be set on fire. Rating might define the intensity of the + fire, affecting eg. the speed of the spreading of an open fire. +- wool: any wool (any origin, any color) +- metal: any metal +- weapon: any weapon +- heavy: anything considerably heavy + +Digging time calculation specifics +----------------------------------- +Groups such as **crumbly**, **cracky** and **snappy** are used for this +purpose. Rating is 1, 2 or 3. A higher rating for such a group implies +faster digging time. + +The **level** group is used to limit the toughness of nodes a tool can dig +and to scale the digging times / damage to a greater extent. + +^ PLEASE DO UNDERSTAND THIS, otherwise you cannot use the system to it's + full potential. + +Tools define their properties by a list of parameters for groups. They +cannot dig other groups; thus it is important to use a standard bunch of +groups to enable interaction with tools. + +**Tools define:** + * Full punch interval + * Maximum drop level + * For an arbitrary list of groups: + * Uses (until the tool breaks) + * Maximum level (usually 0, 1, 2 or 3) + * Digging times + * Damage groups + +**Full punch interval**: +When used as a weapon, the tool will do full damage if this time is spent +between punches. If eg. half the time is spent, the tool will do half +damage. + +**Maximum drop level** +Suggests the maximum level of node, when dug with the tool, that will drop +it's useful item. (eg. iron ore to drop a lump of iron). +- This is not automated; it is the responsibility of the node definition + to implement this + +**Uses** +Determines how many uses the tool has when it is used for digging a node, +of this group, of the maximum level. For lower leveled nodes, the use count +is multiplied by 3^leveldiff. +- uses=10, leveldiff=0 -> actual uses: 10 +- uses=10, leveldiff=1 -> actual uses: 30 +- uses=10, leveldiff=2 -> actual uses: 90 + +**Maximum level** +Tells what is the maximum level of a node of this group that the tool will +be able to dig. + +**Digging times** +List of digging times for different ratings of the group, for nodes of the +maximum level. + * For example, as a lua table, ''times={2=2.00, 3=0.70}''. This would + result in the tool to be able to dig nodes that have a rating of 2 or 3 + for this group, and unable to dig the rating 1, which is the toughest. + Unless there is a matching group that enables digging otherwise. + +**Damage groups** +List of damage for groups of entities. See "Entity damage mechanism". + +Example definition of the capabilities of a tool +------------------------------------------------- +tool_capabilities = { + full_punch_interval=1.5, + max_drop_level=1, + groupcaps={ + crumbly={maxlevel=2, uses=20, times={[1]=1.60, [2]=1.20, [3]=0.80}} + } + damage_groups = {fleshy=2}, +} + +This makes the tool be able to dig nodes that fullfill both of these: +- Have the **crumbly** group +- Have a **level** group less or equal to 2 + +Table of resulting digging times: +crumbly 0 1 2 3 4 <- level + -> 0 - - - - - + 1 0.80 1.60 1.60 - - + 2 0.60 1.20 1.20 - - + 3 0.40 0.80 0.80 - - + +level diff: 2 1 0 -1 -2 + +Table of resulting tool uses: + -> 0 - - - - - + 1 180 60 20 - - + 2 180 60 20 - - + 3 180 60 20 - - + +Notes: +- At crumbly=0, the node is not diggable. +- At crumbly=3, the level difference digging time divider kicks in and makes + easy nodes to be quickly breakable. +- At level > 2, the node is not diggable, because it's level > maxlevel + +Entity damage mechanism +------------------------ +Damage calculation: +damage = 0 +foreach group in cap.damage_groups: + damage += cap.damage_groups[group] * limit(actual_interval / cap.full_punch_interval, 0.0, 1.0) + * (object.armor_groups[group] / 100.0) + -- Where object.armor_groups[group] is 0 for inexisting values +return damage + +Client predicts damage based on damage groups. Because of this, it is able to +give an immediate response when an entity is damaged or dies; the response is +pre-defined somehow (eg. by defining a sprite animation) (not implemented; +TODO). +- Currently a smoke puff will appear when an entity dies. + +The group **immortal** completely disables normal damage. + +Entities can define a special armor group, which is **punch_operable**. This +group disables the regular damage mechanism for players punching it by hand or +a non-tool item, so that it can do something else than take damage. + +On the Lua side, every punch calls ''entity:on_punch(puncher, +time_from_last_punch, tool_capabilities, direction)''. This should never be +called directly, because damage is usually not handled by the entity itself. + * ''puncher'' is the object performing the punch. Can be nil. Should never be + accessed unless absolutely required, to encourage interoperability. + * ''time_from_last_punch'' is time from last punch (by puncher) or nil. + * ''tool_capabilities'' can be nil. + * ''direction'' is a unit vector, pointing from the source of the punch to + the punched object. + +To punch an entity/object in Lua, call ''object:punch(puncher, +time_from_last_punch, tool_capabilities, direction)''. + * Return value is tool wear. + * Parameters are equal to the above callback. + * If ''direction'' is nil and ''puncher'' is not nil, ''direction'' will be + automatically filled in based on the location of ''puncher''. + +Node Metadata +------------- +The instance of a node in the world normally only contains the three values +mentioned in "Nodes". However, it is possible to insert extra data into a +node. It is called "node metadata"; See "NodeMetaRef". + +Metadata contains two things: +- A key-value store +- An inventory + +Some of the values in the key-value store are handled specially: +- formspec: Defines a right-click inventory menu. See "Formspec". +- infotext: Text shown on the screen when the node is pointed at + +Example stuff: + +local meta = minetest.get_meta(pos) +meta:set_string("formspec", + "invsize[8,9;]".. + "list[context;main;0,0;8,4;]".. + "list[current_player;main;0,5;8,4;]") +meta:set_string("infotext", "Chest"); +local inv = meta:get_inventory() +inv:set_size("main", 8*4) +print(dump(meta:to_table())) +meta:from_table({ + inventory = { + main = {[1] = "default:dirt", [2] = "", [3] = "", [4] = "", [5] = "", [6] = "", [7] = "", [8] = "", [9] = "", [10] = "", [11] = "", [12] = "", [13] = "", [14] = "default:cobble", [15] = "", [16] = "", [17] = "", [18] = "", [19] = "", [20] = "default:cobble", [21] = "", [22] = "", [23] = "", [24] = "", [25] = "", [26] = "", [27] = "", [28] = "", [29] = "", [30] = "", [31] = "", [32] = ""} + }, + fields = { + formspec = "invsize[8,9;]list[context;main;0,0;8,4;]list[current_player;main;0,5;8,4;]", + infotext = "Chest" + } +}) + +Formspec +-------- +Formspec defines a menu. Currently not much else than inventories are +supported. It is a string, with a somewhat strange format. + +Spaces and newlines can be inserted between the blocks, as is used in the +examples. + +Examples: +- Chest: + invsize[8,9;] + list[context;main;0,0;8,4;] + list[current_player;main;0,5;8,4;] +- Furnace: + invsize[8,9;] + list[context;fuel;2,3;1,1;] + list[context;src;2,1;1,1;] + list[context;dst;5,1;2,2;] + list[current_player;main;0,5;8,4;] +- Minecraft-like player inventory + invsize[8,7.5;] + image[1,0.6;1,2;player.png] + list[current_player;main;0,3.5;8,4;] + list[current_player;craft;3,0;3,3;] + list[current_player;craftpreview;7,1;1,1;] + +Elements: + +size[,] +^ Define the size of the menu in inventory slots +^ deprecated: invsize[,;] + +list[;;,;,;] +list[;;,;,;] +^ Show an inventory list + +listcolors[;] +^ Sets background color of slots in HEX-Color format +^ Sets background color of slots on mouse hovering + +listcolors[;;] +^ Sets background color of slots in HEX-Color format +^ Sets background color of slots on mouse hovering +^ Sets color of slots border + +listcolors[;;;;] +^ Sets background color of slots in HEX-Color format +^ Sets background color of slots on mouse hovering +^ Sets color of slots border +^ Sets background color of tooltips +^ Sets font color of tooltips + +image[,;,;] +^ Show an image +^ Position and size units are inventory slots + +item_image[,;,;] +^ Show an inventory image of registered item/node +^ Position and size units are inventory slots + +bgcolor[;] +^ Sets background color of formspec in HEX-Color format +^ If true the background color is drawn fullscreen (does not effect the size of the formspec) + +background[,;,;] +^ Use a background. Inventory rectangles are not drawn then. +^ Position and size units are inventory slots +^ Example for formspec 8x4 in 16x resolution: image shall be sized 8*16px x 4*16px + +background[,;,;;] +^ Use a background. Inventory rectangles are not drawn then. +^ Position and size units are inventory slots +^ Example for formspec 8x4 in 16x resolution: image shall be sized 8*16px x 4*16px +^ If true the background is clipped to formspec size (x and y are used as offset values, w and h are ignored) + +pwdfield[,;,;;