commit d0bf1148428260543b75a9357b0f6e76bd5ea7b2 Author: devmarth Date: Fri Mar 20 12:41:15 2015 -0400 Fix commits diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2e62a4e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.cpp diff=cpp +*.h diff=cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f12c41f --- /dev/null +++ b/.gitignore @@ -0,0 +1,87 @@ +## Generic ignorable patterns and files +*~ +.*.swp +*bak* +tags +*.vim +*.orig +*.rej + +## Non-static Minetest directories +/bin/ +/games/* +!/games/minimal/ +/cache/ +/textures/* +!/textures/base/ +/sounds/ +/mods/* +!/mods/minetest/ +/mods/minetest/* +!/mods/minetest/mods_here.txt +/worlds/ +/world/ + +## Configuration/log files +minetest.conf +debug.txt + +## Doxygen files +doc/Doxyfile +doc/html/ +doc/doxygen_* + +## Build files +CMakeFiles/* +src/CMakeFiles/* +src/Makefile +src/android_version.h +src/cmake_config.h +src/cmake_config_githash.h +src/cmake_install.cmake +src/script/CMakeFiles/* +src/script/common/CMakeFiles/* +src/script/cpp_api/CMakeFiles/* +src/script/lua_api/CMakeFiles/* +src/util/CMakeFiles/* +src/jthread/CMakeFiles/* +src/jthread/Makefile +src/jthread/cmake_config.h +src/jthread/cmake_install.cmake +src/jthread/libjthread.a +src/json/libjson.a +src/lua/build/ +src/lua/CMakeFiles/ +src/cguittfont/CMakeFiles/ +src/cguittfont/libcguittfont.a +src/cguittfont/cmake_install.cmake +src/cguittfont/Makefile +src/json/CMakeFiles/ +src/json/libjsoncpp.a +src/sqlite/CMakeFiles/* +src/sqlite/libsqlite3.a +src/client/CMakeFiles/ +src/network/CMakeFiles/ +CMakeCache.txt +CPackConfig.cmake +CPackSourceConfig.cmake +Makefile +cmake_install.cmake +locale/ +*.cbp +*.layout +*.o + +## Android build files +build/android/assets +build/android/bin +build/android/Debug +build/android/deps +build/android/gen +build/android/jni/src +build/android/libs +build/android/obj +build/android/path.cfg +build/android/AndroidManifest.xml +timestamp + diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..c487460 --- /dev/null +++ b/.mailmap @@ -0,0 +1,33 @@ +0gb.us <0gb.us@0gb.us> +Calinou +Perttu Ahola celeron55 +Perttu Ahola celeron55 +Craig Robbins +Diego Martínez +Ilya Zhuravlev +kwolekr +PilzAdam PilzAdam +PilzAdam Pilz Adam +PilzAdam PilzAdam +proller +proller +RealBadAngel +RealBadAngel +Selat +ShadowNinja ShadowNinja +Shen Zheyu arsdragonfly +Pavel Elagin elagin +Esteban I. Ruiz Moreno Esteban I. RM +manuel duarte manuel joaquim +manuel duarte sweetbomber +Diego Martínez kaeza +Diego Martínez Diego Martinez +Lord James Lord89James +BlockMen Block Men +sfan5 Sfan5 +DannyDark dannydark +Ilya Pavlov Ilya +Ilya Zhuravlev xyzz +sapier sapier +sapier sapier + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4bce211 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: cpp +compiler: + - gcc + - clang +env: + - WINDOWS=32 + - WINDOWS=64 + - WINDOWS=no +before_install: ./util/travis/before_install.sh +script: ./util/travis/script.sh +notifications: + email: false +matrix: + fast_finish: true + exclude: + - env: WINDOWS=32 + compiler: clang + - env: WINDOWS=64 + compiler: clang diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6091ec7 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,240 @@ +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(blokel) + +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 12) +set(VERSION_PATCH_ORIG ${VERSION_PATCH}) + +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) + set(SHAREDIR ".") + set(BINDIR ".") + set(DOCDIR "./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(APPDATADIR "unix/appdata") + set(ICONDIR "unix/icons") + set(LOCALEDIR "locale") + else() + set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}") + set(BINDIR "${CMAKE_INSTALL_PREFIX}/bin") + set(DOCDIR "${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME}") + set(MANDIR "${CMAKE_INSTALL_PREFIX}/share/man") + set(EXAMPLE_CONF_DIR ${DOCDIR}) + set(XDG_APPS_DIR "${CMAKE_INSTALL_PREFIX}/share/applications") + set(APPDATADIR "${CMAKE_INSTALL_PREFIX}/share/appdata") + set(ICONDIR "${CMAKE_INSTALL_PREFIX}/share/icons") + set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}/locale") + endif() +endif() + +set(CUSTOM_SHAREDIR "" CACHE STRING "Directory to install data files into") +if(NOT CUSTOM_SHAREDIR STREQUAL "") + set(SHAREDIR "${CUSTOM_SHAREDIR}") + message(STATUS "Using SHAREDIR=${SHAREDIR}") +endif() +set(CUSTOM_BINDIR "" CACHE STRING "Directory to install binaries into") +if(NOT CUSTOM_BINDIR STREQUAL "") + set(BINDIR "${CUSTOM_BINDIR}") + message(STATUS "Using BINDIR=${BINDIR}") +endif() +set(CUSTOM_DOCDIR "" CACHE STRING "Directory to install documentation into") +if(NOT CUSTOM_DOCDIR STREQUAL "") + set(DOCDIR "${CUSTOM_DOCDIR}") + 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/menu_lua_api.txt" DESTINATION "${DOCDIR}") +install(FILES "doc/mapformat.txt" DESTINATION "${DOCDIR}") +install(FILES "minetest.conf.example" DESTINATION "${EXAMPLE_CONF_DIR}") + +if(UNIX AND NOT APPLE) + install(FILES "doc/minetest.6" "doc/minetestserver.6" DESTINATION "${MANDIR}/man6") + install(FILES "misc/minetest.desktop" DESTINATION "${XDG_APPS_DIR}") + install(FILES "misc/minetest.appdata.xml" DESTINATION "${APPDATADIR}") + install(FILES "misc/minetest-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() + + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-win64") + else(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-win32") + endif(CMAKE_SIZEOF_VOID_P EQUAL 8) + + 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) + set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-osx") + set(CPACK_PACKAGE_ICON ${CMAKE_CURRENT_SOURCE_DIR}/misc/minetest-icon.icns) + set(CPACK_BUNDLE_NAME ${PROJECT_NAME}) + set(CPACK_BUNDLE_ICON ${CPACK_PACKAGE_ICON}) + set(CPACK_BUNDLE_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/misc/Info.plist) + 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) + +# Add a target to generate API documentation with Doxygen +find_package(Doxygen) +if(DOXYGEN_FOUND) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/doc/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile @ONLY) + add_custom_target(doc + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc + COMMENT "Generating API documentation with Doxygen" VERBATIM + ) +endif(DOXYGEN_FOUND) + diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..56b7c18 --- /dev/null +++ b/README.txt @@ -0,0 +1,254 @@ +Blokel +======== + +Blokel is a game inspired by Minecraft. It's goal is to make a better experience than Minecraft and the like. It's built to be faster, prettier, and better. +Blokel was also made open source as an engine, so other people all over the globe can contribute to the project or use the code to make their own game. + +Blokel is not released, but you can compile the current source to see the progress so far, with instructions below. + +Copyright (c) 2015 Preston (Marth) Cammarata +and contributors from all over the globe (edits from "blokel" or "devmarth" are the same person. which is the lead developer of blokel, as said above) + +Source based on Minetest + +Join us on IRC at #blokel (or #blokel-dev for developers) @freenode.net + +In case you downloaded the source code: +--------------------------------------- +If you downloaded the Blokel Engine source code in which this file is +contained, you probably want to download the blokel_game project too: + https://github.com/blokel/blokel_game/ +See the README.txt in it. (this adds features to the game, rather than it be a minimal test) + +Further documentation +---------------------- +Because it's based on Minetest, documentation is hosted there temporarily, but for now, that'll do. +- 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 +- E: Run +- Shift: sneak/go down +- Q: drop itemstack (+ SHIFT for single item) +- I: inventory +- Mouse: turn/look +- Mouse left: dig/punch +- Mouse right: place/use +- Mouse wheel: select item +- T: chat +- 1-8: select item + +- Esc: pause menu (pauses only singleplayer game) +- R: Enable/Disable full range view +- +: Increase view range +- -: Decrease view range +- K: Enable/Disable fly (needs fly privilege) +- J: Enable/Disable fast (needs fast privilege) +- H: Enable/Disable noclip (needs noclip privilege) + +- F1: Hide/Show HUD +- F2: Hide/Show Chat +- F3: Disable/Enable Fog +- F4: Disable/Enable Camera update (Mapblocks are not updated anymore when disabled) +- F5: Toogle through debug info screens +- F6: Toogle through output data +- F7: Toggle through camera modes +- F10: Show/Hide console +- F12: Take screenshot + + +Compiling +-------------------------------------- +Currently, Blokel is only supported on Linux based operating systems. All compile instructions are the same as Minetest. So if there are no instructions for your current distro, google it for Minetest. + +Ubuntu: +- Create a .sh file with the script from http://pastebin.com/j6tmWKNg +- Goto terminal, and goto the directory with script inside of it, and chmod the file so you can run it. (chmod +x) +- Type ./compilescript.sh (it can be named anything, but compilescript.sh is an example) + +Fedora: +- Create a .sh file with the script from http://pastebin.com/QepTnwGt +- Goto terminal, and goto the directory with the script inside of it, and chmod the file so you can run it. (chmod +x) +- Type ./compilescript.sh (it can be named anything, but compilescript.sh is an example) +- If there are errors, install the dependencies found in the script manually and then compile it. + + + +Note: +- This game is not even closed to being finished, so the only way you can play is by compiling the source yourself. +- Do not expect this to be perfect. It will be buggy, sometimes slow, and even not work at all. + + +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/build/android/AndroidManifest.xml.template b/build/android/AndroidManifest.xml.template new file mode 100644 index 0000000..c93c169 --- /dev/null +++ b/build/android/AndroidManifest.xml.template @@ -0,0 +1,35 @@ + + + + + + + + ###DEBUG_BUILD### + + + + + + + + + + + + + + diff --git a/build/android/build.xml b/build/android/build.xml new file mode 100644 index 0000000..50b48b7 --- /dev/null +++ b/build/android/build.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/build/android/irrlicht-back_button.patch b/build/android/irrlicht-back_button.patch new file mode 100644 index 0000000..227749b --- /dev/null +++ b/build/android/irrlicht-back_button.patch @@ -0,0 +1,19 @@ +--- irrlicht/source/Irrlicht/Android/CIrrDeviceAndroid.cpp 2014-06-03 20:56:21.289559503 +0200 ++++ irrlicht/source/Irrlicht/Android/CIrrDeviceAndroid.cpp.orig 2014-06-03 20:57:39.281556749 +0200 +@@ -423,6 +423,7 @@ + } + + device->postEventFromUser(event); ++ status = 1; + } + break; + default: +@@ -479,7 +480,7 @@ + KeyMap[1] = KEY_LBUTTON; // AKEYCODE_SOFT_LEFT + KeyMap[2] = KEY_RBUTTON; // AKEYCODE_SOFT_RIGHT + KeyMap[3] = KEY_HOME; // AKEYCODE_HOME +- KeyMap[4] = KEY_BACK; // AKEYCODE_BACK ++ KeyMap[4] = KEY_CANCEL; // AKEYCODE_BACK + KeyMap[5] = KEY_UNKNOWN; // AKEYCODE_CALL + KeyMap[6] = KEY_UNKNOWN; // AKEYCODE_ENDCALL + KeyMap[7] = KEY_KEY_0; // AKEYCODE_0 diff --git a/build/android/irrlicht-texturehack.patch b/build/android/irrlicht-texturehack.patch new file mode 100644 index 0000000..a458ede --- /dev/null +++ b/build/android/irrlicht-texturehack.patch @@ -0,0 +1,240 @@ +--- irrlicht/source/Irrlicht/COGLESTexture.cpp.orig 2014-06-22 17:01:13.266568869 +0200 ++++ irrlicht/source/Irrlicht/COGLESTexture.cpp 2014-06-22 17:03:59.298572810 +0200 +@@ -366,112 +366,140 @@ + void(*convert)(const void*, s32, void*) = 0; + getFormatParameters(ColorFormat, InternalFormat, filtering, PixelFormat, PixelType, convert); + +- // make sure we don't change the internal format of existing images +- if (!newTexture) +- InternalFormat = oldInternalFormat; +- +- Driver->setActiveTexture(0, this); +- +- if (Driver->testGLError()) +- os::Printer::log("Could not bind Texture", ELL_ERROR); +- +- // mipmap handling for main texture +- if (!level && newTexture) +- { +- // auto generate if possible and no mipmap data is given +- if (!IsCompressed && HasMipMaps && !mipmapData && Driver->queryFeature(EVDF_MIP_MAP_AUTO_UPDATE)) +- { +- if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED)) +- glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST); +- else if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_QUALITY)) +- glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST); +- else +- glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); ++ bool retry = false; ++ ++ do { ++ if (retry) { ++ InternalFormat = GL_RGBA; ++ PixelFormat = GL_RGBA; ++ convert = CColorConverter::convert_A8R8G8B8toA8B8G8R8; ++ } ++ // make sure we don't change the internal format of existing images ++ if (!newTexture) ++ InternalFormat = oldInternalFormat; + +- glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); +- AutomaticMipmapUpdate=true; +- } ++ Driver->setActiveTexture(0, this); + +- // enable bilinear filter without mipmaps +- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); +- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); +- } ++ if (Driver->testGLError()) ++ os::Printer::log("Could not bind Texture", ELL_ERROR); + +- // now get image data and upload to GPU ++ // mipmap handling for main texture ++ if (!level && newTexture) ++ { ++ // auto generate if possible and no mipmap data is given ++ if (!IsCompressed && HasMipMaps && !mipmapData && Driver->queryFeature(EVDF_MIP_MAP_AUTO_UPDATE)) ++ { ++ if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED)) ++ glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST); ++ else if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_QUALITY)) ++ glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST); ++ else ++ glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); ++ ++ glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); ++ AutomaticMipmapUpdate=true; ++ } ++ ++ // enable bilinear filter without mipmaps ++ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); ++ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); ++ } + +- u32 compressedImageSize = IImage::getCompressedImageSize(ColorFormat, image->getDimension().Width, image->getDimension().Height); ++ // now get image data and upload to GPU + +- void* source = image->lock(); ++ u32 compressedImageSize = IImage::getCompressedImageSize(ColorFormat, image->getDimension().Width, image->getDimension().Height); + +- IImage* tmpImage = 0; ++ void* source = image->lock(); + +- if (convert) +- { +- tmpImage = new CImage(image->getColorFormat(), image->getDimension()); +- void* dest = tmpImage->lock(); +- convert(source, image->getDimension().getArea(), dest); +- image->unlock(); +- source = dest; +- } ++ IImage* tmpImage = 0; + +- if (newTexture) +- { +- if (IsCompressed) ++ if (convert) + { +- glCompressedTexImage2D(GL_TEXTURE_2D, 0, InternalFormat, image->getDimension().Width, +- image->getDimension().Height, 0, compressedImageSize, source); ++ tmpImage = new CImage(image->getColorFormat(), image->getDimension()); ++ void* dest = tmpImage->lock(); ++ convert(source, image->getDimension().getArea(), dest); ++ image->unlock(); ++ source = dest; + } +- else +- glTexImage2D(GL_TEXTURE_2D, level, InternalFormat, image->getDimension().Width, +- image->getDimension().Height, 0, PixelFormat, PixelType, source); +- } +- else +- { +- if (IsCompressed) ++ ++ if (newTexture) + { +- glCompressedTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width, +- image->getDimension().Height, PixelFormat, compressedImageSize, source); ++ if (IsCompressed) ++ { ++ glCompressedTexImage2D(GL_TEXTURE_2D, 0, InternalFormat, image->getDimension().Width, ++ image->getDimension().Height, 0, compressedImageSize, source); ++ } ++ else ++ glTexImage2D(GL_TEXTURE_2D, level, InternalFormat, image->getDimension().Width, ++ image->getDimension().Height, 0, PixelFormat, PixelType, source); + } + else +- glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width, +- image->getDimension().Height, PixelFormat, PixelType, source); +- } +- +- if (convert) +- { +- tmpImage->unlock(); +- tmpImage->drop(); +- } +- else +- image->unlock(); +- +- if (!level && newTexture) +- { +- if (IsCompressed && !mipmapData) + { +- if (image->hasMipMaps()) +- mipmapData = static_cast(image->lock())+compressedImageSize; ++ if (IsCompressed) ++ { ++ glCompressedTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width, ++ image->getDimension().Height, PixelFormat, compressedImageSize, source); ++ } + else +- HasMipMaps = false; ++ glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width, ++ image->getDimension().Height, PixelFormat, PixelType, source); + } + +- regenerateMipMapLevels(mipmapData); +- +- if (HasMipMaps) // might have changed in regenerateMipMapLevels ++ if (convert) + { +- // enable bilinear mipmap filter +- GLint filteringMipMaps = GL_LINEAR_MIPMAP_NEAREST; +- +- if (filtering != GL_LINEAR) +- filteringMipMaps = GL_NEAREST_MIPMAP_NEAREST; ++ tmpImage->unlock(); ++ tmpImage->drop(); ++ } ++ else ++ image->unlock(); ++ ++ if (glGetError() != GL_NO_ERROR) { ++ static bool warned = false; ++ if ((!retry) && (ColorFormat == ECF_A8R8G8B8)) { ++ ++ if (!warned) { ++ os::Printer::log("Your driver claims to support GL_BGRA but fails on trying to upload a texture, converting to GL_RGBA and trying again", ELL_ERROR); ++ warned = true; ++ } ++ } ++ else if (retry) { ++ os::Printer::log("Neither uploading texture as GL_BGRA nor, converted one using GL_RGBA succeeded", ELL_ERROR); ++ } ++ retry = !retry; ++ continue; ++ } else { ++ retry = false; ++ } + +- glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filteringMipMaps); +- glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); ++ if (!level && newTexture) ++ { ++ if (IsCompressed && !mipmapData) ++ { ++ if (image->hasMipMaps()) ++ mipmapData = static_cast(image->lock())+compressedImageSize; ++ else ++ HasMipMaps = false; ++ } ++ ++ regenerateMipMapLevels(mipmapData); ++ ++ if (HasMipMaps) // might have changed in regenerateMipMapLevels ++ { ++ // enable bilinear mipmap filter ++ GLint filteringMipMaps = GL_LINEAR_MIPMAP_NEAREST; ++ ++ if (filtering != GL_LINEAR) ++ filteringMipMaps = GL_NEAREST_MIPMAP_NEAREST; ++ ++ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filteringMipMaps); ++ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); ++ } + } +- } + +- if (Driver->testGLError()) +- os::Printer::log("Could not glTexImage2D", ELL_ERROR); ++ if (Driver->testGLError()) ++ os::Printer::log("Could not glTexImage2D", ELL_ERROR); ++ } ++ while(retry); + } + + +--- irrlicht/source/Irrlicht/COGLESTexture.cpp.orig 2014-06-25 00:28:50.820501856 +0200 ++++ irrlicht/source/Irrlicht/COGLESTexture.cpp 2014-06-25 00:08:37.712544692 +0200 +@@ -422,6 +422,9 @@ + source = dest; + } + ++ //clear old error ++ glGetError(); ++ + if (newTexture) + { + if (IsCompressed) diff --git a/build/android/irrlicht-touchcount.patch b/build/android/irrlicht-touchcount.patch new file mode 100644 index 0000000..d4e4b9c --- /dev/null +++ b/build/android/irrlicht-touchcount.patch @@ -0,0 +1,30 @@ +--- irrlicht.orig/include/IEventReceiver.h 2014-06-03 19:43:50.433713133 +0200 ++++ irrlicht/include/IEventReceiver.h 2014-06-03 19:44:36.993711489 +0200 +@@ -375,6 +375,9 @@ + // Y position of simple touch. + s32 Y; + ++ // number of current touches ++ s32 touchedCount; ++ + //! Type of touch event. + ETOUCH_INPUT_EVENT Event; + }; +--- irrlicht.orig/source/Irrlicht/Android/CIrrDeviceAndroid.cpp 2014-06-03 19:43:50.505713130 +0200 ++++ irrlicht/source/Irrlicht/Android/CIrrDeviceAndroid.cpp 2014-06-03 19:45:37.265709359 +0200 +@@ -315,6 +315,7 @@ + event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, i); + event.TouchInput.X = AMotionEvent_getX(androidEvent, i); + event.TouchInput.Y = AMotionEvent_getY(androidEvent, i); ++ event.TouchInput.touchedCount = AMotionEvent_getPointerCount(androidEvent); + + device->postEventFromUser(event); + } +@@ -326,6 +327,7 @@ + event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, pointerIndex); + event.TouchInput.X = AMotionEvent_getX(androidEvent, pointerIndex); + event.TouchInput.Y = AMotionEvent_getY(androidEvent, pointerIndex); ++ event.TouchInput.touchedCount = AMotionEvent_getPointerCount(androidEvent); + + device->postEventFromUser(event); + } diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk new file mode 100644 index 0000000..2668aad --- /dev/null +++ b/build/android/jni/Android.mk @@ -0,0 +1,329 @@ +LOCAL_PATH := $(call my-dir)/.. + +#LOCAL_ADDRESS_SANITIZER:=true + +include $(CLEAR_VARS) +LOCAL_MODULE := Irrlicht +LOCAL_SRC_FILES := deps/irrlicht/lib/Android/libIrrlicht.a +include $(PREBUILT_STATIC_LIBRARY) + +ifeq ($(HAVE_LEVELDB), 1) + include $(CLEAR_VARS) + LOCAL_MODULE := LevelDB + LOCAL_SRC_FILES := deps/leveldb/libleveldb.a + include $(PREBUILT_STATIC_LIBRARY) +endif + +include $(CLEAR_VARS) +LOCAL_MODULE := curl +LOCAL_SRC_FILES := deps/curl/lib/.libs/libcurl.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := freetype +LOCAL_SRC_FILES := deps/freetype2-android/Android/obj/local/$(TARGET_ARCH_ABI)/libfreetype2-static.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := openal +LOCAL_SRC_FILES := deps/openal-soft/libs/$(TARGET_LIBDIR)/libopenal.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := ogg +LOCAL_SRC_FILES := deps/libvorbis-libogg-android/libs/$(TARGET_LIBDIR)/libogg.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := vorbis +LOCAL_SRC_FILES := deps/libvorbis-libogg-android/libs/$(TARGET_LIBDIR)/libvorbis.so +include $(PREBUILT_SHARED_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := ssl +LOCAL_SRC_FILES := deps/openssl/libssl.a +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := crypto +LOCAL_SRC_FILES := deps/openssl/libcrypto.a +include $(PREBUILT_STATIC_LIBRARY) + + +include $(CLEAR_VARS) +LOCAL_MODULE := blokel + +LOCAL_CPP_FEATURES += exceptions + +ifdef GPROF +GPROF_DEF=-DGPROF +endif + +LOCAL_CFLAGS := -D_IRR_ANDROID_PLATFORM_ \ + -DHAVE_TOUCHSCREENGUI \ + -DUSE_CURL=1 \ + -DUSE_SOUND=1 \ + -DUSE_FREETYPE=1 \ + -DUSE_LEVELDB=$(HAVE_LEVELDB) \ + $(GPROF_DEF) \ + -pipe -fstrict-aliasing + +ifndef NDEBUG +LOCAL_CFLAGS += -g -D_DEBUG -O0 -fno-omit-frame-pointer +else +LOCAL_CFLAGS += -fexpensive-optimizations -O3 +endif + +ifdef GPROF +PROFILER_LIBS := android-ndk-profiler +LOCAL_CFLAGS += -pg +endif + +# LOCAL_CFLAGS += -fsanitize=address +# LOCAL_LDFLAGS += -fsanitize=address + +ifeq ($(TARGET_ARCH_ABI),x86) +LOCAL_CFLAGS += -fno-stack-protector +endif + +LOCAL_C_INCLUDES := \ + jni/src jni/src/sqlite \ + jni/src/script \ + jni/src/lua/src \ + jni/src/json \ + jni/src/cguittfont \ + deps/irrlicht/include \ + deps/freetype2-android/include \ + deps/curl/include \ + deps/openal-soft/jni/OpenAL/include \ + deps/libvorbis-libogg-android/jni/include \ + deps/leveldb/include \ + deps/sqlite/ + +LOCAL_SRC_FILES := \ + jni/src/ban.cpp \ + jni/src/camera.cpp \ + jni/src/cavegen.cpp \ + jni/src/chat.cpp \ + jni/src/client.cpp \ + jni/src/clientiface.cpp \ + jni/src/clientmap.cpp \ + jni/src/clientmedia.cpp \ + jni/src/clientobject.cpp \ + jni/src/clouds.cpp \ + jni/src/collision.cpp \ + jni/src/content_abm.cpp \ + jni/src/content_cao.cpp \ + jni/src/content_cso.cpp \ + jni/src/content_mapblock.cpp \ + jni/src/content_mapnode.cpp \ + jni/src/content_nodemeta.cpp \ + jni/src/content_sao.cpp \ + jni/src/convert_json.cpp \ + jni/src/craftdef.cpp \ + jni/src/database-dummy.cpp \ + jni/src/database-sqlite3.cpp \ + jni/src/database.cpp \ + jni/src/debug.cpp \ + jni/src/defaultsettings.cpp \ + jni/src/drawscene.cpp \ + jni/src/dungeongen.cpp \ + jni/src/emerge.cpp \ + jni/src/environment.cpp \ + jni/src/filecache.cpp \ + jni/src/filesys.cpp \ + jni/src/fontengine.cpp \ + jni/src/game.cpp \ + jni/src/genericobject.cpp \ + jni/src/gettext.cpp \ + jni/src/guiChatConsole.cpp \ + jni/src/guiEngine.cpp \ + jni/src/guiFileSelectMenu.cpp \ + jni/src/guiFormSpecMenu.cpp \ + jni/src/guiKeyChangeMenu.cpp \ + jni/src/guiPasswordChange.cpp \ + jni/src/guiTable.cpp \ + jni/src/guiVolumeChange.cpp \ + jni/src/httpfetch.cpp \ + jni/src/hud.cpp \ + jni/src/inventory.cpp \ + jni/src/inventorymanager.cpp \ + jni/src/itemdef.cpp \ + jni/src/keycode.cpp \ + jni/src/light.cpp \ + jni/src/localplayer.cpp \ + jni/src/log.cpp \ + jni/src/main.cpp \ + jni/src/map.cpp \ + jni/src/mapblock.cpp \ + jni/src/mapblock_mesh.cpp \ + jni/src/mapgen.cpp \ + jni/src/mapgen_singlenode.cpp \ + jni/src/mapgen_v5.cpp \ + jni/src/mapgen_v6.cpp \ + jni/src/mapgen_v7.cpp \ + jni/src/mapnode.cpp \ + jni/src/mapsector.cpp \ + jni/src/mesh.cpp \ + jni/src/mg_biome.cpp \ + jni/src/mg_decoration.cpp \ + jni/src/mg_ore.cpp \ + jni/src/mg_schematic.cpp \ + jni/src/mods.cpp \ + jni/src/nameidmapping.cpp \ + jni/src/nodedef.cpp \ + jni/src/nodemetadata.cpp \ + jni/src/nodetimer.cpp \ + jni/src/noise.cpp \ + jni/src/object_properties.cpp \ + jni/src/particles.cpp \ + jni/src/pathfinder.cpp \ + jni/src/player.cpp \ + jni/src/porting_android.cpp \ + jni/src/porting.cpp \ + jni/src/quicktune.cpp \ + jni/src/rollback.cpp \ + jni/src/rollback_interface.cpp \ + jni/src/serialization.cpp \ + jni/src/server.cpp \ + jni/src/serverlist.cpp \ + jni/src/serverobject.cpp \ + jni/src/shader.cpp \ + jni/src/sky.cpp \ + jni/src/socket.cpp \ + jni/src/sound.cpp \ + jni/src/sound_openal.cpp \ + jni/src/staticobject.cpp \ + jni/src/subgame.cpp \ + jni/src/test.cpp \ + jni/src/tool.cpp \ + jni/src/treegen.cpp \ + jni/src/version.cpp \ + jni/src/voxel.cpp \ + jni/src/voxelalgorithms.cpp \ + jni/src/util/base64.cpp \ + jni/src/util/directiontables.cpp \ + jni/src/util/numeric.cpp \ + jni/src/util/pointedthing.cpp \ + jni/src/util/serialize.cpp \ + jni/src/util/sha1.cpp \ + jni/src/util/string.cpp \ + jni/src/util/timetaker.cpp \ + jni/src/touchscreengui.cpp \ + jni/src/database-leveldb.cpp \ + jni/src/settings.cpp \ + jni/src/wieldmesh.cpp \ + jni/src/client/clientlauncher.cpp \ + jni/src/client/tile.cpp + +# Network +LOCAL_SRC_FILES += \ + jni/src/network/connection.cpp \ + jni/src/network/networkpacket.cpp \ + jni/src/network/clientopcodes.cpp \ + jni/src/network/serveropcodes.cpp \ + jni/src/network/packethandlers/server.cpp \ + jni/src/network/packethandlers/client.cpp + +# lua api +LOCAL_SRC_FILES += \ + jni/src/script/common/c_content.cpp \ + jni/src/script/common/c_converter.cpp \ + jni/src/script/common/c_internal.cpp \ + jni/src/script/common/c_types.cpp \ + jni/src/script/cpp_api/s_base.cpp \ + jni/src/script/cpp_api/s_entity.cpp \ + jni/src/script/cpp_api/s_env.cpp \ + jni/src/script/cpp_api/s_inventory.cpp \ + jni/src/script/cpp_api/s_item.cpp \ + jni/src/script/cpp_api/s_mainmenu.cpp \ + jni/src/script/cpp_api/s_node.cpp \ + jni/src/script/cpp_api/s_nodemeta.cpp \ + jni/src/script/cpp_api/s_player.cpp \ + jni/src/script/cpp_api/s_server.cpp \ + jni/src/script/cpp_api/s_async.cpp \ + jni/src/script/lua_api/l_base.cpp \ + jni/src/script/lua_api/l_craft.cpp \ + jni/src/script/lua_api/l_env.cpp \ + jni/src/script/lua_api/l_inventory.cpp \ + jni/src/script/lua_api/l_item.cpp \ + jni/src/script/lua_api/l_mainmenu.cpp \ + jni/src/script/lua_api/l_mapgen.cpp \ + jni/src/script/lua_api/l_nodemeta.cpp \ + jni/src/script/lua_api/l_nodetimer.cpp \ + jni/src/script/lua_api/l_noise.cpp \ + jni/src/script/lua_api/l_object.cpp \ + jni/src/script/lua_api/l_particles.cpp \ + jni/src/script/lua_api/l_rollback.cpp \ + jni/src/script/lua_api/l_server.cpp \ + jni/src/script/lua_api/l_settings.cpp \ + jni/src/script/lua_api/l_util.cpp \ + jni/src/script/lua_api/l_vmanip.cpp \ + jni/src/script/scripting_game.cpp \ + jni/src/script/scripting_mainmenu.cpp + +#freetype2 support +LOCAL_SRC_FILES += \ + jni/src/cguittfont/xCGUITTFont.cpp + +# lua +LOCAL_SRC_FILES += \ + jni/src/lua/src/lapi.c \ + jni/src/lua/src/lauxlib.c \ + jni/src/lua/src/lbaselib.c \ + jni/src/lua/src/lcode.c \ + jni/src/lua/src/ldblib.c \ + jni/src/lua/src/ldebug.c \ + jni/src/lua/src/ldo.c \ + jni/src/lua/src/ldump.c \ + jni/src/lua/src/lfunc.c \ + jni/src/lua/src/lgc.c \ + jni/src/lua/src/linit.c \ + jni/src/lua/src/liolib.c \ + jni/src/lua/src/llex.c \ + jni/src/lua/src/lmathlib.c \ + jni/src/lua/src/lmem.c \ + jni/src/lua/src/loadlib.c \ + jni/src/lua/src/lobject.c \ + jni/src/lua/src/lopcodes.c \ + jni/src/lua/src/loslib.c \ + jni/src/lua/src/lparser.c \ + jni/src/lua/src/lstate.c \ + jni/src/lua/src/lstring.c \ + jni/src/lua/src/lstrlib.c \ + jni/src/lua/src/ltable.c \ + jni/src/lua/src/ltablib.c \ + jni/src/lua/src/ltm.c \ + jni/src/lua/src/lundump.c \ + jni/src/lua/src/lvm.c \ + jni/src/lua/src/lzio.c \ + jni/src/lua/src/print.c + +# sqlite +LOCAL_SRC_FILES += deps/sqlite/sqlite3.c + +# jthread +LOCAL_SRC_FILES += \ + jni/src/jthread/pthread/jevent.cpp \ + jni/src/jthread/pthread/jmutex.cpp \ + jni/src/jthread/pthread/jsemaphore.cpp \ + jni/src/jthread/pthread/jthread.cpp + +# json +LOCAL_SRC_FILES += jni/src/json/jsoncpp.cpp + +LOCAL_SHARED_LIBRARIES := openal ogg vorbis +LOCAL_STATIC_LIBRARIES := Irrlicht freetype curl ssl crypto android_native_app_glue $(PROFILER_LIBS) + +ifeq ($(HAVE_LEVELDB), 1) + LOCAL_STATIC_LIBRARIES += LevelDB +endif +LOCAL_LDLIBS := -lEGL -llog -lGLESv1_CM -lGLESv2 -lz -landroid + +include $(BUILD_SHARED_LIBRARY) + +# at the end of Android.mk +ifdef GPROF +$(call import-module,android-ndk-profiler) +endif +$(call import-module,android/native_app_glue) diff --git a/build/android/jni/Application.mk b/build/android/jni/Application.mk new file mode 100644 index 0000000..b7ffc56 --- /dev/null +++ b/build/android/jni/Application.mk @@ -0,0 +1,8 @@ +# NDK_TOOLCHAIN_VERSION := clang3.3 + +APP_PLATFORM := android-9 +APP_MODULES := minetest +APP_STL := gnustl_static + +APP_CPPFLAGS += -fexceptions +APP_GNUSTL_FORCE_CPP_FEATURES := rtti diff --git a/build/android/libvorbis-libogg-fpu.patch b/build/android/libvorbis-libogg-fpu.patch new file mode 100644 index 0000000..52ab397 --- /dev/null +++ b/build/android/libvorbis-libogg-fpu.patch @@ -0,0 +1,37 @@ +--- libvorbis-libogg-android/jni/libvorbis-jni/Android.mk.orig 2014-06-17 19:22:50.621559073 +0200 ++++ libvorbis-libogg-android/jni/libvorbis-jni/Android.mk 2014-06-17 19:38:20.641581140 +0200 +@@ -4,9 +4,6 @@ + + LOCAL_MODULE := vorbis-jni + LOCAL_CFLAGS += -I$(LOCAL_PATH)/../include -fsigned-char +-ifeq ($(TARGET_ARCH),arm) +- LOCAL_CFLAGS += -march=armv6 -marm -mfloat-abi=softfp -mfpu=vfp +-endif + + LOCAL_SHARED_LIBRARIES := libogg libvorbis + +--- libvorbis-libogg-android/jni/libvorbis/Android.mk.orig 2014-06-17 19:22:39.077558797 +0200 ++++ libvorbis-libogg-android/jni/libvorbis/Android.mk 2014-06-17 19:38:52.121581887 +0200 +@@ -4,9 +4,6 @@ + + LOCAL_MODULE := libvorbis + LOCAL_CFLAGS += -I$(LOCAL_PATH)/../include -ffast-math -fsigned-char +-ifeq ($(TARGET_ARCH),arm) +- LOCAL_CFLAGS += -march=armv6 -marm -mfloat-abi=softfp -mfpu=vfp +-endif + LOCAL_SHARED_LIBRARIES := libogg + + LOCAL_SRC_FILES := \ +--- libvorbis-libogg-android/jni/libogg/Android.mk.orig 2014-06-17 19:22:33.965558675 +0200 ++++ libvorbis-libogg-android/jni/libogg/Android.mk 2014-06-17 19:38:25.337581252 +0200 +@@ -4,10 +4,6 @@ + + LOCAL_MODULE := libogg + LOCAL_CFLAGS += -I$(LOCAL_PATH)/../include -ffast-math -fsigned-char +-ifeq ($(TARGET_ARCH),arm) +- LOCAL_CFLAGS += -march=armv6 -marm -mfloat-abi=softfp -mfpu=vfp +-endif +- + + LOCAL_SRC_FILES := \ + bitwise.c \ diff --git a/build/android/openssl_arch.patch b/build/android/openssl_arch.patch new file mode 100644 index 0000000..d9ebbd5 --- /dev/null +++ b/build/android/openssl_arch.patch @@ -0,0 +1,11 @@ +--- openssl-1.0.1j/Configure.orig 2014-10-15 14:53:39.000000000 +0200 ++++ openssl-1.0.1j/Configure 2015-01-03 22:41:43.505749921 +0100 +@@ -407,6 +407,8 @@ + "android","gcc:-mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -fomit-frame-pointer -Wall::-D_REENTRANT::-ldl:BN_LLONG RC4_CHAR RC4_CHUNK DES_INT DES_UNROLL BF_PTR:${no_asm}:dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)", + "android-x86","gcc:-mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -fomit-frame-pointer -Wall::-D_REENTRANT::-ldl:BN_LLONG ${x86_gcc_des} ${x86_gcc_opts}:".eval{my $asm=${x86_elf_asm};$asm=~s/:elf/:android/;$asm}.":dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)", + "android-armv7","gcc:-march=armv7-a -mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -fomit-frame-pointer -Wall::-D_REENTRANT::-ldl:BN_LLONG RC4_CHAR RC4_CHUNK DES_INT DES_UNROLL BF_PTR:${armv4_asm}:dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)", ++"android-arm","gcc:-march=armv4 -mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -fomit-frame-pointer -Wall::-D_REENTRANT::-ldl:BN_LLONG RC4_CHAR RC4_CHUNK DES_INT DES_UNROLL BF_PTR:${armv4_asm}:dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)", ++"android-mips32","gcc:-march=mips32 -mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -fomit-frame-pointer -Wall::-D_REENTRANT::-ldl:BN_LLONG RC4_CHAR RC4_CHUNK DES_INT DES_UNROLL BF_PTR:${mips32_asm}:o32:dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)", + + #### *BSD [do see comment about ${BSDthreads} above!] + "BSD-generic32","gcc:-DTERMIOS -O3 -fomit-frame-pointer -Wall::${BSDthreads}:::BN_LLONG RC2_CHAR RC4_INDEX DES_INT DES_UNROLL:${no_asm}:dlfcn:bsd-gcc-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)", diff --git a/build/android/project.properties b/build/android/project.properties new file mode 100644 index 0000000..cc2a7c5 --- /dev/null +++ b/build/android/project.properties @@ -0,0 +1 @@ +target=android-10 diff --git a/build/android/res/drawable-hdpi/irr_icon.png b/build/android/res/drawable-hdpi/irr_icon.png new file mode 100644 index 0000000..0b6861a Binary files /dev/null and b/build/android/res/drawable-hdpi/irr_icon.png differ diff --git a/build/android/res/drawable-ldpi/irr_icon.png b/build/android/res/drawable-ldpi/irr_icon.png new file mode 100644 index 0000000..b8c5d01 Binary files /dev/null and b/build/android/res/drawable-ldpi/irr_icon.png differ diff --git a/build/android/res/drawable-mdpi/irr_icon.png b/build/android/res/drawable-mdpi/irr_icon.png new file mode 100644 index 0000000..951a7f8 Binary files /dev/null and b/build/android/res/drawable-mdpi/irr_icon.png differ diff --git a/build/android/res/drawable-xhdpi/irr_icon.png b/build/android/res/drawable-xhdpi/irr_icon.png new file mode 100644 index 0000000..2ec528e Binary files /dev/null and b/build/android/res/drawable-xhdpi/irr_icon.png differ diff --git a/build/android/res/layout/assetcopy.xml b/build/android/res/layout/assetcopy.xml new file mode 100644 index 0000000..ade4b0c --- /dev/null +++ b/build/android/res/layout/assetcopy.xml @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/build/android/res/values/styles.xml b/build/android/res/values/styles.xml new file mode 100644 index 0000000..25b8df5 --- /dev/null +++ b/build/android/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/build/android/src/net/minetest/minetest/MinetestAssetCopy.java b/build/android/src/net/minetest/minetest/MinetestAssetCopy.java new file mode 100644 index 0000000..9a72234 --- /dev/null +++ b/build/android/src/net/minetest/minetest/MinetestAssetCopy.java @@ -0,0 +1,456 @@ +package net.blokel.blokel; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.Vector; +import java.util.Iterator; +import java.lang.Object; + +import android.app.Activity; +import android.content.res.AssetFileDescriptor; + +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Environment; +import android.util.Log; +import android.view.Display; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.graphics.Rect; +import android.graphics.Paint; +import android.text.TextPaint; + +public class BlokelAssetCopy extends Activity +{ + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + setContentView(R.layout.assetcopy); + + m_ProgressBar = (ProgressBar) findViewById(R.id.progressBar1); + m_Filename = (TextView) findViewById(R.id.textView1); + + Display display = getWindowManager().getDefaultDisplay(); + m_ProgressBar.getLayoutParams().width = (int) (display.getWidth() * 0.8); + m_ProgressBar.invalidate(); + + /* check if there's already a copy in progress and reuse in case it is*/ + BlokelAssetCopy prevActivity = + (BlokelAssetCopy) getLastNonConfigurationInstance(); + if(prevActivity!= null) { + m_AssetCopy = prevActivity.m_AssetCopy; + } + else { + m_AssetCopy = new copyAssetTask(); + m_AssetCopy.execute(); + } + } + + /* preserve asset copy background task to prevent restart of copying */ + /* this way of doing it is not recommended for latest android version */ + /* but the recommended way isn't available on android 2.x */ + public Object onRetainNonConfigurationInstance() + { + return this; + } + + ProgressBar m_ProgressBar; + TextView m_Filename; + + copyAssetTask m_AssetCopy; + + private class copyAssetTask extends AsyncTask + { + private long getFullSize(String filename) + { + long size = 0; + try { + InputStream src = getAssets().open(filename); + byte[] buf = new byte[4096]; + + int len = 0; + while ((len = src.read(buf)) > 0) + { + size += len; + } + } + catch (IOException e) + { + e.printStackTrace(); + } + return size; + } + + @Override + protected String doInBackground(String... files) + { + m_foldernames = new Vector(); + m_filenames = new Vector(); + m_tocopy = new Vector(); + m_asset_size_unknown = new Vector(); + String baseDir = + Environment.getExternalStorageDirectory().getAbsolutePath() + + "/"; + + + // prepare temp folder + File TempFolder = new File(baseDir + "Blokel/tmp/"); + + if (!TempFolder.exists()) + { + TempFolder.mkdir(); + } + else { + File[] todel = TempFolder.listFiles(); + + for(int i=0; i < todel.length; i++) + { + Log.v("BlokelAssetCopy","deleting: " + todel[i].getAbsolutePath()); + todel[i].delete(); + } + } + + // add a .nomedia file + try { + OutputStream dst = new FileOutputStream(baseDir + "Blokel/.nomedia"); + dst.close(); + } catch (IOException e) { + Log.e("BlokelAssetCopy","Failed to create .nomedia file"); + e.printStackTrace(); + } + + + // build lists from prepared data + BuildFolderList(); + BuildFileList(); + + // scan filelist + ProcessFileList(); + + // doing work + m_copy_started = true; + m_ProgressBar.setMax(m_tocopy.size()); + + for (int i = 0; i < m_tocopy.size(); i++) + { + try + { + String filename = m_tocopy.get(i); + publishProgress(i); + + boolean asset_size_unknown = false; + long filesize = -1; + + if (m_asset_size_unknown.contains(filename)) + { + File testme = new File(baseDir + "/" + filename); + + if(testme.exists()) + { + filesize = testme.length(); + } + asset_size_unknown = true; + } + + InputStream src; + try + { + src = getAssets().open(filename); + } catch (IOException e) { + Log.e("BlokelAssetCopy","Copying file: " + filename + " FAILED (not in assets)"); + e.printStackTrace(); + continue; + } + + // Transfer bytes from in to out + byte[] buf = new byte[1*1024]; + int len = src.read(buf, 0, 1024); + + /* following handling is crazy but we need to deal with */ + /* compressed assets.Flash chips limited livetime due to */ + /* write operations, we can't allow large files to destroy */ + /* users flash. */ + if (asset_size_unknown) + { + if ( (len > 0) && (len < buf.length) && (len == filesize)) + { + src.close(); + continue; + } + + if (len == buf.length) + { + src.close(); + long size = getFullSize(filename); + if ( size == filesize) + { + continue; + } + src = getAssets().open(filename); + len = src.read(buf, 0, 1024); + } + } + if (len > 0) + { + int total_filesize = 0; + OutputStream dst; + try + { + dst = new FileOutputStream(baseDir + "/" + filename); + } catch (IOException e) { + Log.e("BlokelAssetCopy","Copying file: " + baseDir + + "/" + filename + " FAILED (couldn't open output file)"); + e.printStackTrace(); + src.close(); + continue; + } + dst.write(buf, 0, len); + total_filesize += len; + + while ((len = src.read(buf)) > 0) + { + dst.write(buf, 0, len); + total_filesize += len; + } + + dst.close(); + Log.v("BlokelAssetCopy","Copied file: " + + m_tocopy.get(i) + " (" + total_filesize + + " bytes)"); + } + else if (len < 0) + { + Log.e("BlokelAssetCopy","Copying file: " + + m_tocopy.get(i) + " failed, size < 0"); + } + src.close(); + } + catch (IOException e) + { + Log.e("BlokelAssetCopy","Copying file: " + + m_tocopy.get(i) + " failed"); + e.printStackTrace(); + } + } + return ""; + } + + + /** + * update progress bar + */ + protected void onProgressUpdate(Integer... progress) + { + + if (m_copy_started) + { + boolean shortened = false; + String todisplay = m_tocopy.get(progress[0]); + m_ProgressBar.setProgress(progress[0]); + + // make sure our text doesn't exceed our layout width + Rect bounds = new Rect(); + Paint textPaint = m_Filename.getPaint(); + textPaint.getTextBounds(todisplay, 0, todisplay.length(), bounds); + + while (bounds.width() > getResources().getDisplayMetrics().widthPixels * 0.7) { + if (todisplay.length() < 2) { + break; + } + todisplay = todisplay.substring(1); + textPaint.getTextBounds(todisplay, 0, todisplay.length(), bounds); + shortened = true; + } + + if (! shortened) { + m_Filename.setText(todisplay); + } + else { + m_Filename.setText(".." + todisplay); + } + } + else + { + boolean shortened = false; + String todisplay = m_Foldername; + String full_text = "scanning " + todisplay + " ..."; + // make sure our text doesn't exceed our layout width + Rect bounds = new Rect(); + Paint textPaint = m_Filename.getPaint(); + textPaint.getTextBounds(full_text, 0, full_text.length(), bounds); + + while (bounds.width() > getResources().getDisplayMetrics().widthPixels * 0.7) { + if (todisplay.length() < 2) { + break; + } + todisplay = todisplay.substring(1); + full_text = "scanning " + todisplay + " ..."; + textPaint.getTextBounds(full_text, 0, full_text.length(), bounds); + shortened = true; + } + + if (! shortened) { + m_Filename.setText(full_text); + } + else { + m_Filename.setText("scanning .." + todisplay + " ..."); + } + } + } + + /** + * check al files and folders in filelist + */ + protected void ProcessFileList() + { + String FlashBaseDir = + Environment.getExternalStorageDirectory().getAbsolutePath(); + + Iterator itr = m_filenames.iterator(); + + while (itr.hasNext()) + { + String current_path = (String) itr.next(); + String FlashPath = FlashBaseDir + "/" + current_path; + + if (isAssetFolder(current_path)) + { + /* store information and update gui */ + m_Foldername = current_path; + publishProgress(0); + + /* open file in order to check if it's a folder */ + File current_folder = new File(FlashPath); + if (!current_folder.exists()) + { + if (!current_folder.mkdirs()) + { + Log.e("BlokelAssetCopy","\t failed create folder: " + + FlashPath); + } + else + { + Log.v("BlokelAssetCopy","\t created folder: " + + FlashPath); + } + } + + continue; + } + + /* if it's not a folder it's most likely a file */ + boolean refresh = true; + + File testme = new File(FlashPath); + + long asset_filesize = -1; + long stored_filesize = -1; + + if (testme.exists()) + { + try + { + AssetFileDescriptor fd = getAssets().openFd(current_path); + asset_filesize = fd.getLength(); + fd.close(); + } + catch (IOException e) + { + refresh = true; + m_asset_size_unknown.add(current_path); + Log.e("BlokelAssetCopy","Failed to open asset file \"" + + FlashPath + "\" for size check"); + } + + stored_filesize = testme.length(); + + if (asset_filesize == stored_filesize) + { + refresh = false; + } + + } + + if (refresh) + { + m_tocopy.add(current_path); + } + } + } + + /** + * read list of folders prepared on package build + */ + protected void BuildFolderList() + { + try + { + InputStream is = getAssets().open("index.txt"); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + + String line = reader.readLine(); + while (line != null) + { + m_foldernames.add(line); + line = reader.readLine(); + } + is.close(); + } catch (IOException e1) + { + Log.e("BlokelAssetCopy","Error on processing index.txt"); + e1.printStackTrace(); + } + } + + /** + * read list of asset files prepared on package build + */ + protected void BuildFileList() + { + long entrycount = 0; + try + { + InputStream is = getAssets().open("filelist.txt"); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + + String line = reader.readLine(); + while (line != null) + { + m_filenames.add(line); + line = reader.readLine(); + entrycount ++; + } + is.close(); + } + catch (IOException e1) + { + Log.e("BlokelAssetCopy","Error on processing filelist.txt"); + e1.printStackTrace(); + } + } + + protected void onPostExecute (String result) + { + finish(); + } + + protected boolean isAssetFolder(String path) + { + return m_foldernames.contains(path); + } + + boolean m_copy_started = false; + String m_Foldername = "media"; + Vector m_foldernames; + Vector m_filenames; + Vector m_tocopy; + Vector m_asset_size_unknown; + } +} diff --git a/build/android/src/net/minetest/minetest/MinetestTextEntry.java b/build/android/src/net/minetest/minetest/MinetestTextEntry.java new file mode 100644 index 0000000..6e188ee --- /dev/null +++ b/build/android/src/net/minetest/minetest/MinetestTextEntry.java @@ -0,0 +1,91 @@ +package net.blokel.blokel; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.text.InputType; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.View.OnKeyListener; +import android.widget.EditText; + +public class BlokelTextEntry extends Activity { + public AlertDialog mTextInputDialog; + public EditText mTextInputWidget; + + private final int MultiLineTextInput = 1; + private final int SingleLineTextInput = 2; + private final int SingleLinePasswordInput = 3; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle b = getIntent().getExtras(); + String acceptButton = b.getString("EnterButton"); + String hint = b.getString("hint"); + String current = b.getString("current"); + int editType = b.getInt("editType"); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + mTextInputWidget = new EditText(this); + mTextInputWidget.setHint(hint); + mTextInputWidget.setText(current); + mTextInputWidget.setMinWidth(300); + if (editType == SingleLinePasswordInput) { + mTextInputWidget.setInputType(InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_VARIATION_PASSWORD); + } + else { + mTextInputWidget.setInputType(InputType.TYPE_CLASS_TEXT); + } + + + builder.setView(mTextInputWidget); + + if (editType == MultiLineTextInput) { + builder.setPositiveButton(acceptButton, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) + { pushResult(mTextInputWidget.getText().toString()); } + }); + } + + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + cancelDialog(); + } + }); + + mTextInputWidget.setOnKeyListener(new OnKeyListener() { + @Override + public boolean onKey(View view, int KeyCode, KeyEvent event) { + if ( KeyCode == KeyEvent.KEYCODE_ENTER){ + + pushResult(mTextInputWidget.getText().toString()); + return true; + } + return false; + } + }); + + mTextInputDialog = builder.create(); + mTextInputDialog.show(); + } + + public void pushResult(String text) { + Intent resultData = new Intent(); + resultData.putExtra("text", text); + setResult(Activity.RESULT_OK,resultData); + mTextInputDialog.dismiss(); + finish(); + } + + public void cancelDialog() { + setResult(Activity.RESULT_CANCELED); + mTextInputDialog.dismiss(); + finish(); + } +} diff --git a/build/android/src/net/minetest/minetest/MtNativeActivity.java b/build/android/src/net/minetest/minetest/MtNativeActivity.java new file mode 100644 index 0000000..bf6e366 --- /dev/null +++ b/build/android/src/net/minetest/minetest/MtNativeActivity.java @@ -0,0 +1,93 @@ +package net.blokel.blokel; + +import android.app.NativeActivity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.WindowManager; + +public class MtNativeActivity extends NativeActivity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + m_MessagReturnCode = -1; + m_MessageReturnValue = ""; + + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + + public void copyAssets() { + Intent intent = new Intent(this, BlokelAssetCopy.class); + startActivity(intent); + } + + public void showDialog(String acceptButton, String hint, String current, + int editType) { + + Intent intent = new Intent(this, BlokelTextEntry.class); + Bundle params = new Bundle(); + params.putString("acceptButton", acceptButton); + params.putString("hint", hint); + params.putString("current", current); + params.putInt("editType", editType); + intent.putExtras(params); + startActivityForResult(intent, 101); + m_MessageReturnValue = ""; + m_MessagReturnCode = -1; + } + + public static native void putMessageBoxResult(String text); + + /* ugly code to workaround putMessageBoxResult not beeing found */ + public int getDialogState() { + return m_MessagReturnCode; + } + + public String getDialogValue() { + m_MessagReturnCode = -1; + return m_MessageReturnValue; + } + + public float getDensity() { + return getResources().getDisplayMetrics().density; + } + + public int getDisplayWidth() { + return getResources().getDisplayMetrics().widthPixels; + } + + public int getDisplayHeight() { + return getResources().getDisplayMetrics().heightPixels; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, + Intent data) { + if (requestCode == 101) { + if (resultCode == RESULT_OK) { + String text = data.getStringExtra("text"); + m_MessagReturnCode = 0; + m_MessageReturnValue = text; + } + else { + m_MessagReturnCode = 1; + } + } + } + + static { + System.loadLibrary("openal"); + System.loadLibrary("ogg"); + System.loadLibrary("vorbis"); + System.loadLibrary("ssl"); + System.loadLibrary("crypto"); + } + + private int m_MessagReturnCode; + private String m_MessageReturnValue; +} diff --git a/builtin/async/init.lua b/builtin/async/init.lua new file mode 100644 index 0000000..1b25496 --- /dev/null +++ b/builtin/async/init.lua @@ -0,0 +1,17 @@ + +core.log("info", "Initializing Asynchronous environment") + +function core.job_processor(serialized_func, serialized_param) + local func = loadstring(serialized_func) + local param = core.deserialize(serialized_param) + local retval = nil + + if type(func) == "function" then + retval = core.serialize(func(param)) + else + core.log("error", "ASYNC WORKER: Unable to deserialize function") + end + + return retval or core.serialize(nil) +end + diff --git a/builtin/common/async_event.lua b/builtin/common/async_event.lua new file mode 100644 index 0000000..988af79 --- /dev/null +++ b/builtin/common/async_event.lua @@ -0,0 +1,40 @@ + +core.async_jobs = {} + +local function handle_job(jobid, serialized_retval) + local retval = core.deserialize(serialized_retval) + assert(type(core.async_jobs[jobid]) == "function") + core.async_jobs[jobid](retval) + core.async_jobs[jobid] = nil +end + +if core.register_globalstep then + core.register_globalstep(function(dtime) + for i, job in ipairs(core.get_finished_jobs()) do + handle_job(job.jobid, job.retval) + end + end) +else + core.async_event_handler = handle_job +end + +function core.handle_async(func, parameter, callback) + -- Serialize function + local serialized_func = string.dump(func) + + assert(serialized_func ~= nil) + + -- Serialize parameters + local serialized_param = core.serialize(parameter) + + if serialized_param == nil then + return false + end + + local jobid = core.do_async_callback(serialized_func, serialized_param) + + core.async_jobs[jobid] = callback + + return true +end + diff --git a/builtin/common/filterlist.lua b/builtin/common/filterlist.lua new file mode 100644 index 0000000..2106811 --- /dev/null +++ b/builtin/common/filterlist.lua @@ -0,0 +1,317 @@ +--Minetest +--Copyright (C) 2013 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +-------------------------------------------------------------------------------- +-- TODO improve doc -- +-- TODO code cleanup -- +-- Generic implementation of a filter/sortable list -- +-- Usage: -- +-- Filterlist needs to be initialized on creation. To achieve this you need to -- +-- pass following functions: -- +-- raw_fct() (mandatory): -- +-- function returning a table containing the elements to be filtered -- +-- compare_fct(element1,element2) (mandatory): -- +-- function returning true/false if element1 is same element as element2 -- +-- uid_match_fct(element1,uid) (optional) -- +-- function telling if uid is attached to element1 -- +-- filter_fct(element,filtercriteria) (optional) -- +-- function returning true/false if filtercriteria met to element -- +-- fetch_param (optional) -- +-- parameter passed to raw_fct to aquire correct raw data -- +-- -- +-------------------------------------------------------------------------------- +filterlist = {} + +-------------------------------------------------------------------------------- +function filterlist.refresh(self) + self.m_raw_list = self.m_raw_list_fct(self.m_fetch_param) + filterlist.process(self) +end + +-------------------------------------------------------------------------------- +function filterlist.create(raw_fct,compare_fct,uid_match_fct,filter_fct,fetch_param) + + assert((raw_fct ~= nil) and (type(raw_fct) == "function")) + assert((compare_fct ~= nil) and (type(compare_fct) == "function")) + + local self = {} + + self.m_raw_list_fct = raw_fct + self.m_compare_fct = compare_fct + self.m_filter_fct = filter_fct + self.m_uid_match_fct = uid_match_fct + + self.m_filtercriteria = nil + self.m_fetch_param = fetch_param + + self.m_sortmode = "none" + self.m_sort_list = {} + + self.m_processed_list = nil + self.m_raw_list = self.m_raw_list_fct(self.m_fetch_param) + + self.add_sort_mechanism = filterlist.add_sort_mechanism + self.set_filtercriteria = filterlist.set_filtercriteria + self.get_filtercriteria = filterlist.get_filtercriteria + self.set_sortmode = filterlist.set_sortmode + self.get_list = filterlist.get_list + self.get_raw_list = filterlist.get_raw_list + self.get_raw_element = filterlist.get_raw_element + self.get_raw_index = filterlist.get_raw_index + self.get_current_index = filterlist.get_current_index + self.size = filterlist.size + self.uid_exists_raw = filterlist.uid_exists_raw + self.raw_index_by_uid = filterlist.raw_index_by_uid + self.refresh = filterlist.refresh + + filterlist.process(self) + + return self +end + +-------------------------------------------------------------------------------- +function filterlist.add_sort_mechanism(self,name,fct) + self.m_sort_list[name] = fct +end + +-------------------------------------------------------------------------------- +function filterlist.set_filtercriteria(self,criteria) + if criteria == self.m_filtercriteria and + type(criteria) ~= "table" then + return + end + self.m_filtercriteria = criteria + filterlist.process(self) +end + +-------------------------------------------------------------------------------- +function filterlist.get_filtercriteria(self) + return self.m_filtercriteria +end + +-------------------------------------------------------------------------------- +--supported sort mode "alphabetic|none" +function filterlist.set_sortmode(self,mode) + if (mode == self.m_sortmode) then + return + end + self.m_sortmode = mode + filterlist.process(self) +end + +-------------------------------------------------------------------------------- +function filterlist.get_list(self) + return self.m_processed_list +end + +-------------------------------------------------------------------------------- +function filterlist.get_raw_list(self) + return self.m_raw_list +end + +-------------------------------------------------------------------------------- +function filterlist.get_raw_element(self,idx) + if type(idx) ~= "number" then + idx = tonumber(idx) + end + + if idx ~= nil and idx > 0 and idx <= #self.m_raw_list then + return self.m_raw_list[idx] + end + + return nil +end + +-------------------------------------------------------------------------------- +function filterlist.get_raw_index(self,listindex) + assert(self.m_processed_list ~= nil) + + if listindex ~= nil and listindex > 0 and + listindex <= #self.m_processed_list then + local entry = self.m_processed_list[listindex] + + for i,v in ipairs(self.m_raw_list) do + + if self.m_compare_fct(v,entry) then + return i + end + end + end + + return 0 +end + +-------------------------------------------------------------------------------- +function filterlist.get_current_index(self,listindex) + assert(self.m_processed_list ~= nil) + + if listindex ~= nil and listindex > 0 and + listindex <= #self.m_raw_list then + local entry = self.m_raw_list[listindex] + + for i,v in ipairs(self.m_processed_list) do + + if self.m_compare_fct(v,entry) then + return i + end + end + end + + return 0 +end + +-------------------------------------------------------------------------------- +function filterlist.process(self) + assert(self.m_raw_list ~= nil) + + if self.m_sortmode == "none" and + self.m_filtercriteria == nil then + self.m_processed_list = self.m_raw_list + return + end + + self.m_processed_list = {} + + for k,v in pairs(self.m_raw_list) do + if self.m_filtercriteria == nil or + self.m_filter_fct(v,self.m_filtercriteria) then + table.insert(self.m_processed_list,v) + end + end + + if self.m_sortmode == "none" then + return + end + + if self.m_sort_list[self.m_sortmode] ~= nil and + type(self.m_sort_list[self.m_sortmode]) == "function" then + + self.m_sort_list[self.m_sortmode](self) + end +end + +-------------------------------------------------------------------------------- +function filterlist.size(self) + if self.m_processed_list == nil then + return 0 + end + + return #self.m_processed_list +end + +-------------------------------------------------------------------------------- +function filterlist.uid_exists_raw(self,uid) + for i,v in ipairs(self.m_raw_list) do + if self.m_uid_match_fct(v,uid) then + return true + end + end + return false +end + +-------------------------------------------------------------------------------- +function filterlist.raw_index_by_uid(self, uid) + local elementcount = 0 + local elementidx = 0 + for i,v in ipairs(self.m_raw_list) do + if self.m_uid_match_fct(v,uid) then + elementcount = elementcount +1 + elementidx = i + end + end + + + -- If there are more elements than one with same name uid can't decide which + -- one is meant. self shouldn't be possible but just for sure. + if elementcount > 1 then + elementidx=0 + end + + return elementidx +end + +-------------------------------------------------------------------------------- +-- COMMON helper functions -- +-------------------------------------------------------------------------------- + +-------------------------------------------------------------------------------- +function compare_worlds(world1,world2) + + if world1.path ~= world2.path then + return false + end + + if world1.name ~= world2.name then + return false + end + + if world1.gameid ~= world2.gameid then + return false + end + + return true +end + +-------------------------------------------------------------------------------- +function sort_worlds_alphabetic(self) + + table.sort(self.m_processed_list, function(a, b) + --fixes issue #857 (crash due to sorting nil in worldlist) + if a == nil or b == nil then + if a == nil and b ~= nil then return false end + if b == nil and a ~= nil then return true end + return false + end + if a.name:lower() == b.name:lower() then + return a.name < b.name + end + return a.name:lower() < b.name:lower() + end) +end + +-------------------------------------------------------------------------------- +function sort_mod_list(self) + + table.sort(self.m_processed_list, function(a, b) + -- Show game mods at bottom + if a.typ ~= b.typ then + return b.typ == "game_mod" + end + -- If in same or no modpack, sort by name + if a.modpack == b.modpack then + if a.name:lower() == b.name:lower() then + return a.name < b.name + end + return a.name:lower() < b.name:lower() + -- Else compare name to modpack name + else + -- Always show modpack pseudo-mod on top of modpack mod list + if a.name == b.modpack then + return true + elseif b.name == a.modpack then + return false + end + + local name_a = a.modpack or a.name + local name_b = b.modpack or b.name + if name_a:lower() == name_b:lower() then + return name_a < name_b + end + return name_a:lower() < name_b:lower() + end + end) +end diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua new file mode 100644 index 0000000..39fca7d --- /dev/null +++ b/builtin/common/misc_helpers.lua @@ -0,0 +1,596 @@ +-- Minetest: builtin/misc_helpers.lua + +-------------------------------------------------------------------------------- +function basic_dump(o) + local tp = type(o) + if tp == "number" then + return tostring(o) + elseif tp == "string" then + return string.format("%q", o) + elseif tp == "boolean" then + return tostring(o) + elseif tp == "nil" then + return "nil" + -- Uncomment for full function dumping support. + -- Not currently enabled because bytecode isn't very human-readable and + -- dump's output is intended for humans. + --elseif tp == "function" then + -- return string.format("loadstring(%q)", string.dump(o)) + else + return string.format("<%s>", tp) + end +end + +local keywords = { + ["and"] = true, + ["break"] = true, + ["do"] = true, + ["else"] = true, + ["elseif"] = true, + ["end"] = true, + ["false"] = true, + ["for"] = true, + ["function"] = true, + ["goto"] = true, -- Lua 5.2 + ["if"] = true, + ["in"] = true, + ["local"] = true, + ["nil"] = true, + ["not"] = true, + ["or"] = true, + ["repeat"] = true, + ["return"] = true, + ["then"] = true, + ["true"] = true, + ["until"] = true, + ["while"] = true, +} +local function is_valid_identifier(str) + if not str:find("^[a-zA-Z_][a-zA-Z0-9_]*$") or keywords[str] then + return false + end + return true +end + +-------------------------------------------------------------------------------- +-- Dumps values in a line-per-value format. +-- For example, {test = {"Testing..."}} becomes: +-- _["test"] = {} +-- _["test"][1] = "Testing..." +-- This handles tables as keys and circular references properly. +-- It also handles multiple references well, writing the table only once. +-- The dumped argument is internal-only. + +function dump2(o, name, dumped) + name = name or "_" + -- "dumped" is used to keep track of serialized tables to handle + -- multiple references and circular tables properly. + -- It only contains tables as keys. The value is the name that + -- the table has in the dump, eg: + -- {x = {"y"}} -> dumped[{"y"}] = '_["x"]' + dumped = dumped or {} + if type(o) ~= "table" then + return string.format("%s = %s\n", name, basic_dump(o)) + end + if dumped[o] then + return string.format("%s = %s\n", name, dumped[o]) + end + dumped[o] = name + -- This contains a list of strings to be concatenated later (because + -- Lua is slow at individual concatenation). + local t = {} + for k, v in pairs(o) do + local keyStr + if type(k) == "table" then + if dumped[k] then + keyStr = dumped[k] + else + -- Key tables don't have a name, so use one of + -- the form _G["table: 0xFFFFFFF"] + keyStr = string.format("_G[%q]", tostring(k)) + -- Dump key table + table.insert(t, dump2(k, keyStr, dumped)) + end + else + keyStr = basic_dump(k) + end + local vname = string.format("%s[%s]", name, keyStr) + table.insert(t, dump2(v, vname, dumped)) + end + return string.format("%s = {}\n%s", name, table.concat(t)) +end + +-------------------------------------------------------------------------------- +-- This dumps values in a one-statement format. +-- For example, {test = {"Testing..."}} becomes: +-- [[{ +-- test = { +-- "Testing..." +-- } +-- }]] +-- This supports tables as keys, but not circular references. +-- It performs poorly with multiple references as it writes out the full +-- table each time. +-- The indent field specifies a indentation string, it defaults to a tab. +-- Use the empty string to disable indentation. +-- The dumped and level arguments are internal-only. + +function dump(o, indent, nested, level) + if type(o) ~= "table" then + return basic_dump(o) + end + -- Contains table -> true/nil of currently nested tables + nested = nested or {} + if nested[o] then + return "" + end + nested[o] = true + indent = indent or "\t" + level = level or 1 + local t = {} + local dumped_indexes = {} + for i, v in ipairs(o) do + table.insert(t, dump(v, indent, nested, level + 1)) + dumped_indexes[i] = true + end + for k, v in pairs(o) do + if not dumped_indexes[k] then + if type(k) ~= "string" or not is_valid_identifier(k) then + k = "["..dump(k, indent, nested, level + 1).."]" + end + v = dump(v, indent, nested, level + 1) + table.insert(t, k.." = "..v) + end + end + nested[o] = nil + if indent ~= "" then + local indent_str = "\n"..string.rep(indent, level) + local end_indent_str = "\n"..string.rep(indent, level - 1) + return string.format("{%s%s%s}", + indent_str, + table.concat(t, ","..indent_str), + end_indent_str) + end + return "{"..table.concat(t, ", ").."}" +end + +-------------------------------------------------------------------------------- +-- Localize functions to avoid table lookups (better performance). +local table_insert = table.insert +local str_sub, str_find = string.sub, string.find +function string.split(str, delim, include_empty, max_splits, sep_is_pattern) + delim = delim or "," + max_splits = max_splits or -1 + local items = {} + local pos, len, seplen = 1, #str, #delim + local plain = not sep_is_pattern + max_splits = max_splits + 1 + repeat + local np, npe = str_find(str, delim, pos, plain) + np, npe = (np or (len+1)), (npe or (len+1)) + if (not np) or (max_splits == 1) then + np = len + 1 + npe = np + end + local s = str_sub(str, pos, np - 1) + if include_empty or (s ~= "") then + max_splits = max_splits - 1 + table_insert(items, s) + end + pos = npe + 1 + until (max_splits == 0) or (pos > (len + 1)) + return items +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 math.sign(x, tolerance) + tolerance = tolerance or 0 + if x > tolerance then + return 1 + elseif x < -tolerance then + return -1 + end + return 0 +end + +-------------------------------------------------------------------------------- +function get_last_folder(text,count) + local parts = text:split(DIR_DELIM) + + if count == nil then + return parts[#parts] + end + + local retval = "" + for i=1,count,1 do + retval = retval .. parts[#parts - (count-i)] .. DIR_DELIM + end + + return retval +end + +-------------------------------------------------------------------------------- +function cleanup_path(temppath) + + local parts = temppath:split("-") + temppath = "" + for i=1,#parts,1 do + if temppath ~= "" then + temppath = temppath .. "_" + end + temppath = temppath .. parts[i] + end + + parts = temppath:split(".") + temppath = "" + for i=1,#parts,1 do + if temppath ~= "" then + temppath = temppath .. "_" + end + temppath = temppath .. parts[i] + end + + parts = temppath:split("'") + temppath = "" + for i=1,#parts,1 do + if temppath ~= "" then + temppath = temppath .. "" + end + temppath = temppath .. parts[i] + end + + parts = temppath:split(" ") + temppath = "" + for i=1,#parts,1 do + if temppath ~= "" then + temppath = temppath + end + temppath = temppath .. parts[i] + end + + return temppath +end + +function core.formspec_escape(text) + if text ~= nil then + text = string.gsub(text,"\\","\\\\") + text = string.gsub(text,"%]","\\]") + text = string.gsub(text,"%[","\\[") + text = string.gsub(text,";","\\;") + text = string.gsub(text,",","\\,") + end + return text +end + + +function core.splittext(text,charlimit) + local retval = {} + + local current_idx = 1 + + local start,stop = string.find(text," ",current_idx) + local nl_start,nl_stop = string.find(text,"\n",current_idx) + local gotnewline = false + if nl_start ~= nil and (start == nil or nl_start < start) then + start = nl_start + stop = nl_stop + gotnewline = true + end + local last_line = "" + while start ~= nil do + if string.len(last_line) + (stop-start) > charlimit then + 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 INIT == "game" then + local dirs1 = {9, 18, 7, 12} + local dirs2 = {20, 23, 22, 21} + + function core.rotate_and_place(itemstack, placer, pointed_thing, + infinitestacks, orient_flags) + orient_flags = orient_flags or {} + + local unode = core.get_node_or_nil(pointed_thing.under) + if not unode then + return + end + local undef = core.registered_nodes[unode.name] + if undef and undef.on_rightclick then + undef.on_rightclick(pointed_thing.under, unode, placer, + itemstack, pointed_thing) + return + end + local pitch = placer:get_look_pitch() + local fdir = core.dir_to_facedir(placer:get_look_dir()) + local wield_name = itemstack:get_name() + + local above = pointed_thing.above + local under = pointed_thing.under + local iswall = (above.y == under.y) + local isceiling = not iswall and (above.y < under.y) + local anode = core.get_node_or_nil(above) + if not anode then + return + end + local pos = pointed_thing.above + local node = anode + + if undef and undef.buildable_to then + pos = pointed_thing.under + node = unode + iswall = false + end + + if core.is_protected(pos, placer:get_player_name()) then + core.record_protection_violation(pos, + placer:get_player_name()) + return + end + + local ndef = core.registered_nodes[node.name] + if not ndef or not ndef.buildable_to then + return + end + + if orient_flags.force_floor then + iswall = false + isceiling = false + elseif orient_flags.force_ceiling then + iswall = false + isceiling = true + elseif orient_flags.force_wall then + iswall = true + isceiling = false + elseif orient_flags.invert_wall then + iswall = not iswall + end + + if iswall then + core.set_node(pos, {name = wield_name, + param2 = dirs1[fdir+1]}) + elseif isceiling then + if orient_flags.force_facedir then + core.set_node(pos, {name = wield_name, + param2 = 20}) + else + core.set_node(pos, {name = wield_name, + param2 = dirs2[fdir+1]}) + end + else -- place right side up + if orient_flags.force_facedir then + core.set_node(pos, {name = wield_name, + param2 = 0}) + else + core.set_node(pos, {name = wield_name, + param2 = fdir}) + end + end + + if not infinitestacks then + itemstack:take_item() + return itemstack + end + end + + +-------------------------------------------------------------------------------- +--Wrapper for rotate_and_place() to check for sneak and assume Creative mode +--implies infinite stacks when performing a 6d rotation. +-------------------------------------------------------------------------------- + + + core.rotate_node = function(itemstack, placer, pointed_thing) + core.rotate_and_place(itemstack, placer, pointed_thing, + core.setting_getbool("creative_mode"), + {invert_wall = placer:get_player_control().sneak}) + return itemstack + end +end + +-------------------------------------------------------------------------------- +function core.explode_table_event(evt) + if evt ~= nil then + local parts = evt:split(":") + if #parts == 3 then + local t = parts[1]:trim() + local r = tonumber(parts[2]:trim()) + local c = tonumber(parts[3]:trim()) + if type(r) == "number" and type(c) == "number" + and t ~= "INV" then + return {type=t, row=r, column=c} + end + end + end + return {type="INV", row=0, column=0} +end + +-------------------------------------------------------------------------------- +function core.explode_textlist_event(evt) + if evt ~= nil then + local parts = evt:split(":") + if #parts == 2 then + local t = parts[1]:trim() + local r = tonumber(parts[2]:trim()) + if type(r) == "number" and t ~= "INV" then + return {type=t, index=r} + end + end + end + return {type="INV", index=0} +end + +-------------------------------------------------------------------------------- +function core.explode_scrollbar_event(evt) + local retval = core.explode_textlist_event(evt) + + retval.value = retval.index + retval.index = nil + + return retval +end + +-------------------------------------------------------------------------------- +function core.pos_to_string(pos, decimal_places) + local x = pos.x + local y = pos.y + local z = pos.z + if decimal_places ~= nil then + x = string.format("%." .. decimal_places .. "f", x) + y = string.format("%." .. decimal_places .. "f", y) + z = string.format("%." .. decimal_places .. "f", z) + end + return "(" .. x .. "," .. y .. "," .. z .. ")" +end + +-------------------------------------------------------------------------------- +function core.string_to_pos(value) + if value == nil then + return nil + end + + local p = {} + p.x, p.y, p.z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$") + if p.x and p.y and p.z then + p.x = tonumber(p.x) + p.y = tonumber(p.y) + p.z = tonumber(p.z) + return p + end + local p = {} + p.x, p.y, p.z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$") + if p.x and p.y and p.z then + p.x = tonumber(p.x) + p.y = tonumber(p.y) + p.z = tonumber(p.z) + return p + end + return nil +end + +assert(core.string_to_pos("10.0, 5, -2").x == 10) +assert(core.string_to_pos("( 10.0, 5, -2)").z == -2) +assert(core.string_to_pos("asd, 5, -2)") == nil) + +-------------------------------------------------------------------------------- +function table.copy(t, seen) + local n = {} + seen = seen or {} + seen[t] = n + for k, v in pairs(t) do + n[(type(k) == "table" and (seen[k] or table.copy(k, seen))) or k] = + (type(v) == "table" and (seen[v] or table.copy(v, seen))) or v + end + return n +end +-------------------------------------------------------------------------------- +-- mainmenu only functions +-------------------------------------------------------------------------------- +if INIT == "mainmenu" then + function core.get_game(index) + local games = game.get_games() + + if index > 0 and index <= #games then + return games[index] + end + + return nil + end + + function fgettext_ne(text, ...) + text = core.gettext(text) + local arg = {n=select('#', ...), ...} + if arg.n >= 1 then + -- Insert positional parameters ($1, $2, ...) + local result = '' + local pos = 1 + while pos <= text:len() do + local newpos = text:find('[$]', pos) + if newpos == nil then + result = result .. text:sub(pos) + pos = text:len() + 1 + else + local paramindex = + tonumber(text:sub(newpos+1, newpos+1)) + result = result .. text:sub(pos, newpos-1) + .. tostring(arg[paramindex]) + pos = newpos + 2 + end + end + text = result + end + return text + end + + function fgettext(text, ...) + return core.formspec_escape(fgettext_ne(text, ...)) + end +end + diff --git a/builtin/common/serialize.lua b/builtin/common/serialize.lua new file mode 100644 index 0000000..90b8b2a --- /dev/null +++ b/builtin/common/serialize.lua @@ -0,0 +1,218 @@ +--- Lua module to serialize values as Lua code. +-- From: https://github.com/fab13n/metalua/blob/no-dll/src/lib/serialize.lua +-- License: MIT +-- @copyright 2006-2997 Fabien Fleutot +-- @author Fabien Fleutot +-- @author ShadowNinja +-------------------------------------------------------------------------------- + +--- Serialize an object into a source code string. This string, when passed as +-- an argument to deserialize(), returns an object structurally identical to +-- the original one. The following are currently supported: +-- * Booleans, numbers, strings, and nil. +-- * Functions; uses interpreter-dependent (and sometimes platform-dependent) bytecode! +-- * Tables; they can cantain multiple references and can be recursive, but metatables aren't saved. +-- This works in two phases: +-- 1. Recursively find and record multiple references and recursion. +-- 2. Recursively dump the value into a string. +-- @param x Value to serialize (nil is allowed). +-- @return load()able string containing the value. +function core.serialize(x) + local local_index = 1 -- Top index of the "_" local table in the dump + -- table->nil/1/2 set of tables seen. + -- nil = not seen, 1 = seen once, 2 = seen multiple times. + local seen = {} + + -- nest_points are places where a table appears within itself, directly + -- or not. For instance, all of these chunks create nest points in + -- table x: "x = {}; x[x] = 1", "x = {}; x[1] = x", + -- "x = {}; x[1] = {y = {x}}". + -- To handle those, two tables are used by mark_nest_point: + -- * nested - Transient set of tables being currently traversed. + -- Used for detecting nested tables. + -- * nest_points - parent->{key=value, ...} table cantaining the nested + -- keys and values in the parent. They're all dumped after all the + -- other table operations have been performed. + -- + -- mark_nest_point(p, k, v) fills nest_points with information required + -- to remember that key/value (k, v) creates a nest point in table + -- parent. It also marks "parent" and the nested item(s) as occuring + -- multiple times, since several references to it will be required in + -- order to patch the nest points. + local nest_points = {} + local nested = {} + local function mark_nest_point(parent, k, v) + local nk, nv = nested[k], nested[v] + local np = nest_points[parent] + if not np then + np = {} + nest_points[parent] = np + end + np[k] = v + seen[parent] = 2 + if nk then seen[k] = 2 end + if nv then seen[v] = 2 end + end + + -- First phase, list the tables and functions which appear more than + -- once in x. + local function mark_multiple_occurences(x) + local tp = type(x) + if tp ~= "table" and tp ~= "function" then + -- No identity (comparison is done by value, not by instance) + return + end + if seen[x] == 1 then + seen[x] = 2 + elseif seen[x] ~= 2 then + seen[x] = 1 + end + + if tp == "table" then + nested[x] = true + for k, v in pairs(x) do + if nested[k] or nested[v] then + mark_nest_point(x, k, v) + else + mark_multiple_occurences(k) + mark_multiple_occurences(v) + end + end + nested[x] = nil + end + end + + local dumped = {} -- object->varname set + local local_defs = {} -- Dumped local definitions as source code lines + + -- Mutually recursive local functions: + local dump_val, dump_or_ref_val + + -- If x occurs multiple times, dump the local variable rather than + -- the value. If it's the first time it's dumped, also dump the + -- content in local_defs. + function dump_or_ref_val(x) + if seen[x] ~= 2 then + return dump_val(x) + end + local var = dumped[x] + if var then -- Already referenced + return var + end + -- First occurence, create and register reference + local val = dump_val(x) + local i = local_index + local_index = local_index + 1 + var = "_["..i.."]" + table.insert(local_defs, var.." = "..val) + dumped[x] = var + return var + end + + -- Second phase. Dump the object; subparts occuring multiple times + -- are dumped in local variables which can be referenced multiple + -- times. Care is taken to dump local vars in a sensible order. + function dump_val(x) + local tp = type(x) + if x == nil then return "nil" + elseif tp == "string" then return string.format("%q", x) + elseif tp == "boolean" then return x and "true" or "false" + elseif tp == "function" then + return string.format("loadstring(%q)", string.dump(x)) + elseif tp == "number" then + -- Serialize integers with string.format to prevent + -- scientific notation, which doesn't preserve + -- precision and breaks things like node position + -- hashes. Serialize floats normally. + if math.floor(x) == x then + return string.format("%d", x) + else + return tostring(x) + end + elseif tp == "table" then + local vals = {} + local idx_dumped = {} + local np = nest_points[x] + for i, v in ipairs(x) do + if not np or not np[i] then + table.insert(vals, dump_or_ref_val(v)) + end + idx_dumped[i] = true + end + for k, v in pairs(x) do + if (not np or not np[k]) and + not idx_dumped[k] then + table.insert(vals, + "["..dump_or_ref_val(k).."] = " + ..dump_or_ref_val(v)) + end + end + return "{"..table.concat(vals, ", ").."}" + else + error("Can't serialize data of type "..tp) + end + end + + local function dump_nest_points() + for parent, vals in pairs(nest_points) do + for k, v in pairs(vals) do + table.insert(local_defs, dump_or_ref_val(parent) + .."["..dump_or_ref_val(k).."] = " + ..dump_or_ref_val(v)) + end + end + end + + mark_multiple_occurences(x) + local top_level = dump_or_ref_val(x) + dump_nest_points() + + if next(local_defs) then + return "local _ = {}\n" + ..table.concat(local_defs, "\n") + .."\nreturn "..top_level + else + return "return "..top_level + end +end + +-- Deserialization + +local env = { + loadstring = loadstring, +} + +local safe_env = { + loadstring = function() end, +} + +function core.deserialize(str, safe) + if str:byte(1) == 0x1B then + return nil, "Bytecode prohibited" + end + local f, err = loadstring(str) + if not f then return nil, err end + setfenv(f, safe and safe_env or env) + + local good, data = pcall(f) + if good then + return data + else + return nil, data + end +end + + +-- Unit tests +local test_in = {cat={sound="nyan", speed=400}, dog={sound="woof"}} +local test_out = core.deserialize(core.serialize(test_in)) + +assert(test_in.cat.sound == test_out.cat.sound) +assert(test_in.cat.speed == test_out.cat.speed) +assert(test_in.dog.sound == test_out.dog.sound) + +test_in = {escape_chars="\n\r\t\v\\\"\'", non_european="θשׁ٩∂"} +test_out = core.deserialize(core.serialize(test_in)) +assert(test_in.escape_chars == test_out.escape_chars) +assert(test_in.non_european == test_out.non_european) + diff --git a/builtin/common/strict.lua b/builtin/common/strict.lua new file mode 100644 index 0000000..c353bb9 --- /dev/null +++ b/builtin/common/strict.lua @@ -0,0 +1,54 @@ + +-- Always warn when creating a global variable, even outside of a function. +-- This ignores mod namespaces (variables with the same name as the current mod). +local WARN_INIT = false + + +local function warn(message) + print(os.date("%H:%M:%S: WARNING: ")..message) +end + + +local meta = {} +local declared = {} +-- Key is source file, line, and variable name; seperated by NULs +local warned = {} + +function meta:__newindex(name, value) + local info = debug.getinfo(2, "Sl") + local desc = ("%s:%d"):format(info.short_src, info.currentline) + if not declared[name] then + local warn_key = ("%s\0%d\0%s"):format(info.source, + info.currentline, name) + if not warned[warn_key] and info.what ~= "main" and + info.what ~= "C" then + warn(("Assignment to undeclared ".. + "global %q inside a function at %s.") + :format(name, desc)) + warned[warn_key] = true + end + declared[name] = true + end + -- Ignore mod namespaces + if WARN_INIT and (not core.get_current_modname or + name ~= core.get_current_modname()) then + warn(("Global variable %q created at %s.") + :format(name, desc)) + end + rawset(self, name, value) +end + + +function meta:__index(name) + local info = debug.getinfo(2, "Sl") + local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name) + if not declared[name] and not warned[warn_key] and info.what ~= "C" then + warn(("Undeclared global variable %q accessed at %s:%s") + :format(name, info.short_src, info.currentline)) + warned[warn_key] = true + end + return rawget(self, name) +end + +setmetatable(_G, meta) + diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua new file mode 100644 index 0000000..e9ed3aa --- /dev/null +++ b/builtin/common/vector.lua @@ -0,0 +1,133 @@ + +vector = {} + +function vector.new(a, b, c) + if type(a) == "table" then + assert(a.x and a.y and a.z, "Invalid vector passed to vector.new()") + return {x=a.x, y=a.y, z=a.z} + elseif a then + assert(b and c, "Invalid arguments for vector.new()") + return {x=a, y=b, z=c} + end + return {x=0, y=0, z=0} +end + +function vector.equals(a, b) + return a.x == b.x and + a.y == b.y and + a.z == b.z +end + +function vector.length(v) + return math.hypot(v.x, math.hypot(v.y, v.z)) +end + +function vector.normalize(v) + local len = vector.length(v) + if len == 0 then + return {x=0, y=0, z=0} + else + return vector.divide(v, len) + end +end + +function vector.round(v) + return { + x = math.floor(v.x + 0.5), + y = math.floor(v.y + 0.5), + z = math.floor(v.z + 0.5) + } +end + +function vector.apply(v, func) + return { + x = func(v.x), + y = func(v.y), + z = func(v.z) + } +end + +function vector.distance(a, b) + local x = a.x - b.x + local y = a.y - b.y + local z = a.z - b.z + return math.hypot(x, math.hypot(y, z)) +end + +function vector.direction(pos1, pos2) + local x_raw = pos2.x - pos1.x + local y_raw = pos2.y - pos1.y + local z_raw = pos2.z - pos1.z + local x_abs = math.abs(x_raw) + local y_abs = math.abs(y_raw) + local z_abs = math.abs(z_raw) + if x_abs >= y_abs and + x_abs >= z_abs then + y_raw = y_raw * (1 / x_abs) + z_raw = z_raw * (1 / x_abs) + x_raw = x_raw / x_abs + end + if y_abs >= x_abs and + y_abs >= z_abs then + x_raw = x_raw * (1 / y_abs) + z_raw = z_raw * (1 / y_abs) + y_raw = y_raw / y_abs + end + if z_abs >= y_abs and + z_abs >= x_abs then + x_raw = x_raw * (1 / z_abs) + y_raw = y_raw * (1 / z_abs) + z_raw = z_raw / z_abs + end + return {x=x_raw, y=y_raw, z=z_raw} +end + + +function vector.add(a, b) + if type(b) == "table" then + return {x = a.x + b.x, + y = a.y + b.y, + z = a.z + b.z} + else + return {x = a.x + b, + y = a.y + b, + z = a.z + b} + end +end + +function vector.subtract(a, b) + if type(b) == "table" then + return {x = a.x - b.x, + y = a.y - b.y, + z = a.z - b.z} + else + return {x = a.x - b, + y = a.y - b, + z = a.z - b} + end +end + +function vector.multiply(a, b) + if type(b) == "table" then + return {x = a.x * b.x, + y = a.y * b.y, + z = a.z * b.z} + else + return {x = a.x * b, + y = a.y * b, + z = a.z * b} + end +end + +function vector.divide(a, b) + if type(b) == "table" then + return {x = a.x / b.x, + y = a.y / b.y, + z = a.z / b.z} + else + return {x = a.x / b, + y = a.y / b, + z = a.z / b} + end +end + diff --git a/builtin/fstk/buttonbar.lua b/builtin/fstk/buttonbar.lua new file mode 100644 index 0000000..9a9ec99 --- /dev/null +++ b/builtin/fstk/buttonbar.lua @@ -0,0 +1,210 @@ +--Minetest +--Copyright (C) 2014 sapier +-- +--self 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. +-- +--self 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 self program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +local function buttonbar_formspec(self) + + if self.hidden then + return "" + end + + local formspec = string.format("box[%f,%f;%f,%f;%s]", + self.pos.x,self.pos.y ,self.size.x,self.size.y,self.bgcolor) + + for i=self.startbutton,#self.buttons,1 do + local btn_name = self.buttons[i].name + local btn_pos = {} + + if self.orientation == "horizontal" then + btn_pos.x = self.pos.x + --base pos + (i - self.startbutton) * self.btn_size + --button offset + self.btn_initial_offset + else + btn_pos.x = self.pos.x + (self.btn_size * 0.05) + end + + if self.orientation == "vertical" then + btn_pos.y = self.pos.y + --base pos + (i - self.startbutton) * self.btn_size + --button offset + self.btn_initial_offset + else + btn_pos.y = self.pos.y + (self.btn_size * 0.05) + end + + if (self.orientation == "vertical" and + (btn_pos.y + self.btn_size <= self.pos.y + self.size.y)) or + (self.orientation == "horizontal" and + (btn_pos.x + self.btn_size <= self.pos.x + self.size.x)) then + + local borders="true" + + if self.buttons[i].image ~= nil then + borders="false" + end + + formspec = formspec .. + string.format("image_button[%f,%f;%f,%f;%s;%s;%s;true;%s]tooltip[%s;%s]", + btn_pos.x, btn_pos.y, self.btn_size, self.btn_size, + self.buttons[i].image, btn_name, self.buttons[i].caption, + borders, btn_name, self.buttons[i].tooltip) + else + --print("end of displayable buttons: orientation: " .. self.orientation) + --print( "button_end: " .. (btn_pos.y + self.btn_size - (self.btn_size * 0.05))) + --print( "bar_end: " .. (self.pos.x + self.size.x)) + break + end + end + + if (self.have_move_buttons) then + local btn_dec_pos = {} + btn_dec_pos.x = self.pos.x + (self.btn_size * 0.05) + btn_dec_pos.y = self.pos.y + (self.btn_size * 0.05) + local btn_inc_pos = {} + local btn_size = {} + + if self.orientation == "horizontal" then + btn_size.x = 0.5 + btn_size.y = self.btn_size + btn_inc_pos.x = self.pos.x + self.size.x - 0.5 + btn_inc_pos.y = self.pos.y + (self.btn_size * 0.05) + else + btn_size.x = self.btn_size + btn_size.y = 0.5 + btn_inc_pos.x = self.pos.x + (self.btn_size * 0.05) + btn_inc_pos.y = self.pos.y + self.size.y - 0.5 + end + + local text_dec = "<" + local text_inc = ">" + if self.orientation == "vertical" then + text_dec = "^" + text_inc = "v" + end + + formspec = formspec .. + string.format("image_button[%f,%f;%f,%f;;btnbar_dec_%s;%s;true;true]", + btn_dec_pos.x, btn_dec_pos.y, btn_size.x, btn_size.y, + self.name, text_dec) + + formspec = formspec .. + string.format("image_button[%f,%f;%f,%f;;btnbar_inc_%s;%s;true;true]", + btn_inc_pos.x, btn_inc_pos.y, btn_size.x, btn_size.y, + self.name, text_inc) + end + + return formspec +end + +local function buttonbar_buttonhandler(self, fields) + + if fields["btnbar_inc_" .. self.name] ~= nil and + self.startbutton < #self.buttons then + + self.startbutton = self.startbutton + 1 + return true + end + + if fields["btnbar_dec_" .. self.name] ~= nil and self.startbutton > 1 then + self.startbutton = self.startbutton - 1 + return true + end + + for i=1,#self.buttons,1 do + if fields[self.buttons[i].name] ~= nil then + return self.userbuttonhandler(fields) + end + end +end + +local buttonbar_metatable = { + handle_buttons = buttonbar_buttonhandler, + handle_events = function(self, event) end, + get_formspec = buttonbar_formspec, + + hide = function(self) self.hidden = true end, + show = function(self) self.hidden = false end, + + delete = function(self) ui.delete(self) end, + + add_button = function(self, name, caption, image, tooltip) + if caption == nil then caption = "" end + if image == nil then image = "" end + if tooltip == nil then tooltip = "" end + + table.insert(self.buttons,{ name=name, caption=caption, image=image, tooltip=tooltip}) + if self.orientation == "horizontal" then + if ( (self.btn_size * #self.buttons) + (self.btn_size * 0.05 *2) + > self.size.x ) then + + self.btn_initial_offset = self.btn_size * 0.05 + 0.5 + self.have_move_buttons = true + end + else + if ((self.btn_size * #self.buttons) + (self.btn_size * 0.05 *2) + > self.size.y ) then + + self.btn_initial_offset = self.btn_size * 0.05 + 0.5 + self.have_move_buttons = true + end + end + end, + + set_bgparams = function(self, bgcolor) + if (type(bgcolor) == "string") then + self.bgcolor = bgcolor + end + end, +} + +buttonbar_metatable.__index = buttonbar_metatable + +function buttonbar_create(name, cbf_buttonhandler, pos, orientation, size) + assert(name ~= nil) + assert(cbf_buttonhandler ~= nil) + assert(orientation == "vertical" or orientation == "horizontal") + assert(pos ~= nil and type(pos) == "table") + assert(size ~= nil and type(size) == "table") + + local self = {} + self.name = name + self.type = "addon" + self.bgcolor = "#000000" + self.pos = pos + self.size = size + self.orientation = orientation + self.startbutton = 1 + self.have_move_buttons = false + self.hidden = false + + if self.orientation == "horizontal" then + self.btn_size = self.size.y + else + self.btn_size = self.size.x + end + + if (self.btn_initial_offset == nil) then + self.btn_initial_offset = self.btn_size * 0.05 + end + + self.userbuttonhandler = cbf_buttonhandler + self.buttons = {} + + setmetatable(self,buttonbar_metatable) + + ui.add(self) + return self +end diff --git a/builtin/fstk/dialog.lua b/builtin/fstk/dialog.lua new file mode 100644 index 0000000..214b038 --- /dev/null +++ b/builtin/fstk/dialog.lua @@ -0,0 +1,69 @@ +--Minetest +--Copyright (C) 2014 sapier +-- +--self 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. +-- +--self 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 self program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +local function dialog_event_handler(self,event) + if self.user_eventhandler == nil or + self.user_eventhandler(event) == false then + + --close dialog on esc + if event == "MenuQuit" then + self:delete() + return true + end + end +end + +local dialog_metatable = { + eventhandler = dialog_event_handler, + get_formspec = function(self) + if not self.hidden then return self.formspec(self.data) end + end, + handle_buttons = function(self,fields) + if not self.hidden then return self.buttonhandler(self,fields) end + end, + handle_events = function(self,event) + if not self.hidden then return self.eventhandler(self,event) end + end, + hide = function(self) self.hidden = true end, + show = function(self) self.hidden = false end, + delete = function(self) + if self.parent ~= nil then + self.parent:show() + end + ui.delete(self) + end, + set_parent = function(self,parent) self.parent = parent end +} +dialog_metatable.__index = dialog_metatable + +function dialog_create(name,get_formspec,buttonhandler,eventhandler) + local self = {} + + self.name = name + self.type = "toplevel" + self.hidden = true + self.data = {} + + self.formspec = get_formspec + self.buttonhandler = buttonhandler + self.user_eventhandler = eventhandler + + setmetatable(self,dialog_metatable) + + ui.add(self) + return self +end diff --git a/builtin/fstk/tabview.lua b/builtin/fstk/tabview.lua new file mode 100644 index 0000000..47603fb --- /dev/null +++ b/builtin/fstk/tabview.lua @@ -0,0 +1,273 @@ +--Minetest +--Copyright (C) 2014 sapier +-- +--self 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. +-- +--self 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 self program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +-------------------------------------------------------------------------------- +-- A tabview implementation -- +-- Usage: -- +-- tabview.create: returns initialized tabview raw element -- +-- element.add(tab): add a tab declaration -- +-- element.handle_buttons() -- +-- element.handle_events() -- +-- element.getFormspec() returns formspec of tabview -- +-------------------------------------------------------------------------------- + +-------------------------------------------------------------------------------- +local function add_tab(self,tab) + assert(tab.size == nil or (type(tab.size) == table and + tab.size.x ~= nil and tab.size.y ~= nil)) + assert(tab.cbf_formspec ~= nil and type(tab.cbf_formspec) == "function") + assert(tab.cbf_button_handler == nil or + type(tab.cbf_button_handler) == "function") + assert(tab.cbf_events == nil or type(tab.cbf_events) == "function") + + local newtab = { + name = tab.name, + caption = tab.caption, + button_handler = tab.cbf_button_handler, + event_handler = tab.cbf_events, + get_formspec = tab.cbf_formspec, + tabsize = tab.tabsize, + on_change = tab.on_change, + tabdata = {}, + } + + table.insert(self.tablist,newtab) + + if self.last_tab_index == #self.tablist then + self.current_tab = tab.name + if tab.on_activate ~= nil then + tab.on_activate(nil,tab.name) + end + end +end + +-------------------------------------------------------------------------------- +local function get_formspec(self) + local formspec = "" + + if not self.hidden and (self.parent == nil or not self.parent.hidden) then + + if self.parent == nil then + local tsize = self.tablist[self.last_tab_index].tabsize or + {width=self.width, height=self.height} + formspec = formspec .. + string.format("size[%f,%f,%s]",tsize.width,tsize.height, + dump(self.fixed_size)) + end + formspec = formspec .. self:tab_header() + formspec = formspec .. + self.tablist[self.last_tab_index].get_formspec( + self, + self.tablist[self.last_tab_index].name, + self.tablist[self.last_tab_index].tabdata, + self.tablist[self.last_tab_index].tabsize + ) + end + return formspec +end + +-------------------------------------------------------------------------------- +local function handle_buttons(self,fields) + + if self.hidden then + return false + end + + if self:handle_tab_buttons(fields) then + return true + end + + if self.glb_btn_handler ~= nil and + self.glb_btn_handler(self,fields) then + return true + end + + if self.tablist[self.last_tab_index].button_handler ~= nil then + return + self.tablist[self.last_tab_index].button_handler( + self, + fields, + self.tablist[self.last_tab_index].name, + self.tablist[self.last_tab_index].tabdata + ) + end + + return false +end + +-------------------------------------------------------------------------------- +local function handle_events(self,event) + + if self.hidden then + return false + end + + if self.glb_evt_handler ~= nil and + self.glb_evt_handler(self,event) then + return true + end + + if self.tablist[self.last_tab_index].evt_handler ~= nil then + return + self.tablist[self.last_tab_index].evt_handler( + self, + event, + self.tablist[self.last_tab_index].name, + self.tablist[self.last_tab_index].tabdata + ) + end + + return false +end + + +-------------------------------------------------------------------------------- +local function tab_header(self) + + local toadd = "" + + for i=1,#self.tablist,1 do + + if toadd ~= "" then + toadd = toadd .. "," + end + + toadd = toadd .. self.tablist[i].caption + end + return string.format("tabheader[%f,%f;%s;%s;%i;true;false]", + self.header_x, self.header_y, self.name, toadd, self.last_tab_index); +end + +-------------------------------------------------------------------------------- +local function switch_to_tab(self, index) + --first call on_change for tab to leave + if self.tablist[self.last_tab_index].on_change ~= nil then + self.tablist[self.last_tab_index].on_change("LEAVE", + self.current_tab, self.tablist[index].name) + end + + --update tabview data + self.last_tab_index = index + local old_tab = self.current_tab + self.current_tab = self.tablist[index].name + + if (self.autosave_tab) then + core.setting_set(self.name .. "_LAST",self.current_tab) + end + + -- call for tab to enter + if self.tablist[index].on_change ~= nil then + self.tablist[index].on_change("ENTER", + old_tab,self.current_tab) + end +end + +-------------------------------------------------------------------------------- +local function handle_tab_buttons(self,fields) + --save tab selection to config file + if fields[self.name] then + local index = tonumber(fields[self.name]) + switch_to_tab(self, index) + return true + end + + return false +end + +-------------------------------------------------------------------------------- +local function set_tab_by_name(self, name) + for i=1,#self.tablist,1 do + if self.tablist[i].name == name then + switch_to_tab(self, i) + return true + end + end + + return false +end + +-------------------------------------------------------------------------------- +local function hide_tabview(self) + self.hidden=true + + --call on_change as we're not gonna show self tab any longer + if self.tablist[self.last_tab_index].on_change ~= nil then + self.tablist[self.last_tab_index].on_change("LEAVE", + self.current_tab, nil) + end +end + +-------------------------------------------------------------------------------- +local function show_tabview(self) + self.hidden=false + + -- call for tab to enter + if self.tablist[self.last_tab_index].on_change ~= nil then + self.tablist[self.last_tab_index].on_change("ENTER", + nil,self.current_tab) + end +end + +local tabview_metatable = { + add = add_tab, + handle_buttons = handle_buttons, + handle_events = handle_events, + get_formspec = get_formspec, + show = show_tabview, + hide = hide_tabview, + delete = function(self) ui.delete(self) end, + set_parent = function(self,parent) self.parent = parent end, + set_autosave_tab = + function(self,value) self.autosave_tab = value end, + set_tab = set_tab_by_name, + set_global_button_handler = + function(self,handler) self.glb_btn_handler = handler end, + set_global_event_handler = + function(self,handler) self.glb_evt_handler = handler end, + set_fixed_size = + function(self,state) self.fixed_size = state end, + tab_header = tab_header, + handle_tab_buttons = handle_tab_buttons +} + +tabview_metatable.__index = tabview_metatable + +-------------------------------------------------------------------------------- +function tabview_create(name, size, tabheaderpos) + local self = {} + + self.name = name + self.type = "toplevel" + self.width = size.x + self.height = size.y + self.header_x = tabheaderpos.x + self.header_y = tabheaderpos.y + + setmetatable(self, tabview_metatable) + + self.fixed_size = true + self.hidden = true + self.current_tab = nil + self.last_tab_index = 1 + self.tablist = {} + + self.autosave_tab = false + + ui.add(self) + return self +end diff --git a/builtin/fstk/ui.lua b/builtin/fstk/ui.lua new file mode 100644 index 0000000..708ea19 --- /dev/null +++ b/builtin/fstk/ui.lua @@ -0,0 +1,171 @@ +--Minetest +--Copyright (C) 2014 sapier +-- +--self 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. +-- +--self 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 self program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +ui = {} +ui.childlist = {} +ui.default = nil + +-------------------------------------------------------------------------------- +function ui.add(child) + --TODO check child + ui.childlist[child.name] = child + + return child.name +end + +-------------------------------------------------------------------------------- +function ui.delete(child) + + if ui.childlist[child.name] == nil then + return false + end + + ui.childlist[child.name] = nil + return true +end + +-------------------------------------------------------------------------------- +function ui.set_default(name) + ui.default = name +end + +-------------------------------------------------------------------------------- +function ui.find_by_name(name) + return ui.childlist[name] +end + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +-- Internal functions not to be called from user +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- + +-------------------------------------------------------------------------------- +function ui.update() + local formspec = "" + + -- handle errors + if gamedata ~= nil and gamedata.errormessage ~= nil then + formspec = "size[12,3.2]" .. + "textarea[1,1;10,2;;ERROR: " .. + core.formspec_escape(gamedata.errormessage) .. + ";]".. + "button[4.5,2.5;3,0.5;btn_error_confirm;" .. fgettext("Ok") .. "]" + else + local active_toplevel_ui_elements = 0 + for key,value in pairs(ui.childlist) do + if (value.type == "toplevel") then + local retval = value:get_formspec() + + if retval ~= nil and retval ~= "" then + active_toplevel_ui_elements = active_toplevel_ui_elements +1 + formspec = formspec .. retval + end + end + end + + -- no need to show addons if there ain't a toplevel element + if (active_toplevel_ui_elements > 0) then + for key,value in pairs(ui.childlist) do + if (value.type == "addon") then + local retval = value:get_formspec() + + if retval ~= nil and retval ~= "" then + formspec = formspec .. retval + end + end + end + end + + if (active_toplevel_ui_elements > 1) then + print("WARNING: ui manager detected more then one active ui element, self most likely isn't intended") + end + + if (active_toplevel_ui_elements == 0) then + print("WARNING: not a single toplevel ui element active switching to default") + ui.childlist[ui.default]:show() + formspec = ui.childlist[ui.default]:get_formspec() + end + end + core.update_formspec(formspec) +end + +-------------------------------------------------------------------------------- +function ui.handle_buttons(fields) + + if fields["btn_error_confirm"] then + gamedata.errormessage = nil + update_menu() + return + end + + for key,value in pairs(ui.childlist) do + + local retval = value:handle_buttons(fields) + + if retval then + ui.update() + return + end + end +end + + +-------------------------------------------------------------------------------- +function ui.handle_events(event) + + for key,value in pairs(ui.childlist) do + + if value.handle_events ~= nil then + local retval = value:handle_events(event) + + if retval then + return retval + end + end + end +end + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +-- initialize callbacks +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +core.button_handler = function(fields) + if fields["btn_error_confirm"] then + gamedata.errormessage = nil + ui.update() + return + end + + if ui.handle_buttons(fields) then + ui.update() + end +end + +-------------------------------------------------------------------------------- +core.event_handler = function(event) + if ui.handle_events(event) then + ui.update() + return + end + + if event == "Refresh" then + ui.update() + return + end +end diff --git a/builtin/game/auth.lua b/builtin/game/auth.lua new file mode 100644 index 0000000..93b0099 --- /dev/null +++ b/builtin/game/auth.lua @@ -0,0 +1,200 @@ +-- Minetest: builtin/auth.lua + +-- +-- Authentication handler +-- + +function core.string_to_privs(str, delim) + assert(type(str) == "string") + delim = delim or ',' + local privs = {} + for _, priv in pairs(string.split(str, delim)) do + privs[priv:trim()] = true + end + return privs +end + +function core.privs_to_string(privs, delim) + assert(type(privs) == "table") + delim = delim or ',' + local list = {} + for priv, bool in pairs(privs) do + if bool then + table.insert(list, priv) + end + end + return table.concat(list, delim) +end + +assert(core.string_to_privs("a,b").b == true) +assert(core.privs_to_string({a=true,b=true}) == "a,b") + +core.auth_file_path = core.get_worldpath().."/auth.txt" +core.auth_table = {} + +local function read_auth_file() + local newtable = {} + local file, errmsg = io.open(core.auth_file_path, 'rb') + if not file then + core.log("info", core.auth_file_path.." could not be opened for reading ("..errmsg.."); assuming new world") + return + end + for line in file:lines() do + if line ~= "" then + local fields = line:split(":", true) + local name, password, privilege_string, last_login = unpack(fields) + last_login = tonumber(last_login) + if not (name and password and privilege_string) then + error("Invalid line in auth.txt: "..dump(line)) + end + local privileges = core.string_to_privs(privilege_string) + newtable[name] = {password=password, privileges=privileges, last_login=last_login} + end + end + io.close(file) + core.auth_table = newtable + core.notify_authentication_modified() +end + +local function save_auth_file() + local newtable = {} + -- Check table for validness before attempting to save + for name, stuff in pairs(core.auth_table) do + assert(type(name) == "string") + assert(name ~= "") + assert(type(stuff) == "table") + assert(type(stuff.password) == "string") + assert(type(stuff.privileges) == "table") + assert(stuff.last_login == nil or type(stuff.last_login) == "number") + end + local file, errmsg = io.open(core.auth_file_path, 'w+b') + if not file then + error(core.auth_file_path.." could not be opened for writing: "..errmsg) + end + for name, stuff in pairs(core.auth_table) do + local priv_string = core.privs_to_string(stuff.privileges) + local parts = {name, stuff.password, priv_string, stuff.last_login or ""} + file:write(table.concat(parts, ":").."\n") + end + io.close(file) +end + +read_auth_file() + +core.builtin_auth_handler = { + get_auth = function(name) + assert(type(name) == "string") + -- Figure out what password to use for a new player (singleplayer + -- always has an empty password, otherwise use default, which is + -- usually empty too) + local new_password_hash = "" + -- If not in authentication table, return nil + if not core.auth_table[name] then + return nil + end + -- Figure out what privileges the player should have. + -- Take a copy of the privilege table + local privileges = {} + for priv, _ in pairs(core.auth_table[name].privileges) do + privileges[priv] = true + end + -- If singleplayer, give all privileges except those marked as give_to_singleplayer = false + if core.is_singleplayer() then + for priv, def in pairs(core.registered_privileges) do + if def.give_to_singleplayer then + privileges[priv] = true + end + end + -- For the admin, give everything + elseif name == core.setting_get("name") then + for priv, def in pairs(core.registered_privileges) do + privileges[priv] = true + end + end + -- All done + return { + password = core.auth_table[name].password, + privileges = privileges, + -- Is set to nil if unknown + last_login = core.auth_table[name].last_login, + } + end, + create_auth = function(name, password) + assert(type(name) == "string") + assert(type(password) == "string") + core.log('info', "Built-in authentication handler adding player '"..name.."'") + core.auth_table[name] = { + password = password, + privileges = core.string_to_privs(core.setting_get("default_privs")), + last_login = os.time(), + } + save_auth_file() + end, + set_password = function(name, password) + assert(type(name) == "string") + assert(type(password) == "string") + if not core.auth_table[name] then + core.builtin_auth_handler.create_auth(name, password) + else + core.log('info', "Built-in authentication handler setting password of player '"..name.."'") + core.auth_table[name].password = password + save_auth_file() + end + return true + end, + set_privileges = function(name, privileges) + assert(type(name) == "string") + assert(type(privileges) == "table") + if not core.auth_table[name] then + core.builtin_auth_handler.create_auth(name, + core.get_password_hash(name, + core.setting_get("default_password"))) + end + core.auth_table[name].privileges = privileges + core.notify_authentication_modified(name) + save_auth_file() + end, + reload = function() + read_auth_file() + return true + end, + record_login = function(name) + assert(type(name) == "string") + assert(core.auth_table[name]).last_login = os.time() + save_auth_file() + end, +} + +function core.register_authentication_handler(handler) + if core.registered_auth_handler then + error("Add-on authentication handler already registered by "..core.registered_auth_handler_modname) + end + core.registered_auth_handler = handler + core.registered_auth_handler_modname = core.get_current_modname() +end + +function core.get_auth_handler() + return core.registered_auth_handler or core.builtin_auth_handler +end + +local function auth_pass(name) + return function(...) + local auth_handler = core.get_auth_handler() + if auth_handler[name] then + return auth_handler[name](...) + end + return false + end +end + +core.set_player_password = auth_pass("set_password") +core.set_player_privs = auth_pass("set_privileges") +core.auth_reload = auth_pass("reload") + + +local record_login = auth_pass("record_login") + +core.register_on_joinplayer(function(player) + record_login(player:get_player_name()) +end) + diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua new file mode 100644 index 0000000..2101178 --- /dev/null +++ b/builtin/game/chatcommands.lua @@ -0,0 +1,808 @@ +-- Minetest: builtin/chatcommands.lua + +-- +-- Chat command handler +-- + +core.chatcommands = {} +function core.register_chatcommand(cmd, def) + def = def or {} + def.params = def.params or "" + def.description = def.description or "" + def.privs = def.privs or {} + core.chatcommands[cmd] = def +end + +if core.setting_getbool("mod_profiling") then + local tracefct = profiling_print_log + profiling_print_log = nil + core.register_chatcommand("save_mod_profile", + { + params = "", + description = "save mod profiling data to logfile " .. + "(depends on default loglevel)", + func = tracefct, + privs = { server=true } + }) +end + +core.register_on_chat_message(function(name, message) + local cmd, param = string.match(message, "^/([^ ]+) *(.*)") + if not param then + param = "" + end + local cmd_def = core.chatcommands[cmd] + if not cmd_def then + return false + end + local has_privs, missing_privs = core.check_player_privs(name, cmd_def.privs) + if has_privs then + local success, message = cmd_def.func(name, param) + if message then + core.chat_send_player(name, message) + end + else + core.chat_send_player(name, "You don't have permission" + .. " to run this command (missing privileges: " + .. table.concat(missing_privs, ", ") .. ")") + end + return true -- Handled chat message +end) + +-- +-- Chat commands +-- +core.register_chatcommand("me", { + params = "", + description = "chat action (eg. /me orders a pizza)", + privs = {shout=true}, + func = function(name, param) + core.chat_send_all("* " .. name .. " " .. param) + end, +}) + +core.register_chatcommand("help", { + privs = {}, + params = "[all/privs/]", + description = "Get help for commands or list privileges", + func = function(name, param) + local function format_help_line(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 = "" + local cmds = {} + for cmd, def in pairs(core.chatcommands) do + if core.check_player_privs(name, def.privs) then + table.insert(cmds, cmd) + end + end + table.sort(cmds) + return true, "Available commands: " .. table.concat(cmds, " ") .. "\n" + .. "Use '/help ' to get more information," + .. " or '/help all' to list everything." + elseif param == "all" then + local cmds = {} + for cmd, def in pairs(core.chatcommands) do + if core.check_player_privs(name, def.privs) then + table.insert(cmds, format_help_line(cmd, def)) + end + end + table.sort(cmds) + return true, "Available commands:\n"..table.concat(cmds, "\n") + elseif param == "privs" then + local privs = {} + for priv, def in pairs(core.registered_privileges) do + table.insert(privs, priv .. ": " .. def.description) + end + table.sort(privs) + return true, "Available privileges:\n"..table.concat(privs, "\n") + else + local cmd = param + local def = core.chatcommands[cmd] + if not def then + return false, "Command not available: "..cmd + else + return true, format_help_line(cmd, def) + end + end + end, +}) + +core.register_chatcommand("privs", { + params = "", + description = "print out privileges of player", + func = function(name, param) + param = (param ~= "" and param or name) + return true, "Privileges of " .. param .. ": " + .. core.privs_to_string( + core.get_player_privs(param), ' ') + end, +}) +core.register_chatcommand("grant", { + params = " |all", + description = "Give privilege to player", + func = function(name, param) + if not core.check_player_privs(name, {privs=true}) and + not core.check_player_privs(name, {basic_privs=true}) then + return false, "Your privileges are insufficient." + end + local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)") + if not grantname or not grantprivstr then + return false, "Invalid parameters (see /help grant)" + elseif not core.auth_table[grantname] then + return false, "Player " .. grantname .. " does not exist." + end + local grantprivs = core.string_to_privs(grantprivstr) + if grantprivstr == "all" then + grantprivs = core.registered_privileges + end + local privs = core.get_player_privs(grantname) + local privs_unknown = "" + for priv, _ in pairs(grantprivs) do + if priv ~= "interact" and priv ~= "shout" and + not core.check_player_privs(name, {privs=true}) then + return false, "Your privileges are insufficient." + end + if not core.registered_privileges[priv] then + privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n" + end + privs[priv] = true + end + if privs_unknown ~= "" then + return false, privs_unknown + end + core.set_player_privs(grantname, privs) + core.log("action", name..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname) + if grantname ~= name then + core.chat_send_player(grantname, name + .. " granted you privileges: " + .. core.privs_to_string(grantprivs, ' ')) + end + return true, "Privileges of " .. grantname .. ": " + .. core.privs_to_string( + core.get_player_privs(grantname), ' ') + end, +}) +core.register_chatcommand("revoke", { + params = " |all", + description = "Remove privilege from player", + privs = {}, + func = function(name, param) + if not core.check_player_privs(name, {privs=true}) and + not core.check_player_privs(name, {basic_privs=true}) then + return false, "Your privileges are insufficient." + end + local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)") + if not revoke_name or not revoke_priv_str then + return false, "Invalid parameters (see /help revoke)" + elseif not core.auth_table[revoke_name] then + return false, "Player " .. revoke_name .. " does not exist." + end + local revoke_privs = core.string_to_privs(revoke_priv_str) + local privs = core.get_player_privs(revoke_name) + for priv, _ in pairs(revoke_privs) do + if priv ~= "interact" and priv ~= "shout" and priv ~= "interact_extra" and + not core.check_player_privs(name, {privs=true}) then + return false, "Your privileges are insufficient." + end + end + if revoke_priv_str == "all" then + privs = {} + else + for priv, _ in pairs(revoke_privs) do + privs[priv] = nil + end + end + core.set_player_privs(revoke_name, privs) + core.log("action", name..' revoked (' + ..core.privs_to_string(revoke_privs, ', ') + ..') privileges from '..revoke_name) + if revoke_name ~= name then + core.chat_send_player(revoke_name, name + .. " revoked privileges from you: " + .. core.privs_to_string(revoke_privs, ' ')) + end + return true, "Privileges of " .. revoke_name .. ": " + .. core.privs_to_string( + core.get_player_privs(revoke_name), ' ') + end, +}) + +core.register_chatcommand("setpassword", { + params = " ", + description = "set given password", + privs = {password=true}, + func = function(name, param) + local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$") + if not toname then + toname = param:match("^([^ ]+) *$") + raw_password = nil + end + if not toname then + return false, "Name field required" + end + local act_str_past = "?" + local act_str_pres = "?" + if not raw_password then + core.set_player_password(toname, "") + act_str_past = "cleared" + act_str_pres = "clears" + else + core.set_player_password(toname, + core.get_password_hash(toname, + raw_password)) + act_str_past = "set" + act_str_pres = "sets" + end + if toname ~= name then + core.chat_send_player(toname, "Your password was " + .. act_str_past .. " by " .. name) + end + + core.log("action", name .. " " .. act_str_pres + .. " password of " .. toname .. ".") + + return true, "Password of player \"" .. toname .. "\" " .. act_str_past + end, +}) + +core.register_chatcommand("clearpassword", { + params = "", + description = "set empty password", + privs = {password=true}, + func = function(name, param) + local toname = param + if toname == "" then + return false, "Name field required" + end + core.set_player_password(toname, '') + + core.log("action", name .. " clears password of " .. toname .. ".") + + return true, "Password of player \"" .. toname .. "\" cleared" + end, +}) + +core.register_chatcommand("auth_reload", { + params = "", + description = "reload authentication data", + privs = {server=true}, + func = function(name, param) + local done = core.auth_reload() + return done, (done and "Done." or "Failed.") + end, +}) + +core.register_chatcommand("teleport", { + params = ",, | | ,, | ", + 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 = core.get_node_or_nil(p) + if n and n.name then + local def = core.registered_nodes[n.name] + if def and not def.walkable then + return p, true + end + end + end + return pos, false + end + + local teleportee = nil + local p = {} + p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$") + p.x = tonumber(p.x) + p.y = tonumber(p.y) + p.z = tonumber(p.z) + teleportee = core.get_player_by_name(name) + if teleportee and p.x and p.y and p.z then + teleportee:setpos(p) + return true, "Teleporting to "..core.pos_to_string(p) + end + + local teleportee = nil + local p = nil + local target_name = nil + target_name = param:match("^([^ ]+)$") + teleportee = core.get_player_by_name(name) + if target_name then + local target = core.get_player_by_name(target_name) + if target then + p = target:getpos() + end + end + if teleportee and p then + p = find_free_position_near(p) + teleportee:setpos(p) + return true, "Teleporting to " .. target_name + .. " at "..core.pos_to_string(p) + end + + if not core.check_player_privs(name, {bring=true}) then + return false, "You don't have permission to teleport other players (missing bring privilege)" + end + + local teleportee = nil + local p = {} + local teleportee_name = nil + teleportee_name, p.x, p.y, p.z = param:match( + "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$") + p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z) + if teleportee_name then + teleportee = core.get_player_by_name(teleportee_name) + end + if teleportee and p.x and p.y and p.z then + teleportee:setpos(p) + return true, "Teleporting " .. teleportee_name + .. " to " .. core.pos_to_string(p) + end + + local teleportee = nil + local p = nil + local teleportee_name = nil + local target_name = nil + teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$") + if teleportee_name then + teleportee = core.get_player_by_name(teleportee_name) + end + if target_name then + local target = core.get_player_by_name(target_name) + if target then + p = target:getpos() + end + end + if teleportee and p then + p = find_free_position_near(p) + teleportee:setpos(p) + return true, "Teleporting " .. teleportee_name + .. " to " .. target_name + .. " at " .. core.pos_to_string(p) + end + + return false, 'Invalid parameters ("' .. param + .. '") or player not found (see /help teleport)' + end, +}) + +core.register_chatcommand("set", { + params = "[-n] | ", + description = "set or read server configuration setting", + privs = {server=true}, + func = function(name, param) + local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)") + if arg and arg == "-n" and setname and setvalue then + core.setting_set(setname, setvalue) + return true, setname .. " = " .. setvalue + end + local setname, setvalue = string.match(param, "([^ ]+) (.+)") + if setname and setvalue then + if not core.setting_get(setname) then + return false, "Failed. Use '/set -n ' to create a new setting." + end + core.setting_set(setname, setvalue) + return true, setname .. " = " .. setvalue + end + local setname = string.match(param, "([^ ]+)") + if setname then + local setvalue = core.setting_get(setname) + if not setvalue then + setvalue = "" + end + return true, setname .. " = " .. setvalue + end + return false, "Invalid parameters (see /help set)." + end, +}) + +core.register_chatcommand("deleteblocks", { + params = "(here [radius]) | ( )", + description = "delete map blocks contained in area pos1 to pos2", + privs = {server=true}, + func = function(name, param) + local p1 = {} + local p2 = {} + local args = param:split(" ") + if args[1] == "here" then + local player = core.get_player_by_name(name) + if player == nil then + core.log("error", "player is nil") + return false, "Unable to get current position; player is nil" + end + p1 = player:getpos() + p2 = p1 + + if #args >= 2 then + local radius = tonumber(args[2]) or 0 + p1 = vector.add(p1, radius) + p2 = vector.subtract(p2, radius) + end + else + local pos1, pos2 = unpack(param:split(") (")) + if pos1 == nil or pos2 == nil then + return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)" + end + + p1 = core.string_to_pos(pos1 .. ")") + p2 = core.string_to_pos("(" .. pos2) + + if p1 == nil or p2 == nil then + return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)" + end + end + + if core.delete_area(p1, p2) then + return true, "Successfully cleared area ranging from " .. + core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1) + else + return false, "Failed to clear one or more blocks in area" + end + end, +}) + +core.register_chatcommand("mods", { + params = "", + description = "List mods installed on the server", + privs = {}, + func = function(name, param) + return true, table.concat(core.get_modnames(), ", ") + end, +}) + +local function handle_give_command(cmd, giver, receiver, stackstring) + core.log("action", giver .. " invoked " .. cmd + .. ', stackstring="' .. stackstring .. '"') + local itemstack = ItemStack(stackstring) + if itemstack:is_empty() then + return false, "Cannot give an empty item" + elseif not itemstack:is_known() then + return false, "Cannot give an unknown item" + end + local receiverref = core.get_player_by_name(receiver) + if receiverref == nil then + return false, receiver .. " is not a known player" + end + local leftover = receiverref:get_inventory():add_item("main", itemstack) + local partiality + if leftover:is_empty() then + partiality = "" + elseif leftover:get_count() == itemstack:get_count() then + partiality = "could not be " + else + partiality = "partially " + end + -- The actual item stack string may be different from what the "giver" + -- entered (e.g. big numbers are always interpreted as 2^16-1). + stackstring = itemstack:to_string() + if giver == receiver then + return true, ("%q %sadded to inventory.") + :format(stackstring, partiality) + else + core.chat_send_player(receiver, ("%q %sadded to inventory.") + :format(stackstring, partiality)) + return true, ("%q %sadded to %s's inventory.") + :format(stackstring, partiality, receiver) + end +end + +core.register_chatcommand("give", { + params = " ", + description = "give item to player", + privs = {give=true}, + func = function(name, param) + local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$") + if not toname or not itemstring then + return false, "Name and ItemString required" + end + return handle_give_command("/give", name, toname, itemstring) + end, +}) + +core.register_chatcommand("giveme", { + params = "", + description = "give item to yourself", + privs = {give=true}, + func = function(name, param) + local itemstring = string.match(param, "(.+)$") + if not itemstring then + return false, "ItemString required" + end + return handle_give_command("/giveme", name, name, itemstring) + end, +}) + +core.register_chatcommand("spawnentity", { + params = "", + description = "Spawn entity at your position", + privs = {give=true, interact=true}, + func = function(name, param) + local entityname = string.match(param, "(.+)$") + if not entityname then + return false, "EntityName required" + end + core.log("action", ("/spawnentity invoked, entityname=%q") + :format(entityname)) + local player = core.get_player_by_name(name) + if player == nil then + core.log("error", "Unable to spawn entity, player is nil") + return false, "Unable to spawn entity, player is nil" + end + local p = player:getpos() + p.y = p.y + 1 + core.add_entity(p, entityname) + return true, ("%q spawned."):format(entityname) + end, +}) + +core.register_chatcommand("pulverize", { + params = "", + description = "Destroy item in hand", + func = function(name, param) + local player = core.get_player_by_name(name) + if not player then + core.log("error", "Unable to pulverize, no player.") + return false, "Unable to pulverize, no player." + end + if player:get_wielded_item():is_empty() then + return false, "Unable to pulverize, no item in hand." + end + player:set_wielded_item(nil) + return true, "An item was pulverized." + end, +}) + +-- Key = player name +core.rollback_punch_callbacks = {} + +core.register_on_punchnode(function(pos, node, puncher) + local name = puncher:get_player_name() + if core.rollback_punch_callbacks[name] then + core.rollback_punch_callbacks[name](pos, node, puncher) + core.rollback_punch_callbacks[name] = nil + end +end) + +core.register_chatcommand("rollback_check", { + params = "[] [] [limit]", + description = "Check who has last touched a node or near it," + .. " max. ago (default range=0," + .. " seconds=86400=24h, limit=5)", + privs = {rollback=true}, + func = function(name, param) + if not core.setting_getbool("enable_rollback_recording") then + return false, "Rollback functions are disabled." + end + local range, seconds, limit = + param:match("(%d+) *(%d*) *(%d*)") + range = tonumber(range) or 0 + seconds = tonumber(seconds) or 86400 + limit = tonumber(limit) or 5 + if limit > 100 then + return false, "That limit is too high!" + end + + core.rollback_punch_callbacks[name] = function(pos, node, puncher) + local name = puncher:get_player_name() + core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...") + local actions = core.rollback_get_node_actions(pos, range, seconds, limit) + if not actions then + core.chat_send_player(name, "Rollback functions are disabled") + return + end + local num_actions = #actions + if num_actions == 0 then + core.chat_send_player(name, "Nobody has touched" + .. " the specified location in " + .. seconds .. " seconds") + return + end + local time = os.time() + for i = num_actions, 1, -1 do + local action = actions[i] + core.chat_send_player(name, + ("%s %s %s -> %s %d seconds ago.") + :format( + core.pos_to_string(action.pos), + action.actor, + action.oldnode.name, + action.newnode.name, + time - action.time)) + end + end + + return true, "Punch a node (range=" .. range .. ", seconds=" + .. seconds .. "s, limit=" .. limit .. ")" + end, +}) + +core.register_chatcommand("rollback", { + params = " [] | : []", + description = "revert actions of a player; default for is 60", + privs = {rollback=true}, + func = function(name, param) + if not core.setting_getbool("enable_rollback_recording") then + return false, "Rollback functions are disabled." + end + local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)") + if not target_name then + local player_name = nil + player_name, seconds = string.match(param, "([^ ]+) *(%d*)") + if not player_name then + return false, "Invalid parameters. See /help rollback" + .. " and /help rollback_check." + end + target_name = "player:"..player_name + end + seconds = tonumber(seconds) or 60 + core.chat_send_player(name, "Reverting actions of " + .. target_name .. " since " + .. seconds .. " seconds.") + local success, log = core.rollback_revert_actions_by( + target_name, seconds) + local response = "" + if #log > 100 then + response = "(log is too long to show)\n" + else + for _, line in pairs(log) do + response = response .. line .. "\n" + end + end + response = response .. "Reverting actions " + .. (success and "succeeded." or "FAILED.") + return success, response + end, +}) + +core.register_chatcommand("status", { + description = "Print server status", + func = function(name, param) + return true, core.get_server_status() + end, +}) + +core.register_chatcommand("time", { + params = "<0...24000>", + description = "set time of day", + privs = {settime=true}, + func = function(name, param) + if param == "" then + return false, "Missing time." + end + local newtime = tonumber(param) + if newtime == nil then + return false, "Invalid time." + end + core.set_timeofday((newtime % 24000) / 24000) + core.log("action", name .. " sets time " .. newtime) + return true, "Time of day changed." + end, +}) + +core.register_chatcommand("shutdown", { + description = "shutdown server", + privs = {server=true}, + func = function(name, param) + core.log("action", name .. " shuts down server") + core.request_shutdown() + core.chat_send_all("*** Server shutting down (operator request).") + end, +}) + +core.register_chatcommand("ban", { + params = "", + description = "Ban IP of player", + privs = {ban=true}, + func = function(name, param) + if param == "" then + return true, "Ban list: " .. core.get_ban_list() + end + if not core.get_player_by_name(param) then + return false, "No such player." + end + if not core.ban_player(param) then + return false, "Failed to ban player." + end + local desc = core.get_ban_description(param) + core.log("action", name .. " bans " .. desc .. ".") + return true, "Banned " .. desc .. "." + end, +}) + +core.register_chatcommand("unban", { + params = "", + description = "remove IP ban", + privs = {ban=true}, + func = function(name, param) + if not core.unban_player_or_ip(param) then + return false, "Failed to unban player/IP." + end + core.log("action", name .. " unbans " .. param) + return true, "Unbanned " .. param + end, +}) + +core.register_chatcommand("kick", { + params = " [reason]", + description = "kick a player", + privs = {kick=true}, + func = function(name, param) + local tokick, reason = param:match("([^ ]+) (.+)") + tokick = tokick or param + if not core.kick_player(tokick, reason) then + return false, "Failed to kick player " .. tokick + end + core.log("action", name .. " kicked " .. tokick) + return true, "Kicked " .. tokick + end, +}) + +core.register_chatcommand("clearobjects", { + description = "clear all objects in world", + privs = {server=true}, + func = function(name, param) + core.log("action", name .. " clears all objects.") + core.chat_send_all("Clearing all objects. This may take long." + .. " You may experience a timeout. (by " + .. name .. ")") + core.clear_objects() + core.log("action", "Object clearing done.") + core.chat_send_all("*** Cleared all objects.") + end, +}) + +core.register_chatcommand("msg", { + params = " ", + description = "Send a private message", + privs = {shout=true}, + func = function(name, param) + local sendto, message = param:match("^(%S+)%s(.+)$") + if not sendto then + return false, "Invalid usage, see /help msg." + end + if not core.get_player_by_name(sendto) then + return false, "The player " .. sendto + .. " is not online." + end + core.log("action", "PM from " .. name .. " to " .. sendto + .. ": " .. message) + core.chat_send_player(sendto, "PM from " .. name .. ": " + .. message) + return true, "Message sent." + end, +}) + +core.register_chatcommand("last-login", { + params = "[name]", + description = "Get the last login time of a player", + func = function(name, param) + if param == "" then + param = name + end + local pauth = core.get_auth_handler().get_auth(param) + if pauth and pauth.last_login then + -- Time in UTC, ISO 8601 format + return true, "Last login time was " .. + os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login) + end + return false, "Last login time is unknown" + end, +}) + diff --git a/builtin/game/deprecated.lua b/builtin/game/deprecated.lua new file mode 100644 index 0000000..bbe68be --- /dev/null +++ b/builtin/game/deprecated.lua @@ -0,0 +1,53 @@ +-- Minetest: builtin/deprecated.lua + +-- +-- Default material types +-- +function digprop_err() + core.log("info", debug.traceback()) + core.log("info", "WARNING: The core.digprop_* functions are obsolete and need to be replaced by item groups.") +end + +core.digprop_constanttime = digprop_err +core.digprop_stonelike = digprop_err +core.digprop_dirtlike = digprop_err +core.digprop_gravellike = digprop_err +core.digprop_woodlike = digprop_err +core.digprop_leaveslike = digprop_err +core.digprop_glasslike = digprop_err + +core.node_metadata_inventory_move_allow_all = function() + core.log("info", "WARNING: core.node_metadata_inventory_move_allow_all is obsolete and does nothing.") +end + +core.add_to_creative_inventory = function(itemstring) + core.log('info', "WARNING: core.add_to_creative_inventory: This function is deprecated and does nothing.") +end + +-- +-- EnvRef +-- +core.env = {} +local envref_deprecation_message_printed = false +setmetatable(core.env, { + __index = function(table, key) + if not envref_deprecation_message_printed then + core.log("info", "WARNING: core.env:[...] is deprecated and should be replaced with core.[...]") + envref_deprecation_message_printed = true + end + local func = core[key] + if type(func) == "function" then + rawset(table, key, function(self, ...) + return func(...) + end) + else + rawset(table, key, nil) + end + return rawget(table, key) + end +}) + +function core.rollback_get_last_node_actor(pos, range, seconds) + return core.rollback_get_node_actions(pos, range, seconds, 1)[1] +end + diff --git a/builtin/game/detached_inventory.lua b/builtin/game/detached_inventory.lua new file mode 100644 index 0000000..e8f03b5 --- /dev/null +++ b/builtin/game/detached_inventory.lua @@ -0,0 +1,19 @@ +-- Minetest: builtin/detached_inventory.lua + +core.detached_inventories = {} + +function core.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 + core.detached_inventories[name] = stuff + return core.create_detached_inventory_raw(name) +end + diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua new file mode 100644 index 0000000..58f68fc --- /dev/null +++ b/builtin/game/falling.lua @@ -0,0 +1,224 @@ +-- Minetest: builtin/item.lua + +-- +-- Falling stuff +-- + +core.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 core.registered_items[itemname] then + item_texture = core.registered_items[itemname].inventory_image + item_type = core.registered_items[itemname].type + end + local prop = { + is_visible = true, + 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: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 = core.get_node(bcp) + local bcd = core.registered_nodes[bcn.name] + -- Note: walkable is in the node definition, not in item groups + if not bcd or + (bcd.walkable or + (core.get_item_group(self.node.name, "float") ~= 0 and + bcd.liquidtype ~= "none")) then + if bcd and bcd.leveled and + bcn.name == self.node.name then + local addlevel = self.node.level + if addlevel == nil or addlevel <= 0 then + addlevel = bcd.leveled + end + if core.add_node_level(bcp, addlevel) == 0 then + self.object:remove() + return + end + elseif bcd and bcd.buildable_to and + (core.get_item_group(self.node.name, "float") == 0 or + bcd.liquidtype == "none") then + core.remove_node(bcp) + return + end + local np = {x=bcp.x, y=bcp.y+1, z=bcp.z} + -- Check what's here + local n2 = core.get_node(np) + -- If it's not air or liquid, remove node and replace it with + -- it's drops + if n2.name ~= "air" and (not core.registered_nodes[n2.name] or + core.registered_nodes[n2.name].liquidtype == "none") then + core.remove_node(np) + if core.registered_nodes[n2.name].buildable_to == false then + -- Add dropped items + local drops = core.get_node_drops(n2.name, "") + local _, dropped_item + for _, dropped_item in ipairs(drops) do + core.add_item(np, dropped_item) + end + end + -- Run script hook + local _, callback + for _, callback in ipairs(core.registered_on_dignodes) do + callback(np, n2, nil) + end + end + -- Create node and remove entity + core.add_node(np, self.node) + self.object:remove() + nodeupdate(np) + return + end + local vel = self.object:getvelocity() + if vector.equals(vel, {x=0,y=0,z=0}) then + local npos = self.object:getpos() + self.object:setpos(vector.round(npos)) + end + end +}) + +function spawn_falling_node(p, node) + local obj = core.add_entity(p, "__builtin:falling_node") + obj:get_luaentity():set_node(node) +end + +function drop_attached_node(p) + local nn = core.get_node(p).name + core.remove_node(p) + for _,item in ipairs(core.get_node_drops(nn, "")) do + local pos = { + x = p.x + math.random()/2 - 0.25, + y = p.y + math.random()/2 - 0.25, + z = p.z + math.random()/2 - 0.25, + } + core.add_item(pos, item) + end +end + +function check_attached_node(p, n) + local def = core.registered_nodes[n.name] + local d = {x=0, y=0, z=0} + if def.paramtype2 == "wallmounted" then + 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 = core.get_node(p2).name + local def2 = core.registered_nodes[nn] + if def2 and not def2.walkable then + return false + end + return true +end + +-- +-- Some common functions +-- + +function nodeupdate_single(p, delay) + local n = core.get_node(p) + if core.get_item_group(n.name, "falling_node") ~= 0 then + local p_bottom = {x=p.x, y=p.y-1, z=p.z} + local n_bottom = core.get_node(p_bottom) + -- Note: walkable is in the node definition, not in item groups + if core.registered_nodes[n_bottom.name] and + (core.get_item_group(n.name, "float") == 0 or + core.registered_nodes[n_bottom.name].liquidtype == "none") and + (n.name ~= n_bottom.name or (core.registered_nodes[n_bottom.name].leveled and + core.get_node_level(p_bottom) < core.get_node_max_level(p_bottom))) and + (not core.registered_nodes[n_bottom.name].walkable or + core.registered_nodes[n_bottom.name].buildable_to) then + if delay then + core.after(0.1, nodeupdate_single, {x=p.x, y=p.y, z=p.z}, false) + else + n.level = core.get_node_level(p) + core.remove_node(p) + spawn_falling_node(p, n) + nodeupdate(p) + end + end + end + + if core.get_item_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 +core.register_on_placenode(on_placenode) + +function on_dignode(p, node) + nodeupdate(p) +end +core.register_on_dignode(on_dignode) diff --git a/builtin/game/features.lua b/builtin/game/features.lua new file mode 100644 index 0000000..f082b0d --- /dev/null +++ b/builtin/game/features.lua @@ -0,0 +1,30 @@ +-- Minetest: builtin/features.lua + +core.features = { + glasslike_framed = true, + nodebox_as_selectionbox = true, + chat_send_player_param3 = true, + get_all_craft_recipes_works = true, + use_texture_alpha = true, + no_legacy_abms = true, + texture_names_parens = true, +} + +function core.has_feature(arg) + if type(arg) == "table" then + missing_features = {} + result = true + for ft, _ in pairs(arg) do + if not core.features[ftr] then + missing_features[ftr] = true + result = false + end + end + return result, missing_features + elseif type(arg) == "string" then + if not core.features[arg] then + return false, {[arg]=true} + end + return true, {} + end +end diff --git a/builtin/game/forceloading.lua b/builtin/game/forceloading.lua new file mode 100644 index 0000000..8c9fbf5 --- /dev/null +++ b/builtin/game/forceloading.lua @@ -0,0 +1,79 @@ +-- Prevent anyone else accessing those functions +local forceload_block = core.forceload_block +local forceload_free_block = core.forceload_free_block +core.forceload_block = nil +core.forceload_free_block = nil + +local blocks_forceloaded +local total_forceloaded = 0 + +local BLOCKSIZE = 16 +local function get_blockpos(pos) + return { + x = math.floor(pos.x/BLOCKSIZE), + y = math.floor(pos.y/BLOCKSIZE), + z = math.floor(pos.z/BLOCKSIZE)} +end + +function core.forceload_block(pos) + local blockpos = get_blockpos(pos) + local hash = core.hash_node_position(blockpos) + if blocks_forceloaded[hash] ~= nil then + blocks_forceloaded[hash] = blocks_forceloaded[hash] + 1 + return true + else + if total_forceloaded >= (tonumber(core.setting_get("max_forceloaded_blocks")) or 16) then + return false + end + total_forceloaded = total_forceloaded+1 + blocks_forceloaded[hash] = 1 + forceload_block(blockpos) + return true + end +end + +function core.forceload_free_block(pos) + local blockpos = get_blockpos(pos) + local hash = core.hash_node_position(blockpos) + if blocks_forceloaded[hash] == nil then return end + if blocks_forceloaded[hash] > 1 then + blocks_forceloaded[hash] = blocks_forceloaded[hash] - 1 + else + total_forceloaded = total_forceloaded-1 + blocks_forceloaded[hash] = nil + forceload_free_block(blockpos) + end +end + +-- Keep the forceloaded areas after restart +local wpath = core.get_worldpath() +local function read_file(filename) + local f = io.open(filename, "r") + if f==nil then return {} end + local t = f:read("*all") + f:close() + if t=="" or t==nil then return {} end + return core.deserialize(t) or {} +end + +local function write_file(filename, table) + local f = io.open(filename, "w") + f:write(core.serialize(table)) + f:close() +end + +blocks_forceloaded = read_file(wpath.."/force_loaded.txt") +for _, __ in pairs(blocks_forceloaded) do + total_forceloaded = total_forceloaded + 1 +end + +core.after(5, function() + for hash, _ in pairs(blocks_forceloaded) do + local blockpos = core.get_position_from_hash(hash) + forceload_block(blockpos) + end +end) + +core.register_on_shutdown(function() + write_file(wpath.."/force_loaded.txt", blocks_forceloaded) +end) diff --git a/builtin/game/init.lua b/builtin/game/init.lua new file mode 100644 index 0000000..3f82f85 --- /dev/null +++ b/builtin/game/init.lua @@ -0,0 +1,28 @@ + +local scriptpath = minetest.get_builtin_path()..DIR_DELIM +local commonpath = scriptpath.."common"..DIR_DELIM +local gamepath = scriptpath.."game"..DIR_DELIM + +dofile(commonpath.."vector.lua") + +dofile(gamepath.."item.lua") +dofile(gamepath.."register.lua") + +if core.setting_getbool("mod_profiling") then + dofile(gamepath.."mod_profiling.lua") +end + +dofile(gamepath.."item_entity.lua") +dofile(gamepath.."deprecated.lua") +dofile(gamepath.."misc.lua") +dofile(gamepath.."privileges.lua") +dofile(gamepath.."auth.lua") +dofile(gamepath.."chatcommands.lua") +dofile(gamepath.."static_spawn.lua") +dofile(gamepath.."detached_inventory.lua") +dofile(gamepath.."falling.lua") +dofile(gamepath.."features.lua") +dofile(gamepath.."voxelarea.lua") +dofile(gamepath.."forceloading.lua") +dofile(gamepath.."statbars.lua") + diff --git a/builtin/game/item.lua b/builtin/game/item.lua new file mode 100644 index 0000000..d25f4ef --- /dev/null +++ b/builtin/game/item.lua @@ -0,0 +1,618 @@ +-- Minetest: builtin/item.lua + +local function copy_pointed_thing(pointed_thing) + return { + type = pointed_thing.type, + above = vector.new(pointed_thing.above), + under = vector.new(pointed_thing.under), + ref = pointed_thing.ref, + } +end + +-- +-- Item definition helpers +-- + +function core.inventorycube(img1, img2, img3) + img2 = img2 or img1 + img3 = img3 or img1 + return "[inventorycube" + .. "{" .. img1:gsub("%^", "&") + .. "{" .. img2:gsub("%^", "&") + .. "{" .. img3:gsub("%^", "&") +end + +function core.get_pointed_thing_position(pointed_thing, above) + if pointed_thing.type == "node" then + if above then + -- The position where a node would be placed + return pointed_thing.above + 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 core.dir_to_facedir(dir, is6d) + --account for y if requested + if is6d and math.abs(dir.y) > math.abs(dir.x) and math.abs(dir.y) > math.abs(dir.z) then + + --from above + if dir.y < 0 then + if math.abs(dir.x) > math.abs(dir.z) then + if dir.x < 0 then + return 19 + else + return 13 + end + else + if dir.z < 0 then + return 10 + else + return 4 + end + end + + --from below + else + if math.abs(dir.x) > math.abs(dir.z) then + if dir.x < 0 then + return 15 + else + return 17 + end + else + if dir.z < 0 then + return 6 + else + return 8 + end + end + end + + --otherwise, place horizontally + elseif math.abs(dir.x) > math.abs(dir.z) then + if dir.x < 0 then + return 3 + else + return 1 + end + else + if dir.z < 0 then + return 2 + else + return 0 + end + end +end + +function core.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, 2, 6, 4, + 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 core.dir_to_wallmounted(dir) + if math.abs(dir.y) > math.max(math.abs(dir.x), math.abs(dir.z)) then + if dir.y < 0 then + return 1 + else + return 0 + end + elseif math.abs(dir.x) > math.abs(dir.z) then + if dir.x < 0 then + return 3 + else + return 2 + end + else + if dir.z < 0 then + return 5 + else + return 4 + end + end +end + +function core.get_node_drops(nodename, toolname) + local drop = ItemStack({name=nodename}):get_definition().drop + if drop == nil then + -- default drop + return {nodename} + elseif type(drop) == "string" then + -- itemstring drop + return {drop} + elseif drop.items == nil then + -- drop = {} to disable default drop + return {} + end + + -- Extended drop table + local got_items = {} + local got_count = 0 + local _, item, tool + for _, item in ipairs(drop.items) do + local good_rarity = true + local good_tool = true + if item.rarity ~= nil then + good_rarity = item.rarity < 1 or math.random(item.rarity) == 1 + end + if item.tools ~= nil then + good_tool = false + for _, tool in ipairs(item.tools) do + if tool:sub(1, 1) == '~' then + good_tool = toolname:find(tool:sub(2)) ~= nil + else + good_tool = toolname == tool + end + if good_tool then + break + end + end + end + if good_rarity and good_tool then + got_count = got_count + 1 + for _, add_item in ipairs(item.items) do + got_items[#got_items+1] = add_item + end + if drop.max_items ~= nil and got_count == drop.max_items then + break + end + end + end + return got_items +end + +function core.item_place_node(itemstack, placer, pointed_thing, param2) + local item = itemstack:peek_item() + local def = itemstack:get_definition() + if def.type ~= "node" or pointed_thing.type ~= "node" then + return itemstack, false + end + + local under = pointed_thing.under + local oldnode_under = core.get_node_or_nil(under) + local above = pointed_thing.above + local oldnode_above = core.get_node_or_nil(above) + + if not oldnode_under or not oldnode_above then + core.log("info", placer:get_player_name() .. " tried to place" + .. " node in unloaded position " .. core.pos_to_string(above)) + return itemstack, false + end + + local olddef_under = ItemStack({name=oldnode_under.name}):get_definition() + olddef_under = olddef_under or core.nodedef_default + local olddef_above = ItemStack({name=oldnode_above.name}):get_definition() + olddef_above = olddef_above or core.nodedef_default + + if not olddef_above.buildable_to and not olddef_under.buildable_to then + core.log("info", placer:get_player_name() .. " tried to place" + .. " node in invalid position " .. core.pos_to_string(above) + .. ", replacing " .. oldnode_above.name) + return itemstack, false + end + + -- Place above pointed node + local place_to = {x = above.x, y = above.y, z = above.z} + + -- If node under is buildable_to, place into it instead (eg. snow) + if olddef_under.buildable_to then + core.log("info", "node under is buildable to") + place_to = {x = under.x, y = under.y, z = under.z} + end + + if core.is_protected(place_to, placer:get_player_name()) then + core.log("action", placer:get_player_name() + .. " tried to place " .. def.name + .. " at protected position " + .. core.pos_to_string(place_to)) + core.record_protection_violation(place_to, placer:get_player_name()) + return itemstack + end + + core.log("action", placer:get_player_name() .. " places node " + .. def.name .. " at " .. core.pos_to_string(place_to)) + + local oldnode = core.get_node(place_to) + local newnode = {name = def.name, param1 = 0, param2 = param2} + + -- Calculate direction for wall mounted stuff like torches and signs + if def.paramtype2 == 'wallmounted' and not param2 then + local dir = { + x = under.x - above.x, + y = under.y - above.y, + z = under.z - above.z + } + newnode.param2 = core.dir_to_wallmounted(dir) + -- Calculate the direction for furnaces and chests and stuff + elseif def.paramtype2 == 'facedir' and not param2 then + local placer_pos = placer:getpos() + if placer_pos then + local dir = { + x = above.x - placer_pos.x, + y = above.y - placer_pos.y, + z = above.z - placer_pos.z + } + newnode.param2 = core.dir_to_facedir(dir) + core.log("action", "facedir: " .. newnode.param2) + end + end + + -- Check if the node is attached and if it can be placed there + if core.get_item_group(def.name, "attached_node") ~= 0 and + not check_attached_node(place_to, newnode) then + core.log("action", "attached node " .. def.name .. + " can not be placed at " .. core.pos_to_string(place_to)) + return itemstack, false + end + + -- Add node and update + core.add_node(place_to, newnode) + + local take_item = true + + -- Run callback + if def.after_place_node then + -- Deepcopy place_to and pointed_thing because callback can modify it + local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z} + local pointed_thing_copy = copy_pointed_thing(pointed_thing) + if def.after_place_node(place_to_copy, placer, itemstack, + pointed_thing_copy) then + take_item = false + end + end + + -- Run script hook + local _, callback + for _, callback in ipairs(core.registered_on_placenodes) do + -- Deepcopy pos, node and pointed_thing because callback can modify them + local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z} + local newnode_copy = {name=newnode.name, param1=newnode.param1, param2=newnode.param2} + local oldnode_copy = {name=oldnode.name, param1=oldnode.param1, param2=oldnode.param2} + local pointed_thing_copy = copy_pointed_thing(pointed_thing) + if callback(place_to_copy, newnode_copy, placer, oldnode_copy, itemstack, pointed_thing_copy) then + take_item = false + end + end + + if take_item then + itemstack:take_item() + end + return itemstack, true +end + +function core.item_place_object(itemstack, placer, pointed_thing) + local pos = core.get_pointed_thing_position(pointed_thing, true) + if pos ~= nil then + local item = itemstack:take_item() + core.add_item(pos, item) + end + return itemstack +end + +function core.item_place(itemstack, placer, pointed_thing, param2) + -- Call on_rightclick if the pointed node defines it + if pointed_thing.type == "node" and placer and + not placer:get_player_control().sneak then + local n = core.get_node(pointed_thing.under) + local nn = n.name + if core.registered_nodes[nn] and core.registered_nodes[nn].on_rightclick then + return core.registered_nodes[nn].on_rightclick(pointed_thing.under, n, + placer, itemstack, pointed_thing) or itemstack, false + end + end + + if itemstack:get_definition().type == "node" then + return core.item_place_node(itemstack, placer, pointed_thing, param2) + end + return itemstack +end + +function core.item_drop(itemstack, dropper, pos) + if dropper.is_player then + local v = dropper:get_look_dir() + local p = {x=pos.x, y=pos.y+1.2, z=pos.z} + local cs = itemstack:get_count() + if dropper:get_player_control().sneak then + cs = 1 + end + local item = itemstack:take_item(cs) + local obj = core.add_item(p, item) + if obj then + v.x = v.x*2 + v.y = v.y*2 + 2 + v.z = v.z*2 + obj:setvelocity(v) + end + + else + core.add_item(pos, itemstack) + end + return itemstack +end + +function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing) + for _, callback in pairs(core.registered_on_item_eats) do + local result = callback(hp_change, replace_with_item, itemstack, user, pointed_thing) + if result then + return result + end + end + if itemstack:take_item() ~= nil then + user:set_hp(user:get_hp() + hp_change) + + if replace_with_item then + if itemstack:is_empty() then + itemstack:add_item(replace_with_item) + else + local inv = user:get_inventory() + if inv:room_for_item("main", {name=replace_with_item}) then + inv:add_item("main", replace_with_item) + else + local pos = user:getpos() + pos.y = math.floor(pos.y + 0.5) + core.add_item(pos, replace_with_item) + end + end + end + end + return itemstack +end + +function core.item_eat(hp_change, replace_with_item) + return function(itemstack, user, pointed_thing) -- closure + return core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing) + end +end + +function core.node_punch(pos, node, puncher, pointed_thing) + -- Run script hook + for _, callback in ipairs(core.registered_on_punchnodes) do + -- Copy pos and node because callback can modify them + local pos_copy = vector.new(pos) + local node_copy = {name=node.name, param1=node.param1, param2=node.param2} + local pointed_thing_copy = pointed_thing and copy_pointed_thing(pointed_thing) or nil + callback(pos_copy, node_copy, puncher, pointed_thing_copy) + end +end + +function core.handle_node_drops(pos, drops, digger) + -- Add dropped items to object's inventory + if digger:get_inventory() then + local _, dropped_item + for _, dropped_item in ipairs(drops) do + local left = digger:get_inventory():add_item("main", dropped_item) + if not left:is_empty() then + local p = { + x = pos.x + math.random()/2-0.25, + y = pos.y + math.random()/2-0.25, + z = pos.z + math.random()/2-0.25, + } + core.add_item(p, left) + end + end + end +end + +function core.node_dig(pos, node, digger) + local def = ItemStack({name=node.name}):get_definition() + if not def.diggable or (def.can_dig and not def.can_dig(pos,digger)) then + core.log("info", digger:get_player_name() .. " tried to dig " + .. node.name .. " which is not diggable " + .. core.pos_to_string(pos)) + return + end + + if core.is_protected(pos, digger:get_player_name()) then + core.log("action", digger:get_player_name() + .. " tried to dig " .. node.name + .. " at protected position " + .. core.pos_to_string(pos)) + core.record_protection_violation(pos, digger:get_player_name()) + return + end + + core.log('action', digger:get_player_name() .. " digs " + .. node.name .. " at " .. core.pos_to_string(pos)) + + local wielded = digger:get_wielded_item() + local drops = core.get_node_drops(node.name, wielded:get_name()) + + local wdef = wielded:get_definition() + local tp = wielded:get_tool_capabilities() + local dp = core.get_dig_params(def.groups, tp) + if wdef and wdef.after_use then + wielded = wdef.after_use(wielded, digger, node, dp) or wielded + else + -- Wear out tool + if not core.setting_getbool("creative_mode") then + wielded:add_wear(dp.wear) + end + end + digger:set_wielded_item(wielded) + + -- Handle drops + core.handle_node_drops(pos, drops, digger) + + local oldmetadata = nil + if def.after_dig_node then + oldmetadata = core.get_meta(pos):to_table() + end + + -- Remove node and update + core.remove_node(pos) + + -- Run callback + if def.after_dig_node then + -- Copy pos and node because callback can modify them + local pos_copy = {x=pos.x, y=pos.y, z=pos.z} + local node_copy = {name=node.name, param1=node.param1, param2=node.param2} + def.after_dig_node(pos_copy, node_copy, oldmetadata, digger) + end + + -- Run script hook + local _, callback + for _, callback in ipairs(core.registered_on_dignodes) do + -- Copy pos and node because callback can modify them + local pos_copy = {x=pos.x, y=pos.y, z=pos.z} + local node_copy = {name=node.name, param1=node.param1, param2=node.param2} + callback(pos_copy, node_copy, digger) + end +end + +-- This is used to allow mods to redefine core.item_place and so on +-- NOTE: This is not the preferred way. Preferred way is to provide enough +-- callbacks to not require redefining global functions. -celeron55 +local function redef_wrapper(table, name) + return function(...) + return table[name](...) + end +end + +-- +-- Item definition defaults +-- + +core.nodedef_default = { + -- Item properties + type="node", + -- name intentionally not defined here + description = "", + groups = {}, + inventory_image = "", + wield_image = "", + wield_scale = {x=1,y=1,z=1}, + stack_max = 99, + usable = false, + liquids_pointable = false, + tool_capabilities = nil, + node_placement_prediction = nil, + + -- Interaction callbacks + on_place = redef_wrapper(core, 'item_place'), -- core.item_place + on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop + on_use = nil, + can_dig = nil, + + on_punch = redef_wrapper(core, 'node_punch'), -- core.node_punch + on_rightclick = nil, + on_dig = redef_wrapper(core, 'node_dig'), -- core.node_dig + + on_receive_fields = nil, + + on_metadata_inventory_move = core.node_metadata_inventory_move_allow_all, + on_metadata_inventory_offer = core.node_metadata_inventory_offer_allow_all, + on_metadata_inventory_take = core.node_metadata_inventory_take_allow_all, + + -- Node properties + drawtype = "normal", + visual_scale = 1.0, + -- Don't define these because otherwise the old tile_images and + -- special_materials wouldn't be read + --tiles ={""}, + --special_tiles = { + -- {name="", backface_culling=true}, + -- {name="", backface_culling=true}, + --}, + alpha = 255, + post_effect_color = {a=0, r=0, g=0, b=0}, + paramtype = "none", + paramtype2 = "none", + is_ground_content = true, + sunlight_propagates = false, + walkable = true, + pointable = true, + diggable = true, + climbable = false, + buildable_to = false, + liquidtype = "none", + liquid_alternative_flowing = "", + liquid_alternative_source = "", + liquid_viscosity = 0, + drowning = 0, + light_source = 0, + damage_per_second = 0, + selection_box = {type="regular"}, + legacy_facedir_simple = false, + legacy_wallmounted = false, +} + +core.craftitemdef_default = { + type="craft", + -- name intentionally not defined here + description = "", + groups = {}, + inventory_image = "", + wield_image = "", + wield_scale = {x=1,y=1,z=1}, + stack_max = 99, + liquids_pointable = false, + tool_capabilities = nil, + + -- Interaction callbacks + on_place = redef_wrapper(core, 'item_place'), -- core.item_place + on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop + on_use = nil, +} + +core.tooldef_default = { + type="tool", + -- name intentionally not defined here + description = "", + groups = {}, + inventory_image = "", + wield_image = "", + wield_scale = {x=1,y=1,z=1}, + stack_max = 1, + liquids_pointable = false, + tool_capabilities = nil, + + -- Interaction callbacks + on_place = redef_wrapper(core, 'item_place'), -- core.item_place + on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop + on_use = nil, +} + +core.noneitemdef_default = { -- This is used for the hand and unknown items + type="none", + -- name intentionally not defined here + description = "", + groups = {}, + inventory_image = "", + wield_image = "", + wield_scale = {x=1,y=1,z=1}, + stack_max = 99, + liquids_pointable = false, + tool_capabilities = nil, + + -- Interaction callbacks + on_place = redef_wrapper(core, 'item_place'), + on_drop = nil, + on_use = nil, +} diff --git a/builtin/game/item_entity.lua b/builtin/game/item_entity.lua new file mode 100644 index 0000000..d6781fe --- /dev/null +++ b/builtin/game/item_entity.lua @@ -0,0 +1,189 @@ +-- Minetest: builtin/item_entity.lua + +function core.spawn_item(pos, item) + -- Take item in any format + local stack = ItemStack(item) + local obj = core.add_entity(pos, "__builtin:item") + obj:get_luaentity():set_item(stack:to_string()) + return obj +end + +-- If item_entity_ttl is not set, enity will have default life time +-- Setting it to -1 disables the feature + +local time_to_live = tonumber(core.setting_get("item_entity_ttl")) +if not time_to_live then + time_to_live = 900 +end + +core.register_entity(":__builtin:item", { + initial_properties = { + hp_max = 1, + physical = true, + collide_with_objects = false, + collisionbox = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3}, + visual = "wielditem", + visual_size = {x = 0.4, y = 0.4}, + textures = {""}, + spritediv = {x = 1, y = 1}, + initial_sprite_basepos = {x = 0, y = 0}, + is_visible = false, + }, + + itemstring = '', + physical_state = true, + age = 0, + + set_item = function(self, itemstring) + self.itemstring = itemstring + local stack = ItemStack(itemstring) + local count = stack:get_count() + local max_count = stack:get_stack_max() + if count > max_count then + count = max_count + self.itemstring = stack:get_name().." "..max_count + end + local s = 0.2 + 0.1 * (count / max_count) + local c = s + local itemtable = stack:to_table() + local itemname = nil + if itemtable then + itemname = stack:to_table().name + end + local item_texture = nil + local item_type = "" + if core.registered_items[itemname] then + item_texture = core.registered_items[itemname].inventory_image + item_type = core.registered_items[itemname].type + end + local prop = { + is_visible = true, + visual = "wielditem", + textures = {itemname}, + visual_size = {x = s, y = s}, + collisionbox = {-c, -c, -c, c, c, c}, + automatic_rotate = math.pi * 0.5, + } + self.object:set_properties(prop) + end, + + get_staticdata = function(self) + return core.serialize({ + itemstring = self.itemstring, + always_collect = self.always_collect, + age = self.age + }) + end, + + on_activate = function(self, staticdata, dtime_s) + if string.sub(staticdata, 1, string.len("return")) == "return" then + local data = core.deserialize(staticdata) + if data and type(data) == "table" then + self.itemstring = data.itemstring + self.always_collect = data.always_collect + if data.age then + self.age = data.age + dtime_s + else + self.age = dtime_s + end + 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) + self.age = self.age + dtime + if time_to_live > 0 and self.age > time_to_live then + self.itemstring = '' + self.object:remove() + return + end + local p = self.object:getpos() + p.y = p.y - 0.5 + local nn = core.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 core.registered_nodes[nn] or core.registered_nodes[nn].walkable and v.y == 0 then + if self.physical_state then + local own_stack = ItemStack(self.object:get_luaentity().itemstring) + for _,object in ipairs(core.get_objects_inside_radius(p, 0.8)) do + local obj = object:get_luaentity() + if obj and obj.name == "__builtin:item" and obj.physical_state == false then + local stack = ItemStack(obj.itemstring) + if own_stack:get_name() == stack:get_name() and stack:get_free_space() > 0 then + local overflow = false + local count = stack:get_count() + own_stack:get_count() + local max_count = stack:get_stack_max() + if count>max_count then + overflow = true + count = count - max_count + else + self.itemstring = '' + end + local pos=object:getpos() + pos.y = pos.y + (count - stack:get_count()) / max_count * 0.15 + object:moveto(pos, false) + local s, c + local max_count = stack:get_stack_max() + local name = stack:get_name() + if not overflow then + obj.itemstring = name.." "..count + s = 0.2 + 0.1 * (count / max_count) + c = s + object:set_properties({ + visual_size = {x = s, y = s}, + collisionbox = {-c, -c, -c, c, c, c} + }) + self.object:remove() + return + else + s = 0.4 + c = 0.3 + object:set_properties({ + visual_size = {x = s, y = s}, + collisionbox = {-c, -c, -c, c, c, c} + }) + obj.itemstring = name.." "..max_count + s = 0.2 + 0.1 * (count / max_count) + c = s + self.object:set_properties({ + visual_size = {x = s, y = s}, + collisionbox = {-c, -c, -c, c, c, c} + }) + self.itemstring = name.." "..count + end + end + end + end + self.object:setvelocity({x = 0, y = 0, z = 0}) + self.object:setacceleration({x = 0, y = 0, z = 0}) + self.physical_state = false + self.object:set_properties({physical = false}) + end + else + if not self.physical_state then + self.object:setvelocity({x = 0, y = 0, z = 0}) + self.object:setacceleration({x = 0, y = -10, z = 0}) + self.physical_state = true + self.object:set_properties({physical = true}) + end + end + end, + + on_punch = function(self, hitter) + 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.itemstring = '' + self.object:remove() + end, +}) diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua new file mode 100644 index 0000000..c31df54 --- /dev/null +++ b/builtin/game/misc.lua @@ -0,0 +1,114 @@ +-- Minetest: builtin/misc.lua + +-- +-- Misc. API functions +-- + +core.timers_to_add = {} +core.timers = {} +core.register_globalstep(function(dtime) + for _, timer in ipairs(core.timers_to_add) do + table.insert(core.timers, timer) + end + core.timers_to_add = {} + local index = 1 + while index <= #core.timers do + local timer = core.timers[index] + timer.time = timer.time - dtime + if timer.time <= 0 then + timer.func(unpack(timer.args or {})) + table.remove(core.timers,index) + else + index = index + 1 + end + end +end) + +function core.after(time, func, ...) + assert(tonumber(time) and type(func) == "function", + "Invalid core.after invocation") + table.insert(core.timers_to_add, {time=time, func=func, args={...}}) +end + +function core.check_player_privs(name, privs) + local player_privs = core.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 = {} + +core.register_on_joinplayer(function(player) + player_list[player:get_player_name()] = player +end) + +core.register_on_leaveplayer(function(player) + player_list[player:get_player_name()] = nil +end) + +function core.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 core.hash_node_position(pos) + return (pos.z+32768)*65536*65536 + (pos.y+32768)*65536 + pos.x+32768 +end + +function core.get_position_from_hash(hash) + local pos = {} + pos.x = (hash%65536) - 32768 + hash = math.floor(hash/65536) + pos.y = (hash%65536) - 32768 + hash = math.floor(hash/65536) + pos.z = (hash%65536) - 32768 + return pos +end + +function core.get_item_group(name, group) + if not core.registered_items[name] or not + core.registered_items[name].groups[group] then + return 0 + end + return core.registered_items[name].groups[group] +end + +function core.get_node_group(name, group) + core.log("deprecated", "Deprecated usage of get_node_group, use get_item_group instead") + return core.get_item_group(name, group) +end + +function core.setting_get_pos(name) + local value = core.setting_get(name) + if not value then + return nil + end + return core.string_to_pos(value) +end + +-- To be overriden by protection mods +function core.is_protected(pos, name) + return false +end + +function core.record_protection_violation(pos, name) + for _, func in pairs(core.registered_on_protection_violation) do + func(pos, name) + end +end + diff --git a/builtin/game/mod_profiling.lua b/builtin/game/mod_profiling.lua new file mode 100644 index 0000000..df2d102 --- /dev/null +++ b/builtin/game/mod_profiling.lua @@ -0,0 +1,356 @@ +-- Minetest: builtin/game/mod_profiling.lua + +local mod_statistics = {} +mod_statistics.step_total = 0 +mod_statistics.data = {} +mod_statistics.stats = {} +mod_statistics.stats["total"] = { + min_us = math.huge, + max_us = 0, + avg_us = 0, + min_per = 100, + max_per = 100, + avg_per = 100 +} + +local replacement_table = { + "register_globalstep", + "register_on_placenode", + "register_on_dignode", + "register_on_punchnode", + "register_on_generated", + "register_on_newplayer", + "register_on_dieplayer", + "register_on_respawnplayer", + "register_on_prejoinplayer", + "register_on_joinplayer", + "register_on_leaveplayer", + "register_on_cheat", + "register_on_chat_message", + "register_on_player_receive_fields", + "register_on_mapgen_init", + "register_on_craft", + "register_craft_predict", + "register_on_item_eat" +} + +-------------------------------------------------------------------------------- +function mod_statistics.log_time(type, modname, time_in_us) + + if modname == nil then + modname = "builtin" + end + + if mod_statistics.data[modname] == nil then + mod_statistics.data[modname] = {} + end + + if mod_statistics.data[modname][type] == nil then + mod_statistics.data[modname][type] = 0 + end + + mod_statistics.data[modname][type] = + mod_statistics.data[modname][type] + time_in_us + mod_statistics.step_total = mod_statistics.step_total + time_in_us +end + +-------------------------------------------------------------------------------- +function mod_statistics.update_statistics(dtime) + for modname,types in pairs(mod_statistics.data) do + + if mod_statistics.stats[modname] == nil then + mod_statistics.stats[modname] = { + min_us = math.huge, + max_us = 0, + avg_us = 0, + min_per = 100, + max_per = 0, + avg_per = 0 + } + end + + local modtime = 0 + for type,time in pairs(types) do + modtime = modtime + time + if mod_statistics.stats[modname].types == nil then + mod_statistics.stats[modname].types = {} + end + if mod_statistics.stats[modname].types[type] == nil then + mod_statistics.stats[modname].types[type] = { + min_us = math.huge, + max_us = 0, + avg_us = 0, + min_per = 100, + max_per = 0, + avg_per = 0 + } + end + + local toupdate = mod_statistics.stats[modname].types[type] + + --update us values + toupdate.min_us = math.min(time, toupdate.min_us) + toupdate.max_us = math.max(time, toupdate.max_us) + --TODO find better algorithm + toupdate.avg_us = toupdate.avg_us * 0.99 + modtime * 0.01 + + --update percentage values + local cur_per = (time/mod_statistics.step_total) * 100 + toupdate.min_per = math.min(toupdate.min_per, cur_per) + + toupdate.max_per = math.max(toupdate.max_per, cur_per) + + --TODO find better algorithm + toupdate.avg_per = toupdate.avg_per * 0.99 + cur_per * 0.01 + + mod_statistics.data[modname][type] = 0 + end + + --per mod statistics + --update us values + mod_statistics.stats[modname].min_us = + math.min(modtime, mod_statistics.stats[modname].min_us) + mod_statistics.stats[modname].max_us = + math.max(modtime, mod_statistics.stats[modname].max_us) + --TODO find better algorithm + mod_statistics.stats[modname].avg_us = + mod_statistics.stats[modname].avg_us * 0.99 + modtime * 0.01 + + --update percentage values + local cur_per = (modtime/mod_statistics.step_total) * 100 + mod_statistics.stats[modname].min_per = + math.min(mod_statistics.stats[modname].min_per, cur_per) + + mod_statistics.stats[modname].max_per = + math.max(mod_statistics.stats[modname].max_per, cur_per) + + --TODO find better algorithm + mod_statistics.stats[modname].avg_per = + mod_statistics.stats[modname].avg_per * 0.99 + cur_per * 0.01 + end + + --update "total" + mod_statistics.stats["total"].min_us = + math.min(mod_statistics.step_total, mod_statistics.stats["total"].min_us) + mod_statistics.stats["total"].max_us = + math.max(mod_statistics.step_total, mod_statistics.stats["total"].max_us) + --TODO find better algorithm + mod_statistics.stats["total"].avg_us = + mod_statistics.stats["total"].avg_us * 0.99 + + mod_statistics.step_total * 0.01 + + mod_statistics.step_total = 0 +end + +-------------------------------------------------------------------------------- +local function build_callback(log_id, fct) + return function( toregister ) + local modname = core.get_current_modname() + + fct(function(...) + local starttime = core.get_us_time() + -- note maximum 10 return values are supported unless someone finds + -- a way to store a variable lenght return value list + local r0, r1, r2, r3, r4, r5, r6, r7, r8, r9 = toregister(...) + local delta = core.get_us_time() - starttime + mod_statistics.log_time(log_id, modname, delta) + return r0, r1, r2, r3, r4, r5, r6, r7, r8, r9 + end + ) + end +end + +-------------------------------------------------------------------------------- +function profiling_print_log(cmd, filter) + + print("Filter:" .. dump(filter)) + + core.log("action", "Values below show times/percentages per server step.") + core.log("action", "Following suffixes are used for entities:") + core.log("action", "\t#oa := on_activate, #os := on_step, #op := on_punch, #or := on_rightclick, #gs := get_staticdata") + core.log("action", + string.format("%16s | %25s | %10s | %10s | %10s | %9s | %9s | %9s", + "modname", "type" , "min µs", "max µs", "avg µs", "min %", "max %", "avg %") + ) + core.log("action", + "-----------------+---------------------------+-----------+" .. + "-----------+-----------+-----------+-----------+-----------") + for modname,statistics in pairs(mod_statistics.stats) do + if modname ~= "total" then + + if (filter == "") or (modname == filter) then + if modname:len() > 16 then + modname = "..." .. modname:sub(-13) + end + + core.log("action", + string.format("%16s | %25s | %9d | %9d | %9d | %9d | %9d | %9d", + modname, + " ", + statistics.min_us, + statistics.max_us, + statistics.avg_us, + statistics.min_per, + statistics.max_per, + statistics.avg_per) + ) + if core.setting_getbool("detailed_profiling") then + if statistics.types ~= nil then + for type,typestats in pairs(statistics.types) do + + if type:len() > 25 then + type = "..." .. type:sub(-22) + end + + core.log("action", + string.format( + "%16s | %25s | %9d | %9d | %9d | %9d | %9d | %9d", + " ", + type, + typestats.min_us, + typestats.max_us, + typestats.avg_us, + typestats.min_per, + typestats.max_per, + typestats.avg_per) + ) + end + end + end + end + end + end + core.log("action", + "-----------------+---------------------------+-----------+" .. + "-----------+-----------+-----------+-----------+-----------") + + if filter == "" then + core.log("action", + string.format("%16s | %25s | %9d | %9d | %9d | %9d | %9d | %9d", + "total", + " ", + mod_statistics.stats["total"].min_us, + mod_statistics.stats["total"].max_us, + mod_statistics.stats["total"].avg_us, + mod_statistics.stats["total"].min_per, + mod_statistics.stats["total"].max_per, + mod_statistics.stats["total"].avg_per) + ) + end + core.log("action", " ") + + return true +end + +-------------------------------------------------------------------------------- +local function initialize_profiling() + core.log("action", "Initialize tracing") + + mod_statistics.entity_callbacks = {} + + -- first register our own globalstep handler + core.register_globalstep(mod_statistics.update_statistics) + + local rp_register_entity = core.register_entity + core.register_entity = function(name, prototype) + local modname = core.get_current_modname() + local new_on_activate = nil + local new_on_step = nil + local new_on_punch = nil + local new_rightclick = nil + local new_get_staticdata = nil + + if prototype.on_activate ~= nil then + local cbid = name .. "#oa" + mod_statistics.entity_callbacks[cbid] = prototype.on_activate + new_on_activate = function(self, staticdata, dtime_s) + local starttime = core.get_us_time() + mod_statistics.entity_callbacks[cbid](self, staticdata, dtime_s) + local delta = core.get_us_time() -starttime + mod_statistics.log_time(cbid, modname, delta) + end + end + + if prototype.on_step ~= nil then + local cbid = name .. "#os" + mod_statistics.entity_callbacks[cbid] = prototype.on_step + new_on_step = function(self, dtime) + local starttime = core.get_us_time() + mod_statistics.entity_callbacks[cbid](self, dtime) + local delta = core.get_us_time() -starttime + mod_statistics.log_time(cbid, modname, delta) + end + end + + if prototype.on_punch ~= nil then + local cbid = name .. "#op" + mod_statistics.entity_callbacks[cbid] = prototype.on_punch + new_on_punch = function(self, hitter) + local starttime = core.get_us_time() + mod_statistics.entity_callbacks[cbid](self, hitter) + local delta = core.get_us_time() -starttime + mod_statistics.log_time(cbid, modname, delta) + end + end + + if prototype.rightclick ~= nil then + local cbid = name .. "#rc" + mod_statistics.entity_callbacks[cbid] = prototype.rightclick + new_rightclick = function(self, clicker) + local starttime = core.get_us_time() + mod_statistics.entity_callbacks[cbid](self, clicker) + local delta = core.get_us_time() -starttime + mod_statistics.log_time(cbid, modname, delta) + end + end + + if prototype.get_staticdata ~= nil then + local cbid = name .. "#gs" + mod_statistics.entity_callbacks[cbid] = prototype.get_staticdata + new_get_staticdata = function(self) + local starttime = core.get_us_time() + local retval = mod_statistics.entity_callbacks[cbid](self) + local delta = core.get_us_time() -starttime + mod_statistics.log_time(cbid, modname, delta) + return retval + end + end + + prototype.on_activate = new_on_activate + prototype.on_step = new_on_step + prototype.on_punch = new_on_punch + prototype.rightclick = new_rightclick + prototype.get_staticdata = new_get_staticdata + + rp_register_entity(name,prototype) + end + + for i,v in ipairs(replacement_table) do + local to_replace = core[v] + core[v] = build_callback(v, to_replace) + end + + local rp_register_abm = core.register_abm + core.register_abm = function(spec) + + local modname = core.get_current_modname() + + local replacement_spec = { + nodenames = spec.nodenames, + neighbors = spec.neighbors, + interval = spec.interval, + chance = spec.chance, + action = function(pos, node, active_object_count, active_object_count_wider) + local starttime = core.get_us_time() + spec.action(pos, node, active_object_count, active_object_count_wider) + local delta = core.get_us_time() - starttime + mod_statistics.log_time("abm", modname, delta) + end + } + rp_register_abm(replacement_spec) + end + + core.log("action", "Mod profiling initialized") +end + +initialize_profiling() diff --git a/builtin/game/privileges.lua b/builtin/game/privileges.lua new file mode 100644 index 0000000..7e6387c --- /dev/null +++ b/builtin/game/privileges.lua @@ -0,0 +1,53 @@ +-- Minetest: builtin/privileges.lua + +-- +-- Privileges +-- + +core.registered_privileges = {} + +function core.register_privilege(name, param) + local function fill_defaults(def) + if def.give_to_singleplayer == nil then + def.give_to_singleplayer = true + end + if def.description == nil then + def.description = "(no description)" + end + end + local def = {} + if type(param) == "table" then + def = param + else + def = {description = param} + end + fill_defaults(def) + core.registered_privileges[name] = def +end + +core.register_privilege("interact", "Can interact with things and modify the world") +core.register_privilege("teleport", "Can use /teleport command") +core.register_privilege("bring", "Can teleport other players") +core.register_privilege("settime", "Can use /time") +core.register_privilege("privs", "Can modify privileges") +core.register_privilege("basic_privs", "Can modify 'shout' and 'interact' privileges") +core.register_privilege("server", "Can do server maintenance stuff") +core.register_privilege("shout", "Can speak in chat") +core.register_privilege("ban", "Can ban and unban players") +core.register_privilege("kick", "Can kick players") +core.register_privilege("give", "Can use /give and /giveme") +core.register_privilege("password", "Can use /setpassword and /clearpassword") +core.register_privilege("fly", { + description = "Can fly using the free_move mode", + give_to_singleplayer = false, +}) +core.register_privilege("fast", { + description = "Can walk fast using the fast_move mode", + give_to_singleplayer = false, +}) +core.register_privilege("noclip", { + description = "Can fly through walls", + give_to_singleplayer = false, +}) +core.register_privilege("rollback", "Can use the rollback functionality") + diff --git a/builtin/game/register.lua b/builtin/game/register.lua new file mode 100644 index 0000000..f286113 --- /dev/null +++ b/builtin/game/register.lua @@ -0,0 +1,438 @@ +-- Minetest: builtin/misc_register.lua + +-- +-- Make raw registration functions inaccessible to anyone except this file +-- + +local register_item_raw = core.register_item_raw +core.register_item_raw = nil + +local register_alias_raw = core.register_alias_raw +core.register_alias_raw = nil + +-- +-- Item / entity / ABM registration functions +-- + +core.registered_abms = {} +core.registered_entities = {} +core.registered_items = {} +core.registered_nodes = {} +core.registered_craftitems = {} +core.registered_tools = {} +core.registered_aliases = {} + +-- For tables that are indexed by item name: +-- If table[X] does not exist, default to table[core.registered_aliases[X]] +local alias_metatable = { + __index = function(t, name) + return rawget(t, core.registered_aliases[name]) + end +} +setmetatable(core.registered_items, alias_metatable) +setmetatable(core.registered_nodes, alias_metatable) +setmetatable(core.registered_craftitems, alias_metatable) +setmetatable(core.registered_tools, alias_metatable) + +-- These item names may not be used because they would interfere +-- with legacy itemstrings +local forbidden_item_names = { + MaterialItem = true, + MaterialItem2 = true, + MaterialItem3 = true, + NodeItem = true, + node = true, + CraftItem = true, + craft = true, + MBOItem = true, + ToolItem = true, + tool = true, +} + +local function check_modname_prefix(name) + if name:sub(1,1) == ":" then + -- Escape the modname prefix enforcement mechanism + return name:sub(2) + else + -- Modname prefix enforcement + local expected_prefix = core.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 core.register_abm(spec) + -- Add to core.registered_abms + core.registered_abms[#core.registered_abms+1] = spec +end + +function core.register_entity(name, prototype) + -- Check name + if name == nil then + error("Unable to register entity: Name is nil") + end + name = check_modname_prefix(tostring(name)) + + prototype.name = name + prototype.__index = prototype -- so that it can be used as a metatable + + -- Add to core.registered_entities + core.registered_entities[name] = prototype +end + +function core.register_item(name, itemdef) + -- Check name + if name == nil then + error("Unable to register item: Name is nil") + end + name = check_modname_prefix(tostring(name)) + if forbidden_item_names[name] then + error("Unable to register item: Name is forbidden: " .. name) + end + itemdef.name = name + + -- Apply defaults and add to registered_* table + if itemdef.type == "node" then + -- Use the nodebox as selection box if it's not set manually + if itemdef.drawtype == "nodebox" and not itemdef.selection_box then + itemdef.selection_box = itemdef.node_box + elseif itemdef.drawtype == "fencelike" and not itemdef.selection_box then + itemdef.selection_box = { + type = "fixed", + fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8}, + } + end + setmetatable(itemdef, {__index = core.nodedef_default}) + core.registered_nodes[itemdef.name] = itemdef + elseif itemdef.type == "craft" then + setmetatable(itemdef, {__index = core.craftitemdef_default}) + core.registered_craftitems[itemdef.name] = itemdef + elseif itemdef.type == "tool" then + setmetatable(itemdef, {__index = core.tooldef_default}) + core.registered_tools[itemdef.name] = itemdef + elseif itemdef.type == "none" then + setmetatable(itemdef, {__index = core.noneitemdef_default}) + else + error("Unable to register item: Type is invalid: " .. dump(itemdef)) + end + + -- Flowing liquid uses param2 + if itemdef.type == "node" and itemdef.liquidtype == "flowing" then + itemdef.paramtype2 = "flowingliquid" + end + + -- BEGIN Legacy stuff + if itemdef.cookresult_itemstring ~= nil and itemdef.cookresult_itemstring ~= "" then + core.register_craft({ + type="cooking", + output=itemdef.cookresult_itemstring, + recipe=itemdef.name, + cooktime=itemdef.furnace_cooktime + }) + end + if itemdef.furnace_burntime ~= nil and itemdef.furnace_burntime >= 0 then + core.register_craft({ + type="fuel", + recipe=itemdef.name, + burntime=itemdef.furnace_burntime + }) + end + -- END Legacy stuff + + -- Disable all further modifications + getmetatable(itemdef).__newindex = {} + + --core.log("Registering item: " .. itemdef.name) + core.registered_items[itemdef.name] = itemdef + core.registered_aliases[itemdef.name] = nil + register_item_raw(itemdef) +end + +function core.register_node(name, nodedef) + nodedef.type = "node" + core.register_item(name, nodedef) +end + +function core.register_craftitem(name, craftitemdef) + craftitemdef.type = "craft" + + -- BEGIN Legacy stuff + if craftitemdef.inventory_image == nil and craftitemdef.image ~= nil then + craftitemdef.inventory_image = craftitemdef.image + end + -- END Legacy stuff + + core.register_item(name, craftitemdef) +end + +function core.register_tool(name, tooldef) + tooldef.type = "tool" + tooldef.stack_max = 1 + + -- BEGIN Legacy stuff + if tooldef.inventory_image == nil and tooldef.image ~= nil then + tooldef.inventory_image = tooldef.image + end + if tooldef.tool_capabilities == nil and + (tooldef.full_punch_interval ~= nil or + tooldef.basetime ~= nil or + tooldef.dt_weight ~= nil or + tooldef.dt_crackiness ~= nil or + tooldef.dt_crumbliness ~= nil or + tooldef.dt_cuttability ~= nil or + tooldef.basedurability ~= nil or + tooldef.dd_weight ~= nil or + tooldef.dd_crackiness ~= nil or + tooldef.dd_crumbliness ~= nil or + tooldef.dd_cuttability ~= nil) then + tooldef.tool_capabilities = { + full_punch_interval = tooldef.full_punch_interval, + basetime = tooldef.basetime, + dt_weight = tooldef.dt_weight, + dt_crackiness = tooldef.dt_crackiness, + dt_crumbliness = tooldef.dt_crumbliness, + dt_cuttability = tooldef.dt_cuttability, + basedurability = tooldef.basedurability, + dd_weight = tooldef.dd_weight, + dd_crackiness = tooldef.dd_crackiness, + dd_crumbliness = tooldef.dd_crumbliness, + dd_cuttability = tooldef.dd_cuttability, + } + end + -- END Legacy stuff + + core.register_item(name, tooldef) +end + +function core.register_alias(name, convert_to) + if forbidden_item_names[name] then + error("Unable to register alias: Name is forbidden: " .. name) + end + if core.registered_items[name] ~= nil then + core.log("WARNING: Not registering alias, item with same name" .. + " is already defined: " .. name .. " -> " .. convert_to) + else + --core.log("Registering alias: " .. name .. " -> " .. convert_to) + core.registered_aliases[name] = convert_to + register_alias_raw(name, convert_to) + end +end + +function core.on_craft(itemstack, player, old_craft_list, craft_inv) + for _, func in ipairs(core.registered_on_crafts) do + itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack + end + return itemstack +end + +function core.craft_predict(itemstack, player, old_craft_list, craft_inv) + for _, func in ipairs(core.registered_craft_predicts) do + itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack + end + return itemstack +end + +-- Alias the forbidden item names to "" so they can't be +-- created via itemstrings (e.g. /give) +local name +for name in pairs(forbidden_item_names) do + core.registered_aliases[name] = "" + register_alias_raw(name, "") +end + + +-- Deprecated: +-- Aliases for core.register_alias (how ironic...) +--core.alias_node = core.register_alias +--core.alias_tool = core.register_alias +--core.alias_craftitem = core.register_alias + +-- +-- Built-in node definitions. Also defined in C. +-- + +core.register_item(":unknown", { + type = "none", + description = "Unknown Item", + inventory_image = "unknown_item.png", + on_place = core.item_place, + on_drop = core.item_drop, + groups = {not_in_creative_inventory=1}, + diggable = true, +}) + +core.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}, +}) + +core.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) +core.register_item(":", { + type = "none", + groups = {not_in_creative_inventory=1}, +}) + + +function core.override_item(name, redefinition) + if redefinition.name ~= nil then + error("Attempt to redefine name of "..name.." to "..dump(redefinition.name), 2) + end + if redefinition.type ~= nil then + error("Attempt to redefine type of "..name.." to "..dump(redefinition.type), 2) + end + local item = core.registered_items[name] + if not item then + error("Attempt to override non-existent item "..name, 2) + end + for k, v in pairs(redefinition) do + rawset(item, k, v) + end + register_item_raw(item) +end + + +function core.run_callbacks(callbacks, mode, ...) + assert(type(callbacks) == "table") + local cb_len = #callbacks + if cb_len == 0 then + if mode == 2 or mode == 3 then + return true + elseif mode == 4 or mode == 5 then + return false + end + end + local ret = nil + for i = 1, cb_len do + local cb_ret = callbacks[i](...) + + if mode == 0 and i == 1 then + ret = cb_ret + elseif mode == 1 and i == cb_len then + ret = cb_ret + elseif mode == 2 then + if not cb_ret or i == 1 then + ret = cb_ret + end + elseif mode == 3 then + if cb_ret then + return cb_ret + end + ret = cb_ret + elseif mode == 4 then + if (cb_ret and not ret) or i == 1 then + ret = cb_ret + end + elseif mode == 5 and cb_ret then + return cb_ret + end + end + return ret +end + +-- +-- Callback registration +-- + +local function make_registration() + local t = {} + local registerfunc = function(func) 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 + +local function make_registration_wrap(reg_fn_name, clear_fn_name) + local list = {} + + local orig_reg_fn = core[reg_fn_name] + core[reg_fn_name] = function(def) + local retval = orig_reg_fn(def) + if retval ~= nil then + if def.name ~= nil then + list[def.name] = def + else + list[retval] = def + end + end + return retval + end + + local orig_clear_fn = core[clear_fn_name] + core[clear_fn_name] = function() + list = {} + return orig_clear_fn() + end + + return list +end + +core.registered_biomes = make_registration_wrap("register_biome", "clear_registered_biomes") +core.registered_ores = make_registration_wrap("register_ore", "clear_registered_ores") +core.registered_decorations = make_registration_wrap("register_decoration", "clear_registered_decorations") + +core.registered_on_chat_messages, core.register_on_chat_message = make_registration() +core.registered_globalsteps, core.register_globalstep = make_registration() +core.registered_playerevents, core.register_playerevent = make_registration() +core.registered_on_shutdown, core.register_on_shutdown = make_registration() +core.registered_on_punchnodes, core.register_on_punchnode = make_registration() +core.registered_on_placenodes, core.register_on_placenode = make_registration() +core.registered_on_dignodes, core.register_on_dignode = make_registration() +core.registered_on_generateds, core.register_on_generated = make_registration() +core.registered_on_newplayers, core.register_on_newplayer = make_registration() +core.registered_on_dieplayers, core.register_on_dieplayer = make_registration() +core.registered_on_respawnplayers, core.register_on_respawnplayer = make_registration() +core.registered_on_prejoinplayers, core.register_on_prejoinplayer = make_registration() +core.registered_on_joinplayers, core.register_on_joinplayer = make_registration() +core.registered_on_leaveplayers, core.register_on_leaveplayer = make_registration() +core.registered_on_player_receive_fields, core.register_on_player_receive_fields = make_registration_reverse() +core.registered_on_cheats, core.register_on_cheat = make_registration() +core.registered_on_crafts, core.register_on_craft = make_registration() +core.registered_craft_predicts, core.register_craft_predict = make_registration() +core.registered_on_protection_violation, core.register_on_protection_violation = make_registration() +core.registered_on_item_eats, core.register_on_item_eat = make_registration() + +-- +-- Compatibility for on_mapgen_init() +-- + +core.register_on_mapgen_init = function(func) func(core.get_mapgen_params()) end + diff --git a/builtin/game/statbars.lua b/builtin/game/statbars.lua new file mode 100644 index 0000000..61a8b90 --- /dev/null +++ b/builtin/game/statbars.lua @@ -0,0 +1,165 @@ + +local health_bar_definition = +{ + hud_elem_type = "statbar", + position = { x=0.5, y=1 }, + text = "heart.png", + number = 20, + direction = 0, + size = { x=24, y=24 }, + offset = { x=(-10*24)-25, y=-(48+24+16)}, +} + +local breath_bar_definition = +{ + hud_elem_type = "statbar", + position = { x=0.5, y=1 }, + text = "bubble.png", + number = 20, + direction = 0, + size = { x=24, y=24 }, + offset = {x=25,y=-(48+24+16)}, +} + +local hud_ids = {} + +local function initialize_builtin_statbars(player) + + if not player:is_player() then + return + end + + local name = player:get_player_name() + + if name == "" then + return + end + + if (hud_ids[name] == nil) then + hud_ids[name] = {} + -- flags are not transmitted to client on connect, we need to make sure + -- our current flags are transmitted by sending them actively + player:hud_set_flags(player:hud_get_flags()) + end + + if player:hud_get_flags().healthbar and + core.is_yes(core.setting_get("enable_damage")) then + if hud_ids[name].id_healthbar == nil then + health_bar_definition.number = player:get_hp() + hud_ids[name].id_healthbar = player:hud_add(health_bar_definition) + end + else + if hud_ids[name].id_healthbar ~= nil then + player:hud_remove(hud_ids[name].id_healthbar) + hud_ids[name].id_healthbar = nil + end + end + + if (player:get_breath() < 11) then + if player:hud_get_flags().breathbar and + core.is_yes(core.setting_get("enable_damage")) then + if hud_ids[name].id_breathbar == nil then + hud_ids[name].id_breathbar = player:hud_add(breath_bar_definition) + end + else + if hud_ids[name].id_breathbar ~= nil then + player:hud_remove(hud_ids[name].id_breathbar) + hud_ids[name].id_breathbar = nil + end + end + elseif hud_ids[name].id_breathbar ~= nil then + player:hud_remove(hud_ids[name].id_breathbar) + hud_ids[name].id_breathbar = nil + end +end + +local function cleanup_builtin_statbars(player) + + if not player:is_player() then + return + end + + local name = player:get_player_name() + + if name == "" then + return + end + + hud_ids[name] = nil +end + +local function player_event_handler(player,eventname) + assert(player:is_player()) + + local name = player:get_player_name() + + if name == "" then + return + end + + if eventname == "health_changed" then + initialize_builtin_statbars(player) + + if hud_ids[name].id_healthbar ~= nil then + player:hud_change(hud_ids[name].id_healthbar,"number",player:get_hp()) + return true + end + end + + if eventname == "breath_changed" then + initialize_builtin_statbars(player) + + if hud_ids[name].id_breathbar ~= nil then + player:hud_change(hud_ids[name].id_breathbar,"number",player:get_breath()*2) + return true + end + end + + if eventname == "hud_changed" then + initialize_builtin_statbars(player) + return true + end + + return false +end + +function core.hud_replace_builtin(name, definition) + + if definition == nil or + type(definition) ~= "table" or + definition.hud_elem_type ~= "statbar" then + return false + end + + if name == "health" then + health_bar_definition = definition + + for name,ids in pairs(hud_ids) do + local player = core.get_player_by_name(name) + if player and hud_ids[name].id_healthbar then + player:hud_remove(hud_ids[name].id_healthbar) + initialize_builtin_statbars(player) + end + end + return true + end + + if name == "breath" then + breath_bar_definition = definition + + for name,ids in pairs(hud_ids) do + local player = core.get_player_by_name(name) + if player and hud_ids[name].id_breathbar then + player:hud_remove(hud_ids[name].id_breathbar) + initialize_builtin_statbars(player) + end + end + return true + end + + return false +end + +core.register_on_joinplayer(initialize_builtin_statbars) +core.register_on_leaveplayer(cleanup_builtin_statbars) +core.register_playerevent(player_event_handler) diff --git a/builtin/game/static_spawn.lua b/builtin/game/static_spawn.lua new file mode 100644 index 0000000..492ab6c --- /dev/null +++ b/builtin/game/static_spawn.lua @@ -0,0 +1,33 @@ +-- Minetest: builtin/static_spawn.lua + +local function warn_invalid_static_spawnpoint() + if core.setting_get("static_spawnpoint") and + not core.setting_get_pos("static_spawnpoint") then + core.log('error', "The static_spawnpoint setting is invalid: \"".. + core.setting_get("static_spawnpoint").."\"") + end +end + +warn_invalid_static_spawnpoint() + +local function put_player_in_spawn(obj) + warn_invalid_static_spawnpoint() + local static_spawnpoint = core.setting_get_pos("static_spawnpoint") + if not static_spawnpoint then + return false + end + core.log('action', "Moving "..obj:get_player_name().. + " to static spawnpoint at ".. + core.pos_to_string(static_spawnpoint)) + obj:setpos(static_spawnpoint) + return true +end + +core.register_on_newplayer(function(obj) + put_player_in_spawn(obj) +end) + +core.register_on_respawnplayer(function(obj) + return put_player_in_spawn(obj) +end) + diff --git a/builtin/game/voxelarea.lua b/builtin/game/voxelarea.lua new file mode 100644 index 0000000..6d926c9 --- /dev/null +++ b/builtin/game/voxelarea.lua @@ -0,0 +1,109 @@ +VoxelArea = { + MinEdge = {x=1, y=1, z=1}, + MaxEdge = {x=0, y=0, z=0}, + ystride = 0, + zstride = 0, +} + +function VoxelArea:new(o) + o = o or {} + setmetatable(o, self) + self.__index = self + + local e = o:getExtent() + o.ystride = e.x + o.zstride = e.x * e.y + + return o +end + +function VoxelArea:getExtent() + local MaxEdge, MinEdge = self.MaxEdge, self.MinEdge + return { + x = MaxEdge.x - MinEdge.x + 1, + y = MaxEdge.y - MinEdge.y + 1, + z = MaxEdge.z - MinEdge.z + 1, + } +end + +function VoxelArea:getVolume() + local e = self:getExtent() + return e.x * e.y * e.z +end + +function VoxelArea:index(x, y, z) + local MinEdge = self.MinEdge + local i = (z - MinEdge.z) * self.zstride + + (y - MinEdge.y) * self.ystride + + (x - MinEdge.x) + 1 + return math.floor(i) +end + +function VoxelArea:indexp(p) + local MinEdge = self.MinEdge + local i = (p.z - MinEdge.z) * self.zstride + + (p.y - MinEdge.y) * self.ystride + + (p.x - MinEdge.x) + 1 + return math.floor(i) +end + +function VoxelArea:position(i) + local p = {} + local MinEdge = self.MinEdge + + i = i - 1 + + p.z = math.floor(i / self.zstride) + MinEdge.z + i = i % self.zstride + + p.y = math.floor(i / self.ystride) + MinEdge.y + i = i % self.ystride + + p.x = math.floor(i) + MinEdge.x + + return p +end + +function VoxelArea:contains(x, y, z) + local MaxEdge, MinEdge = self.MaxEdge, self.MinEdge + return (x >= MinEdge.x) and (x <= MaxEdge.x) and + (y >= MinEdge.y) and (y <= MaxEdge.y) and + (z >= MinEdge.z) and (z <= MaxEdge.z) +end + +function VoxelArea:containsp(p) + local MaxEdge, MinEdge = self.MaxEdge, self.MinEdge + return (p.x >= MinEdge.x) and (p.x <= MaxEdge.x) and + (p.y >= MinEdge.y) and (p.y <= MaxEdge.y) and + (p.z >= MinEdge.z) and (p.z <= MaxEdge.z) +end + +function VoxelArea:containsi(i) + return (i >= 1) and (i <= self:getVolume()) +end + +function VoxelArea:iter(minx, miny, minz, maxx, maxy, maxz) + local i = self:index(minx, miny, minz) - 1 + local 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/builtin/init.lua b/builtin/init.lua new file mode 100644 index 0000000..095771d --- /dev/null +++ b/builtin/init.lua @@ -0,0 +1,38 @@ +-- +-- 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 = core.debug +math.randomseed(os.time()) +os.setlocale("C", "numeric") +minetest = core + +-- Load other files +local scriptdir = core.get_builtin_path()..DIR_DELIM +local gamepath = scriptdir.."game"..DIR_DELIM +local commonpath = scriptdir.."common"..DIR_DELIM +local asyncpath = scriptdir.."async"..DIR_DELIM + +dofile(commonpath.."strict.lua") +dofile(commonpath.."serialize.lua") +dofile(commonpath.."misc_helpers.lua") + +if INIT == "game" then + dofile(gamepath.."init.lua") +elseif INIT == "mainmenu" then + local mainmenuscript = core.setting_get("main_menu_script") + if mainmenuscript ~= nil and mainmenuscript ~= "" then + dofile(mainmenuscript) + else + dofile(core.get_mainmenu_path()..DIR_DELIM.."init.lua") + end +elseif INIT == "async" then + dofile(asyncpath.."init.lua") +else + error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT))) +end + diff --git a/builtin/mainmenu/common.lua b/builtin/mainmenu/common.lua new file mode 100644 index 0000000..f32d77f --- /dev/null +++ b/builtin/mainmenu/common.lua @@ -0,0 +1,277 @@ +--Minetest +--Copyright (C) 2014 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +-------------------------------------------------------------------------------- +-- Global menu data +-------------------------------------------------------------------------------- +menudata = {} + +-------------------------------------------------------------------------------- +-- Local cached values +-------------------------------------------------------------------------------- +local min_supp_proto = core.get_min_supp_proto() +local max_supp_proto = core.get_max_supp_proto() + +-------------------------------------------------------------------------------- +-- Menu helper functions +-------------------------------------------------------------------------------- + +-------------------------------------------------------------------------------- +local function render_client_count(n) + if n > 99 then + return '99+' + elseif n >= 0 then + return tostring(n) + else + return '?' + end +end + +-------------------------------------------------------------------------------- +function image_column(tooltip, flagname) + return "image," .. + "tooltip=" .. core.formspec_escape(tooltip) .. "," .. + "0=" .. core.formspec_escape(defaulttexturedir .. "blank.png") .. "," .. + "1=" .. core.formspec_escape(defaulttexturedir .. "server_flags_" .. flagname .. ".png") +end + +-------------------------------------------------------------------------------- +function order_favorite_list(list) + local res = {} + --orders the favorite list after support + for i=1,#list,1 do + local fav = list[i] + if is_server_protocol_compat(fav.proto_min, fav.proto_max) then + table.insert(res, fav) + end + end + for i=1,#list,1 do + local fav = list[i] + if not is_server_protocol_compat(fav.proto_min, fav.proto_max) then + table.insert(res, fav) + end + end + return res +end + +-------------------------------------------------------------------------------- +function render_favorite(spec,render_details) + local text = "" + + if spec.name ~= nil then + text = text .. core.formspec_escape(spec.name:trim()) + +-- if spec.description ~= nil and +-- core.formspec_escape(spec.description):trim() ~= "" then +-- text = text .. " (" .. core.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 = "" + local grey_out = not is_server_protocol_compat(spec.proto_max, spec.proto_min) + + if spec.clients ~= nil and spec.clients_max ~= nil then + local clients_color = '' + local clients_percent = 100 * spec.clients / spec.clients_max + + -- Choose a color depending on how many clients are connected + -- (relatively to clients_max) + if spec.clients == 0 then + clients_color = '' -- 0 players: default/white + elseif spec.clients == spec.clients_max then + clients_color = '#dd5b5b' -- full server: red (darker) + elseif clients_percent <= 60 then + clients_color = '#a1e587' -- 0-60%: green + elseif clients_percent <= 90 then + clients_color = '#ffdc97' -- 60-90%: yellow + else + clients_color = '#ffba97' -- 90-100%: orange + end + + if grey_out then + clients_color = '#aaaaaa' + end + + details = details .. + clients_color .. ',' .. + render_client_count(spec.clients) .. ',' .. + '/,' .. + render_client_count(spec.clients_max) .. ',' + elseif grey_out then + details = details .. '#aaaaaa,?,/,?,' + else + details = details .. ',?,/,?,' + end + + if spec.creative then + details = details .. "1," + else + details = details .. "0," + end + + if spec.damage then + details = details .. "1," + else + details = details .. "0," + end + + if spec.pvp then + details = details .. "1," + else + details = details .. "0," + end + + return details .. (grey_out and '#aaaaaa,' or ',') .. text +end + +-------------------------------------------------------------------------------- +os.tempfolder = function() + if core.setting_get("TMPFolder") then + return core.setting_get("TMPFolder") .. DIR_DELIM .. "MT_" .. math.random(0,10000) + end + + local filetocheck = os.tmpname() + os.remove(filetocheck) + + local randname = "MTTempModFolder_" .. math.random(0,10000) + if DIR_DELIM == "\\" then + local tempfolder = os.getenv("TEMP") + return tempfolder .. filetocheck + else + local backstring = filetocheck:reverse() + return filetocheck:sub(0,filetocheck:len()-backstring:find(DIR_DELIM)+1) ..randname + end + +end + +-------------------------------------------------------------------------------- +function menu_render_worldlist() + local retval = "" + + local current_worldlist = menudata.worldlist:get_list() + + for i,v in ipairs(current_worldlist) do + if retval ~= "" then + retval = retval .."," + end + + retval = retval .. core.formspec_escape(v.name) .. + " \\[" .. core.formspec_escape(v.gameid) .. "\\]" + end + + return retval +end + +-------------------------------------------------------------------------------- +function menu_handle_key_up_down(fields,textlist,settingname) + + if fields["key_up"] then + local oldidx = core.get_textlist_index(textlist) + + if oldidx ~= nil and oldidx > 1 then + local newidx = oldidx -1 + core.setting_set(settingname, + menudata.worldlist:get_raw_index(newidx)) + end + return true + end + + if fields["key_down"] then + local oldidx = core.get_textlist_index(textlist) + + if oldidx ~= nil and oldidx < menudata.worldlist:size() then + local newidx = oldidx + 1 + core.setting_set(settingname, + menudata.worldlist:get_raw_index(newidx)) + end + + return true + end + + return false +end + +-------------------------------------------------------------------------------- +function asyncOnlineFavourites() + + menudata.favorites = {} + core.handle_async( + function(param) + return core.get_favorites("online") + end, + nil, + function(result) + if core.setting_getbool("public_serverlist") then + menudata.favorites = order_favorite_list(result) + core.event_handler("Refresh") + end + end + ) +end + +-------------------------------------------------------------------------------- +function text2textlist(xpos,ypos,width,height,tl_name,textlen,text,transparency) + local textlines = core.splittext(text,textlen) + + local retval = "textlist[" .. xpos .. "," .. ypos .. ";" + .. width .. "," .. height .. ";" + .. tl_name .. ";" + + for i=1, #textlines, 1 do + textlines[i] = textlines[i]:gsub("\r","") + retval = retval .. core.formspec_escape(textlines[i]) .. "," + end + + retval = retval .. ";0;" + + if transparency then + retval = retval .. "true" + end + + retval = retval .. "]" + + return retval +end + +-------------------------------------------------------------------------------- +function is_server_protocol_compat(proto_min, proto_max) + return not ((min_supp_proto > (proto_max or 24)) or (max_supp_proto < (proto_min or 13))) +end +-------------------------------------------------------------------------------- +function is_server_protocol_compat_or_error(proto_min, proto_max) + if not is_server_protocol_compat(proto_min, proto_max) then + gamedata.errormessage = fgettext_ne("Protocol version mismatch, server " .. + ((proto_min ~= proto_max) and "supports protocols between $1 and $2" or "enforces protocol version $1") .. + ", we " .. + ((min_supp_proto ~= max_supp_proto) and "support protocols between version $3 and $4." or "only support protocol version $3"), + proto_min or 13, proto_max or 24, min_supp_proto, max_supp_proto) + return false + end + + return true +end diff --git a/builtin/mainmenu/dlg_config_world.lua b/builtin/mainmenu/dlg_config_world.lua new file mode 100644 index 0000000..4d13fae --- /dev/null +++ b/builtin/mainmenu/dlg_config_world.lua @@ -0,0 +1,311 @@ +--Minetest +--Copyright (C) 2013 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +-------------------------------------------------------------------------------- +local function modname_valid(name) + return not name:find("[^a-z0-9_]") +end + +local function get_formspec(data) + + local mod = data.list:get_list()[data.selected_mod] + + local retval = + "size[11,6.5,true]" .. + "label[0.5,-0.25;" .. fgettext("World:") .. "]" .. + "label[1.75,-0.25;" .. data.worldspec.name .. "]" + + if data.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 data.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 = data.list:get_raw_list() + + local all_enabled = true + for j=1,#rawlist,1 do + if rawlist[j].modpack == mod.name and + rawlist[j].enabled ~= true then + all_enabled = false + break + end + end + + if all_enabled == false then + retval = retval .. "button[5.5,-0.125;2,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(data.list) + retval = retval .. ";" .. data.selected_mod .."]" + + return retval +end + +local function enable_mod(this, toset) + local mod = this.data.list:get_list()[this.data.selected_mod] + + if mod.typ == "game_mod" then + -- game mods can't be enabled or disabled + elseif not mod.is_modpack then + if toset == nil then + mod.enabled = not mod.enabled + else + mod.enabled = toset + end + else + local list = this.data.list:get_raw_list() + for i=1,#list,1 do + if list[i].modpack == mod.name then + if toset == nil then + toset = not list[i].enabled + end + list[i].enabled = toset + end + end + end +end + + +local function handle_buttons(this, fields) + + if fields["world_config_modlist"] ~= nil then + local event = core.explode_textlist_event(fields["world_config_modlist"]) + this.data.selected_mod = event.index + core.setting_set("world_config_selected_mod", event.index) + + if event.type == "DCL" then + enable_mod(this) + end + + return true + end + + if fields["key_enter"] ~= nil then + enable_mod(this) + return true + end + + if fields["cb_mod_enable"] ~= nil then + local toset = core.is_yes(fields["cb_mod_enable"]) + enable_mod(this,toset) + return true + end + + if fields["btn_mp_enable"] ~= nil or + fields["btn_mp_disable"] then + local toset = (fields["btn_mp_enable"] ~= nil) + enable_mod(this,toset) + return true + end + + if fields["cb_hide_gamemods"] ~= nil or + fields["cb_hide_mpcontent"] ~= nil then + local current = this.data.list:get_filtercriteria() + + if current == nil then + current = {} + end + + if fields["cb_hide_gamemods"] ~= nil then + if core.is_yes(fields["cb_hide_gamemods"]) then + current.hide_game = true + this.data.hide_gamemods = true + core.setting_set("world_config_hide_gamemods", "true") + else + current.hide_game = false + this.data.hide_gamemods = false + core.setting_set("world_config_hide_gamemods", "false") + end + end + + if fields["cb_hide_mpcontent"] ~= nil then + if core.is_yes(fields["cb_hide_mpcontent"]) then + current.hide_modpackcontents = true + this.data.hide_modpackcontents = true + core.setting_set("world_config_hide_modpackcontents", "true") + else + current.hide_modpackcontents = false + this.data.hide_modpackcontents = false + core.setting_set("world_config_hide_modpackcontents", "false") + end + end + + this.data.list:set_filtercriteria(current) + return true + end + + if fields["btn_config_world_save"] then + + local filename = this.data.worldspec.path .. + DIR_DELIM .. "world.mt" + + local worldfile = Settings(filename) + local mods = worldfile:to_table() + + local rawlist = this.data.list:get_raw_list() + + local i,mod + for i,mod in ipairs(rawlist) do + if not mod.is_modpack and + mod.typ ~= "game_mod" then + if modname_valid(mod.name) then + worldfile:set("load_mod_"..mod.name, tostring(mod.enabled)) + else + if mod.enabled then + gamedata.errormessage = fgettext_ne("Failed to enable mod \"$1\" as it contains disallowed characters. Only chararacters [a-z0-9_] are allowed.", mod.name) + end + end + mods["load_mod_"..mod.name] = nil + end + end + + -- Remove mods that are not present anymore + for key,value in pairs(mods) do + if key:sub(1,9) == "load_mod_" then + worldfile:remove(key) + end + end + + if not worldfile:write() then + core.log("error", "Failed to write world config file") + end + + this:delete() + return true + end + + if fields["btn_config_world_cancel"] then + this:delete() + return true + end + + if fields["btn_all_mods"] then + local list = this.data.list:get_raw_list() + + 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 + return true + end + + return false +end + +function create_configure_world_dlg(worldidx) + + local dlg = dialog_create("sp_config_world", + get_formspec, + handle_buttons, + nil) + + dlg.data.hide_gamemods = core.setting_getbool("world_config_hide_gamemods") + dlg.data.hide_modpackcontents = core.setting_getbool("world_config_hide_modpackcontents") + dlg.data.selected_mod = tonumber(core.setting_get("world_config_selected_mod")) + if dlg.data.selected_mod == nil then + dlg.data.selected_mod = 0 + end + + dlg.data.worldspec = core.get_worlds()[worldidx] + if dlg.data.worldspec == nil then dlg:delete() return nil end + + dlg.data.worldconfig = modmgr.get_worldconfig(dlg.data.worldspec.path) + + if dlg.data.worldconfig == nil or dlg.data.worldconfig.id == nil or + dlg.data.worldconfig.id == "" then + + dlg:delete() + return nil + end + + dlg.data.list = filterlist.create( + modmgr.preparemodlist, --refresh + modmgr.comparemod, --compare + function(element,uid) --uid match + if element.name == uid then + return true + end + end, + function(element,criteria) + if criteria.hide_game and + element.typ == "game_mod" then + return false + end + + if criteria.hide_modpackcontents and + element.modpack ~= nil then + return false + end + return true + end, --filter + { worldpath= dlg.data.worldspec.path, + gameid = dlg.data.worldspec.gameid } + ) + + + if dlg.data.selected_mod > dlg.data.list:size() then + dlg.data.selected_mod = 0 + end + + dlg.data.list:set_filtercriteria( + { + hide_game=dlg.data.hide_gamemods, + hide_modpackcontents= dlg.data.hide_modpackcontents + }) + dlg.data.list:add_sort_mechanism("alphabetic", sort_mod_list) + dlg.data.list:set_sortmode("alphabetic") + + return dlg +end diff --git a/builtin/mainmenu/dlg_create_world.lua b/builtin/mainmenu/dlg_create_world.lua new file mode 100644 index 0000000..b42d119 --- /dev/null +++ b/builtin/mainmenu/dlg_create_world.lua @@ -0,0 +1,143 @@ +--Minetest +--Copyright (C) 2014 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +local function create_world_formspec(dialogdata) + local mapgens = core.get_mapgen_names() + + local current_seed = core.setting_get("fixed_map_seed") or "" + local current_mg = core.setting_get("mg_name") + + local mglist = "" + local selindex = 1 + local i = 1 + for k,v in pairs(mapgens) do + if current_mg == v then + selindex = i + end + i = i + 1 + mglist = mglist .. v .. "," + end + mglist = mglist:sub(1, -2) + + local gameid = core.setting_get("menu_last_game") + + local game, gameidx = nil , 0 + if gameid ~= nil then + game, gameidx = gamemgr.find_by_gameid(gameid) + + if gameidx == nil then + gameidx = 0 + end + end + + current_seed = core.formspec_escape(current_seed) + local retval = + "size[12,6,true]" .. + "label[2,0;" .. fgettext("World name") .. "]".. + "field[4.5,0.4;6,0.5;te_world_name;;]" .. + + "label[2,1;" .. fgettext("Seed") .. "]".. + "field[4.5,1.4;6,0.5;te_seed;;".. current_seed .. "]" .. + + "label[2,2;" .. fgettext("Mapgen") .. "]".. + "dropdown[4.2,2;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]" .. + + "label[2,3;" .. fgettext("Game") .. "]".. + "textlist[4.2,3;5.8,2.3;games;" .. gamemgr.gamelist() .. + ";" .. gameidx .. ";true]" .. + + "button[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") .. "]" + + if #gamemgr.games == 0 then + retval = retval .. "box[2,4;8,1;#ff8800]label[2.25,4;" .. + fgettext("You have no subgames installed.") .. "]label[2.25,4.4;" .. + fgettext("Download one from minetest.net") .. "]" + elseif #gamemgr.games == 1 and gamemgr.games[1].id == "minimal" then + retval = retval .. "box[1.75,4;8.7,1;#ff8800]label[2,4;" .. + fgettext("Warning: The minimal development test is meant for developers.") .. "]label[2,4.4;" .. + fgettext("Download a subgame, such as minetest_game, from minetest.net") .. "]" + end + + return retval + +end + +local function create_world_buttonhandler(this, fields) + + if fields["world_create_confirm"] or + fields["key_enter"] then + + local worldname = fields["te_world_name"] + local gameindex = core.get_textlist_index("games") + + if gameindex ~= nil and + worldname ~= "" then + + local message = nil + + core.setting_set("fixed_map_seed", fields["te_seed"]) + + if not menudata.worldlist:uid_exists_raw(worldname) then + core.setting_set("mg_name",fields["dd_mapgen"]) + message = core.create_world(worldname,gameindex) + else + message = fgettext("A world named \"$1\" already exists", worldname) + end + + if message ~= nil then + gamedata.errormessage = message + else + core.setting_set("menu_last_game",gamemgr.games[gameindex].id) + if this.data.update_worldlist_filter then + menudata.worldlist:set_filtercriteria(gamemgr.games[gameindex].id) + mm_texture.update("singleplayer", gamemgr.games[gameindex].id) + end + menudata.worldlist:refresh() + core.setting_set("mainmenu_last_selected_world", + menudata.worldlist:raw_index_by_uid(worldname)) + end + else + gamedata.errormessage = + fgettext("No worldname given or no game selected") + end + this:delete() + return true + end + + if fields["games"] then + return true + end + + if fields["world_create_cancel"] then + this:delete() + return true + end + + return false +end + + +function create_create_world_dlg(update_worldlistfilter) + local retval = dialog_create("sp_create_world", + create_world_formspec, + create_world_buttonhandler, + nil) + retval.update_worldlist_filter = update_worldlistfilter + + return retval +end diff --git a/builtin/mainmenu/dlg_delete_mod.lua b/builtin/mainmenu/dlg_delete_mod.lua new file mode 100644 index 0000000..6e00b80 --- /dev/null +++ b/builtin/mainmenu/dlg_delete_mod.lua @@ -0,0 +1,68 @@ +--Minetest +--Copyright (C) 2014 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +-------------------------------------------------------------------------------- + +local function delete_mod_formspec(dialogdata) + + dialogdata.mod = modmgr.global_mods:get_list()[dialogdata.selected] + + local retval = + "size[12.4,5,true]" .. + "field[1.75,1;10,3;;" .. fgettext("Are you sure you want to delete \"$1\"?", dialogdata.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 + +-------------------------------------------------------------------------------- +local function delete_mod_buttonhandler(this, fields) + if fields["dlg_delete_mod_confirm"] ~= nil then + + if this.data.mod.path ~= nil and + this.data.mod.path ~= "" and + this.data.mod.path ~= core.get_modpath() then + if not core.delete_dir(this.data.mod.path) then + gamedata.errormessage = fgettext("Modmgr: failed to delete \"$1\"", this.data.mod.path) + end + modmgr.refresh_globals() + else + gamedata.errormessage = fgettext("Modmgr: invalid modpath \"$1\"", this.data.mod.path) + end + this:delete() + return true + end + + if fields["dlg_delete_mod_cancel"] then + this:delete() + return true + end + + return false +end + +-------------------------------------------------------------------------------- +function create_delete_mod_dlg(selected_index) + + local retval = dialog_create("dlg_delete_mod", + delete_mod_formspec, + delete_mod_buttonhandler, + nil) + retval.data.selected = selected_index + return retval +end diff --git a/builtin/mainmenu/dlg_delete_world.lua b/builtin/mainmenu/dlg_delete_world.lua new file mode 100644 index 0000000..aa710ef --- /dev/null +++ b/builtin/mainmenu/dlg_delete_world.lua @@ -0,0 +1,64 @@ +--Minetest +--Copyright (C) 2014 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +local function delete_world_formspec(dialogdata) + + local retval = + "size[12,6,true]" .. + "label[2,2;" .. + fgettext("Delete World \"$1\"?", dialogdata.delete_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") .. "]" + return retval +end + +local function delete_world_buttonhandler(this, fields) + if fields["world_delete_confirm"] then + + if this.data.delete_index > 0 and + this.data.delete_index <= #menudata.worldlist:get_raw_list() then + core.delete_world(this.data.delete_index) + menudata.worldlist:refresh() + end + this:delete() + return true + end + + if fields["world_delete_cancel"] then + this:delete() + return true + end + + return false +end + + +function create_delete_world_dlg(name_to_del,index_to_del) + + assert(name_to_del ~= nil and type(name_to_del) == "string" and name_to_del ~= "") + assert(index_to_del ~= nil and type(index_to_del) == "number") + + local retval = dialog_create("delete_world", + delete_world_formspec, + delete_world_buttonhandler, + nil) + retval.data.delete_name = name_to_del + retval.data.delete_index = index_to_del + + return retval +end diff --git a/builtin/mainmenu/dlg_rename_modpack.lua b/builtin/mainmenu/dlg_rename_modpack.lua new file mode 100644 index 0000000..9e25240 --- /dev/null +++ b/builtin/mainmenu/dlg_rename_modpack.lua @@ -0,0 +1,69 @@ +--Minetest +--Copyright (C) 2014 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +-------------------------------------------------------------------------------- + +local function rename_modpack_formspec(dialogdata) + + dialogdata.mod = modmgr.global_mods:get_list()[dialogdata.selected] + + local retval = + "size[12.4,5,true]" .. + "label[1.75,1;".. fgettext("Rename Modpack:") .. "]".. + "field[4.5,1.4;6,0.5;te_modpack_name;;" .. + dialogdata.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 + +-------------------------------------------------------------------------------- +local function rename_modpack_buttonhandler(this, fields) + if fields["dlg_rename_modpack_confirm"] ~= nil then + local oldpath = core.get_modpath() .. DIR_DELIM .. this.data.mod.name + local targetpath = core.get_modpath() .. DIR_DELIM .. fields["te_modpack_name"] + core.copy_dir(oldpath,targetpath,false) + modmgr.refresh_globals() + modmgr.selected_mod = modmgr.global_mods:get_current_index( + modmgr.global_mods:raw_index_by_uid(fields["te_modpack_name"])) + + this:delete() + return true + end + + if fields["dlg_rename_modpack_cancel"] then + this:delete() + return true + end + + return false +end + +-------------------------------------------------------------------------------- +function create_rename_modpack_dlg(selected_index) + + local retval = dialog_create("dlg_delete_mod", + rename_modpack_formspec, + rename_modpack_buttonhandler, + nil) + retval.data.selected = selected_index + return retval +end diff --git a/builtin/mainmenu/gamemgr.lua b/builtin/mainmenu/gamemgr.lua new file mode 100644 index 0000000..b6faa71 --- /dev/null +++ b/builtin/mainmenu/gamemgr.lua @@ -0,0 +1,83 @@ +--Minetest +--Copyright (C) 2013 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +gamemgr = {} + +-------------------------------------------------------------------------------- +function gamemgr.find_by_gameid(gameid) + for i=1,#gamemgr.games,1 do + if gamemgr.games[i].id == gameid then + return gamemgr.games[i], i + end + end + return nil, nil +end + +-------------------------------------------------------------------------------- +function gamemgr.get_game_mods(gamespec, retval) + if gamespec ~= nil and + gamespec.gamemods_path ~= nil and + gamespec.gamemods_path ~= "" then + get_mods(gamespec.gamemods_path, retval) + end +end + +-------------------------------------------------------------------------------- +function gamemgr.get_game_modlist(gamespec) + local retval = "" + local game_mods = {} + gamemgr.get_game_mods(gamespec, game_mods) + for i=1,#game_mods,1 do + if retval ~= "" then + retval = retval.."," + end + retval = retval .. game_mods[i].name + end + return retval +end + +-------------------------------------------------------------------------------- +function gamemgr.get_game(index) + if index > 0 and index <= #gamemgr.games then + return gamemgr.games[index] + end + + return nil +end + +-------------------------------------------------------------------------------- +function gamemgr.update_gamelist() + gamemgr.games = core.get_games() +end + +-------------------------------------------------------------------------------- +function gamemgr.gamelist() + local retval = "" + if #gamemgr.games > 0 then + retval = retval .. gamemgr.games[1].name + + for i=2,#gamemgr.games,1 do + retval = retval .. "," .. gamemgr.games[i].name + end + end + return retval +end + +-------------------------------------------------------------------------------- +-- read initial data +-------------------------------------------------------------------------------- +gamemgr.update_gamelist() diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua new file mode 100644 index 0000000..d008ec8 --- /dev/null +++ b/builtin/mainmenu/init.lua @@ -0,0 +1,167 @@ +--Minetest +--Copyright (C) 2014 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +mt_color_grey = "#AAAAAA" +mt_color_blue = "#0000DD" +mt_color_green = "#00DD00" +mt_color_dark_green = "#003300" + +--for all other colors ask sfan5 to complete his work! + +local menupath = core.get_mainmenu_path() +local basepath = core.get_builtin_path() +defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" .. + DIR_DELIM .. "pack" .. DIR_DELIM + +dofile(basepath .. DIR_DELIM .. "common" .. DIR_DELIM .. "async_event.lua") +dofile(basepath .. DIR_DELIM .. "common" .. DIR_DELIM .. "filterlist.lua") +dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "buttonbar.lua") +dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "dialog.lua") +dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "tabview.lua") +dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "ui.lua") +dofile(menupath .. DIR_DELIM .. "common.lua") +dofile(menupath .. DIR_DELIM .. "gamemgr.lua") +dofile(menupath .. DIR_DELIM .. "modmgr.lua") +dofile(menupath .. DIR_DELIM .. "store.lua") +dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua") +dofile(menupath .. DIR_DELIM .. "tab_credits.lua") +dofile(menupath .. DIR_DELIM .. "tab_mods.lua") +dofile(menupath .. DIR_DELIM .. "tab_settings.lua") +if PLATFORM ~= "Android" then + dofile(menupath .. DIR_DELIM .. "dlg_create_world.lua") + dofile(menupath .. DIR_DELIM .. "dlg_delete_mod.lua") + dofile(menupath .. DIR_DELIM .. "dlg_delete_world.lua") + dofile(menupath .. DIR_DELIM .. "dlg_rename_modpack.lua") + dofile(menupath .. DIR_DELIM .. "tab_multiplayer.lua") + dofile(menupath .. DIR_DELIM .. "tab_server.lua") + dofile(menupath .. DIR_DELIM .. "tab_singleplayer.lua") + dofile(menupath .. DIR_DELIM .. "tab_texturepacks.lua") + dofile(menupath .. DIR_DELIM .. "textures.lua") +else + dofile(menupath .. DIR_DELIM .. "tab_simple_main.lua") +end + +-------------------------------------------------------------------------------- +local function main_event_handler(tabview, event) + if event == "MenuQuit" then + core.close() + end + return true +end + +-------------------------------------------------------------------------------- +local function init_globals() + -- Init gamedata + gamedata.worldindex = 0 + + + if PLATFORM ~= "Android" then + menudata.worldlist = filterlist.create( + core.get_worlds, + compare_worlds, + -- Unique id comparison function + function(element, uid) + return element.name == uid + end, + -- Filter function + function(element, gameid) + return element.gameid == gameid + end + ) + + menudata.worldlist:add_sort_mechanism("alphabetic", sort_worlds_alphabetic) + menudata.worldlist:set_sortmode("alphabetic") + + if not core.setting_get("menu_last_game") then + local default_game = core.setting_get("default_game") or "minetest" + core.setting_set("menu_last_game", default_game ) + end + + mm_texture.init() + else + local world_list = core.get_worlds() + + local found_singleplayerworld = false + + for i,world in pairs(world_list) do + if world.name == "singleplayerworld" then + found_singleplayerworld = true + gamedata.worldindex = i + break + end + end + + if not found_singleplayerworld then + core.create_world("singleplayerworld", 1) + + local world_list = core.get_worlds() + + for i,world in pairs(world_list) do + if world.name == "singleplayerworld" then + gamedata.worldindex = i + break + end + end + end + end + + -- Create main tabview + local tv_main = tabview_create("maintab",{x=12,y=5.2},{x=0,y=0}) + if PLATFORM ~= "Android" then + tv_main:set_autosave_tab(true) + end + if PLATFORM ~= "Android" then + tv_main:add(tab_singleplayer) + tv_main:add(tab_multiplayer) + tv_main:add(tab_server) + else + tv_main:add(tab_simple_main) + end + tv_main:add(tab_settings) + if PLATFORM ~= "Android" then + tv_main:add(tab_texturepacks) + end + tv_main:add(tab_mods) + tv_main:add(tab_credits) + + tv_main:set_global_event_handler(main_event_handler) + if PLATFORM ~= "Android" then + tv_main:set_fixed_size(true) + else + tv_main:set_fixed_size(false) + end + + if not (PLATFORM == "Android") then + tv_main:set_tab(core.setting_get("maintab_LAST")) + end + ui.set_default("maintab") + tv_main:show() + + -- Create modstore ui + if PLATFORM == "Android" then + modstore.init({x=12, y=6}, 3, 2) + else + modstore.init({x=12, y=8}, 4, 3) + end + + ui.update() + + core.sound_play("main_menu", true) +end + +init_globals() + diff --git a/builtin/mainmenu/init_simple.lua b/builtin/mainmenu/init_simple.lua new file mode 100644 index 0000000..c3891d2 --- /dev/null +++ b/builtin/mainmenu/init_simple.lua @@ -0,0 +1,4 @@ +-- helper file to be able to debug the simple menu on PC +-- without messing around with actual menu code! +PLATFORM="Android" +dofile("builtin/mainmenu/init.lua") diff --git a/builtin/mainmenu/modmgr.lua b/builtin/mainmenu/modmgr.lua new file mode 100644 index 0000000..f293868 --- /dev/null +++ b/builtin/mainmenu/modmgr.lua @@ -0,0 +1,564 @@ +--Minetest +--Copyright (C) 2013 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +-------------------------------------------------------------------------------- +function get_mods(path,retval,modpack) + local mods = core.get_dirlist(path, true) + + for i=1, #mods, 1 do + if mods[i]:sub(1,1) ~= "." then + 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 +end + +--modmanager implementation +modmgr = {} + +-------------------------------------------------------------------------------- +function modmgr.extract(modfile) + if modfile.type == "zip" then + local tempfolder = os.tempfolder() + + if tempfolder ~= nil and + tempfolder ~= "" then + core.create_dir(tempfolder) + if core.extract_zip(modfile.name,tempfolder) then + return tempfolder + end + end + end + return nil +end + +------------------------------------------------------------------------------- +function modmgr.getbasefolder(temppath) + + if temppath == nil then + return { + type = "invalid", + path = "" + } + end + + local testfile = io.open(temppath .. DIR_DELIM .. "init.lua","r") + if testfile ~= nil then + testfile:close() + return { + type="mod", + path=temppath + } + end + + testfile = io.open(temppath .. DIR_DELIM .. "modpack.txt","r") + if testfile ~= nil then + testfile:close() + return { + type="modpack", + path=temppath + } + end + + local subdirs = core.get_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.render_modlist(render_list) + local retval = "" + + if render_list == nil then + if modmgr.global_mods == nil then + modmgr.refresh_globals() + end + render_list = modmgr.global_mods + end + + local list = render_list:get_list() + local last_modpack = nil + + for i,v in ipairs(list) do + if retval ~= "" then + retval = retval .."," + end + + local color = "" + + if v.is_modpack then + local rawlist = render_list:get_raw_list() + + local all_enabled = true + for j=1,#rawlist,1 do + if rawlist[j].modpack == list[i].name and + rawlist[j].enabled ~= true then + all_enabled = false + break + end + end + + if all_enabled == false then + color = mt_color_grey + else + color = mt_color_dark_green + end + end + + if v.typ == "game_mod" then + color = mt_color_blue + else + if v.enabled then + color = mt_color_green + end + end + + retval = retval .. color + if v.modpack ~= nil then + retval = retval .. " " + end + retval = retval .. v.name + end + + return retval +end + +-------------------------------------------------------------------------------- +function modmgr.get_dependencies(modfolder) + local toadd = "" + 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] = core.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.installmod(modfilename,basename) + local modfile = modmgr.identify_filetype(modfilename) + local modpath = modmgr.extract(modfile) + + if modpath == nil then + gamedata.errormessage = fgettext("Install Mod: file: \"$1\"", modfile.name) .. + fgettext("\nInstall Mod: unsupported filetype \"$1\" or broken archive", modfile.type) + return + end + + local basefolder = modmgr.getbasefolder(modpath) + + if basefolder.type == "modpack" then + local clean_path = nil + + if basename ~= nil then + clean_path = "mp_" .. basename + end + + if clean_path == nil then + clean_path = get_last_folder(cleanup_path(basefolder.path)) + end + + if clean_path ~= nil then + local targetpath = core.get_modpath() .. DIR_DELIM .. clean_path + if not core.copy_dir(basefolder.path,targetpath) then + gamedata.errormessage = fgettext("Failed to install $1 to $2", basename, targetpath) + end + else + gamedata.errormessage = fgettext("Install Mod: unable to find suitable foldername for modpack $1", modfilename) + end + end + + if basefolder.type == "mod" then + local targetfolder = basename + + if targetfolder == nil then + targetfolder = modmgr.identify_modname(basefolder.path,"init.lua") + end + + --if heuristic failed try to use current foldername + if targetfolder == nil then + targetfolder = get_last_folder(basefolder.path) + end + + if targetfolder ~= nil and modmgr.isValidModname(targetfolder) then + local targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder + core.copy_dir(basefolder.path,targetpath) + else + gamedata.errormessage = fgettext("Install Mod: unable to find real modname for: $1", modfilename) + end + end + + core.delete_dir(modpath) + + modmgr.refresh_globals() + +end + +-------------------------------------------------------------------------------- +function modmgr.preparemodlist(data) + local retval = {} + + local global_mods = {} + local game_mods = {} + + --read global mods + local modpath = core.get_modpath() + + if modpath ~= nil and + modpath ~= "" then + get_mods(modpath,global_mods) + end + + for i=1,#global_mods,1 do + global_mods[i].typ = "global_mod" + 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 and + not retval[i].is_modpack then + element = retval[i] + break + end + end + if element ~= nil then + element.enabled = core.is_yes(value) + else + core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found") + end + end + end + + return retval +end + +-------------------------------------------------------------------------------- +function modmgr.comparemod(elem1,elem2) + if elem1 == nil or elem2 == nil then + return false + end + if elem1.name ~= elem2.name then + return false + end + if elem1.is_modpack ~= elem2.is_modpack then + return false + end + if elem1.typ ~= elem2.typ then + return false + end + if elem1.modpack ~= elem2.modpack then + return false + end + + if elem1.path ~= elem2.path then + return false + end + + return true +end + +-------------------------------------------------------------------------------- +function modmgr.mod_exists(basename) + + if modmgr.global_mods == nil then + modmgr.refresh_globals() + end + + if modmgr.global_mods:raw_index_by_uid(basename) > 0 then + return true + end + + return false +end + +-------------------------------------------------------------------------------- +function modmgr.get_global_mod(idx) + + if modmgr.global_mods == nil then + return nil + end + + if idx == nil or idx < 1 or + idx > modmgr.global_mods:size() then + return nil + end + + return modmgr.global_mods:get_list()[idx] +end + +-------------------------------------------------------------------------------- +function modmgr.refresh_globals() + modmgr.global_mods = filterlist.create( + modmgr.preparemodlist, --refresh + modmgr.comparemod, --compare + function(element,uid) --uid match + if element.name == uid then + return true + end + end, + nil, --filter + {} + ) + modmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list) + modmgr.global_mods:set_sortmode("alphabetic") +end + +-------------------------------------------------------------------------------- +function modmgr.identify_filetype(name) + + if name:sub(-3):lower() == "zip" then + return { + name = name, + type = "zip" + } + end + + if name:sub(-6):lower() == "tar.gz" or + name:sub(-3):lower() == "tgz"then + return { + name = name, + type = "tgz" + } + end + + if name:sub(-6):lower() == "tar.bz2" then + return { + name = name, + type = "tbz" + } + end + + if name:sub(-2):lower() == "7z" then + return { + name = name, + type = "7z" + } + end + + return { + name = name, + type = "ukn" + } +end diff --git a/builtin/mainmenu/store.lua b/builtin/mainmenu/store.lua new file mode 100644 index 0000000..ad86108 --- /dev/null +++ b/builtin/mainmenu/store.lua @@ -0,0 +1,614 @@ +--Minetest +--Copyright (C) 2013 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +-------------------------------------------------------------------------------- + +--modstore implementation +modstore = {} + +-------------------------------------------------------------------------------- +-- @function [parent=#modstore] init +function modstore.init(size, unsortedmods, searchmods) + + modstore.mods_on_unsorted_page = unsortedmods + modstore.mods_on_search_page = searchmods + modstore.modsperpage = modstore.mods_on_unsorted_page + + modstore.basetexturedir = core.get_texturepath() .. DIR_DELIM .. "base" .. + DIR_DELIM .. "pack" .. DIR_DELIM + + modstore.lastmodtitle = "" + modstore.last_search = "" + + modstore.searchlist = filterlist.create( + function() + if modstore.modlist_unsorted ~= nil and + modstore.modlist_unsorted.data ~= nil then + return modstore.modlist_unsorted.data + end + return {} + end, + function(element,modid) + if element.id == modid then + return true + end + return false + end, --compare fct + nil, --uid match fct + function(element,substring) + if substring == nil or + substring == "" then + return false + end + substring = substring:upper() + + if element.title ~= nil and + element.title:upper():find(substring) ~= nil then + return true + end + + if element.details ~= nil and + element.details.author ~= nil and + element.details.author:upper():find(substring) ~= nil then + return true + end + + if element.details ~= nil and + element.details.description ~= nil and + element.details.description:upper():find(substring) ~= nil then + return true + end + return false + end --filter fct + ) + + modstore.current_list = nil + + modstore.tv_store = tabview_create("modstore",size,{x=0,y=0}) + + modstore.tv_store:set_global_event_handler(modstore.handle_events) + + modstore.tv_store:add( + { + name = "unsorted", + caption = fgettext("Unsorted"), + cbf_formspec = modstore.unsorted_tab, + cbf_button_handler = modstore.handle_buttons, + on_change = + function() modstore.modsperpage = modstore.mods_on_unsorted_page end + } + ) + + modstore.tv_store:add( + { + name = "search", + caption = fgettext("Search"), + cbf_formspec = modstore.getsearchpage, + cbf_button_handler = modstore.handle_buttons, + on_change = modstore.activate_search_tab + } + ) +end + +-------------------------------------------------------------------------------- +-- @function [parent=#modstore] nametoindex +function modstore.nametoindex(name) + + for i=1,#modstore.tabnames,1 do + if modstore.tabnames[i] == name then + return i + end + end + + return 1 +end + +-------------------------------------------------------------------------------- +-- @function [parent=#modstore] showdownloading +function modstore.showdownloading(title) + local new_dlg = dialog_create("store_downloading", + function(data) + return "size[6,2]label[0.25,0.75;" .. + fgettext("Downloading $1, please wait...", data.title) .. "]" + end, + function(this,fields) + if fields["btn_hidden_close_download"] ~= nil then + if fields["btn_hidden_close_download"].successfull then + modstore.lastmodentry = fields["btn_hidden_close_download"] + modstore.successfulldialog(this) + else + this.parent:show() + this:delete() + modstore.lastmodtitle = "" + end + + return true + end + + return false + end, + nil) + + new_dlg:set_parent(modstore.tv_store) + modstore.tv_store:hide() + new_dlg.data.title = title + new_dlg:show() +end + +-------------------------------------------------------------------------------- +-- @function [parent=#modstore] successfulldialog +function modstore.successfulldialog(downloading_dlg) + local new_dlg = dialog_create("store_downloading", + function(data) + local retval = "" + retval = retval .. "size[6,2,true]" + if modstore.lastmodentry ~= nil then + retval = retval .. "label[0,0.25;" .. fgettext("Successfully installed:") .. "]" + retval = retval .. "label[3,0.25;" .. modstore.lastmodentry.moddetails.title .. "]" + retval = retval .. "label[0,0.75;" .. fgettext("Shortname:") .. "]" + retval = retval .. "label[3,0.75;" .. core.formspec_escape(modstore.lastmodentry.moddetails.basename) .. "]" + end + retval = retval .. "button[2.2,1.5;1.5,0.5;btn_confirm_mod_successfull;" .. fgettext("Ok") .. "]" + return retval + end, + function(this,fields) + if fields["btn_confirm_mod_successfull"] ~= nil then + this.parent:show() + downloading_dlg:delete() + this:delete() + + return true + end + + return false + end, + nil) + + new_dlg:set_parent(modstore.tv_store) + modstore.tv_store:hide() + new_dlg:show() +end + +-------------------------------------------------------------------------------- +-- @function [parent=#modstore] handle_buttons +function modstore.handle_buttons(parent, fields, name, data) + + if fields["btn_modstore_page_up"] then + if modstore.current_list ~= nil and modstore.current_list.page > 0 then + modstore.current_list.page = modstore.current_list.page - 1 + end + return true + end + + if fields["btn_modstore_page_down"] then + if modstore.current_list ~= nil and + modstore.current_list.page 1 then + local versiony = ypos + 0.05 + retval = retval .. "dropdown[9.1," .. versiony .. ";2.48,0.25;dd_version" .. details.id .. ";" + local versions = "" + for i=1,#details.versions , 1 do + if versions ~= "" then + versions = versions .. "," + end + + versions = versions .. details.versions[i].date:sub(1,10) + end + retval = retval .. versions .. ";1]" + end + + if details.basename then + --install button + local buttony = ypos + 1.2 + retval = retval .."button[9.1," .. buttony .. ";2.5,0.5;btn_install_mod_" .. details.id .. ";" + + if modmgr.mod_exists(details.basename) then + retval = retval .. fgettext("re-Install") .."]" + else + retval = retval .. fgettext("Install") .."]" + end + end + + return retval +end + +-------------------------------------------------------------------------------- +--@function [parent=#modstore] getmodlist +function modstore.getmodlist(list,yoffset) + modstore.current_list = list + + if yoffset == nil then + yoffset = 0 + end + + local sb_y_start = 0.2 + yoffset + local sb_y_end = (modstore.modsperpage * 1.75) + ((modstore.modsperpage-1) * 0.15) + local close_button = "button[4," .. (sb_y_end + 0.3 + yoffset) .. + ";4,0.5;btn_modstore_close;" .. fgettext("Close store") .. "]" + + if #list.data == 0 then + return close_button + end + + local scrollbar = "" + scrollbar = scrollbar .. "label[0.1,".. (sb_y_end + 0.25 + yoffset) ..";" + .. fgettext("Page $1 of $2", list.page+1, list.pagecount) .. "]" + scrollbar = scrollbar .. "box[11.6," .. sb_y_start .. ";0.28," .. sb_y_end .. ";#000000]" + local scrollbarpos = (sb_y_start + 0.5) + + ((sb_y_end -1.6)/(list.pagecount-1)) * list.page + scrollbar = scrollbar .. "box[11.6," ..scrollbarpos .. ";0.28,0.5;#32CD32]" + scrollbar = scrollbar .. "button[11.6," .. (sb_y_start) + .. ";0.5,0.5;btn_modstore_page_up;^]" + scrollbar = scrollbar .. "button[11.6," .. (sb_y_start + sb_y_end - 0.5) + .. ";0.5,0.5;btn_modstore_page_down;v]" + + local retval = "" + + local endmod = (list.page * modstore.modsperpage) + modstore.modsperpage + + if (endmod > #list.data) then + endmod = #list.data + end + + for i=(list.page * modstore.modsperpage) +1, endmod, 1 do + --getmoddetails + local details = list.data[i].details + + if details == nil then + details = {} + details.title = list.data[i].title + details.author = "" + details.rating = -1 + details.description = "" + end + + if details ~= nil then + local screenshot_ypos = + yoffset +(i-1 - (list.page * modstore.modsperpage))*1.9 +0.2 + + retval = retval .. modstore.getshortmodinfo(screenshot_ypos, + list.data[i], + details) + end + end + + return retval .. scrollbar .. close_button +end + +-------------------------------------------------------------------------------- +--@function [parent=#modstore] getsearchpage +function modstore.getsearchpage(tabview, name, tabdata) + local retval = "" + local search = "" + + if modstore.last_search ~= nil then + search = modstore.last_search + end + + retval = retval .. + "button[9.5,0.2;2.5,0.5;btn_modstore_search;".. fgettext("Search") .. "]" .. + "field[0.5,0.5;9,0.5;te_modstore_search;;" .. search .. "]" + + retval = retval .. + modstore.getmodlist( + modstore.currentlist, + 1.75) + + return retval; +end + +-------------------------------------------------------------------------------- +--@function [parent=#modstore] unsorted_tab +function modstore.unsorted_tab() + return modstore.getmodlist(modstore.modlist_unsorted) +end + +-------------------------------------------------------------------------------- +--@function [parent=#modstore] activate_search_tab +function modstore.activate_search_tab(type, old_tab, new_tab) + + if old_tab == new_tab then + return + end + filterlist.set_filtercriteria(modstore.searchlist,modstore.last_search) + filterlist.refresh(modstore.searchlist) + modstore.modsperpage = modstore.mods_on_search_page + modstore.currentlist = { + page = 0, + pagecount = + math.ceil(filterlist.size(modstore.searchlist) / modstore.modsperpage), + data = filterlist.get_list(modstore.searchlist), + } +end + diff --git a/builtin/mainmenu/tab_credits.lua b/builtin/mainmenu/tab_credits.lua new file mode 100644 index 0000000..5611320 --- /dev/null +++ b/builtin/mainmenu/tab_credits.lua @@ -0,0 +1,64 @@ +--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. + +-------------------------------------------------------------------------------- + +tab_credits = { + name = "credits", + caption = fgettext("Credits"), + cbf_formspec = function (tabview, name, tabdata) + return "label[0.5,3.2;Blokel " .. core.get_version() .. "]" .. + "textlist[3.5,-0.25;8.5,5.8;list_credits;" .. + "#FFFF00" .. fgettext("Core Developers") .."," .. + "Preston (Marth) Cammarata (devmarth),".. + "Perttu Ahola (celeron55) ,".. + "Ryan Kwolek (kwolekr) ,".. + "PilzAdam ," .. + "Lisa Milne (darkrose) ,".. + "Maciej Kasatkin (RealBadAngel) ,".. + "sfan5 ,".. + "kahrl ,".. + "sapier,".. + "ShadowNinja ,".. + "Nathanael Courant (Nore/Novatux) ,".. + "BlockMen,".. + "Craig Robbins (Zeno),".. + ",".. + "#FFFF00" .. fgettext("Active Contributors") .. "," .. + "Preston (Marth) Cammarata (devmarth),".. + "TriBlade9 ,".. + "SmallJoker ,".. + "Zefram ,".. + "," .. + "#FFFF00" .. fgettext("Previous Contributors") .. "," .. + "Vanessa Ezekowitz (VanessaE) ,".. + "Jurgen Doser (doserj) ,".. + "Jeija ,".. + "MirceaKitsune ,".. + "dannydark ,".. + "0gb.us <0gb.us@0gb.us>,".. + "proller ,".. + "Ilya Zhuravlev (xyz) ,".. + "Guiseppe Bilotta (Oblomov) ,".. + "Jonathan Neuschafer ,".. + "Nils Dagsson Moskopp (erlehmann) ,".. + "Constantin Wenger (SpeedProg) ,".. + "matttpt ,".. + "JacobF ,".. + ";0;true]" + end + } diff --git a/builtin/mainmenu/tab_mods.lua b/builtin/mainmenu/tab_mods.lua new file mode 100644 index 0000000..901f145 --- /dev/null +++ b/builtin/mainmenu/tab_mods.lua @@ -0,0 +1,169 @@ +--Minetest +--Copyright (C) 2014 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +-------------------------------------------------------------------------------- +local function get_formspec(tabview, name, tabdata) + + if modmgr.global_mods == nil then + modmgr.refresh_globals() + end + + if tabdata.selected_mod == nil then + tabdata.selected_mod = 1 + end + + local retval = + "label[0.05,-0.25;".. fgettext("Installed Mods:") .. "]" .. + "textlist[0,0.25;5.1,4.35;modlist;" .. + modmgr.render_modlist(modmgr.global_mods) .. + ";" .. tabdata.selected_mod .. "]" + + retval = retval .. +-- "label[0.8,4.2;" .. fgettext("Add mod:") .. "]" .. +-- TODO Disabled due to upcoming release 0.4.8 and irrlicht messing up localization +-- "button[0.75,4.85;1.8,0.5;btn_mod_mgr_install_local;".. fgettext("Local install") .. "]" .. + "button[0,4.85;5.25,0.5;btn_modstore;".. fgettext("Online mod repository") .. "]" + + local selected_mod = nil + + if filterlist.size(modmgr.global_mods) >= tabdata.selected_mod then + selected_mod = modmgr.global_mods:get_list()[tabdata.selected_mod] + end + + if selected_mod ~= nil then + local modscreenshot = nil + + --check for screenshot beeing available + local screenshotfilename = selected_mod.path .. DIR_DELIM .. "screenshot.png" + local error = nil + local screenshotfile,error = io.open(screenshotfilename,"r") + if error == nil then + screenshotfile:close() + modscreenshot = screenshotfilename + end + + if modscreenshot == nil then + modscreenshot = defaulttexturedir .. "no_screenshot.png" + end + + retval = retval + .. "image[5.5,0;3,2;" .. core.formspec_escape(modscreenshot) .. "]" + .. "label[8.25,0.6;" .. selected_mod.name .. "]" + + local descriptionlines = nil + error = nil + local descriptionfilename = selected_mod.path .. "description.txt" + local descriptionfile,error = io.open(descriptionfilename,"r") + if error == nil then + local descriptiontext = descriptionfile:read("*all") + + descriptionlines = core.splittext(descriptiontext,42) + descriptionfile:close() + else + descriptionlines = {} + 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 .. core.formspec_escape(descriptionlines[i]) .. "," + end + + + if selected_mod.is_modpack then + retval = retval .. ";0]" .. + "button[10,4.85;2,0.5;btn_mod_mgr_rename_modpack;" .. + fgettext("Rename") .. "]" + retval = retval .. "button[5.5,4.85;4.5,0.5;btn_mod_mgr_delete_mod;" + .. fgettext("Uninstall selected modpack") .. "]" + else + --show dependencies + + retval = retval .. "," .. fgettext("Depends:") .. "," + + local 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 + +-------------------------------------------------------------------------------- +local function handle_buttons(tabview, fields, tabname, tabdata) + if fields["modlist"] ~= nil then + local event = core.explode_textlist_event(fields["modlist"]) + tabdata.selected_mod = event.index + return true + end + + if fields["btn_mod_mgr_install_local"] ~= nil then + core.show_file_open_dialog("mod_mgt_open_dlg",fgettext("Select Mod File:")) + return true + end + + if fields["btn_modstore"] ~= nil then + local modstore_ui = ui.find_by_name("modstore") + if modstore_ui ~= nil then + tabview:hide() + modstore.update_modlist() + modstore_ui:show() + else + print("modstore ui element not found") + end + return true + end + + if fields["btn_mod_mgr_rename_modpack"] ~= nil then + local dlg_renamemp = create_rename_modpack_dlg(tabdata.selected_mod) + dlg_renamemp:set_parent(tabview) + tabview:hide() + dlg_renamemp:show() + return true + end + + if fields["btn_mod_mgr_delete_mod"] ~= nil then + local dlg_delmod = create_delete_mod_dlg(tabdata.selected_mod) + dlg_delmod:set_parent(tabview) + tabview:hide() + dlg_delmod:show() + return true + end + + if fields["mod_mgt_open_dlg_accepted"] ~= nil and + fields["mod_mgt_open_dlg_accepted"] ~= "" then + modmgr.installmod(fields["mod_mgt_open_dlg_accepted"],nil) + return true + end + + return false +end + +-------------------------------------------------------------------------------- +tab_mods = { + name = "mods", + caption = fgettext("Mods"), + cbf_formspec = get_formspec, + cbf_button_handler = handle_buttons, + on_change = gamemgr.update_gamelist +} diff --git a/builtin/mainmenu/tab_multiplayer.lua b/builtin/mainmenu/tab_multiplayer.lua new file mode 100644 index 0000000..5702597 --- /dev/null +++ b/builtin/mainmenu/tab_multiplayer.lua @@ -0,0 +1,261 @@ +--Minetest +--Copyright (C) 2014 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +-------------------------------------------------------------------------------- +local function get_formspec(tabview, name, tabdata) + local render_details = core.is_yes(core.setting_getbool("public_serverlist")) + + local retval = + "label[7.75,-0.15;" .. fgettext("Address / Port :") .. "]" .. + "label[7.75,1.05;" .. fgettext("Name / Password :") .. "]" .. + "field[8,0.75;3.4,0.5;te_address;;" .. + core.formspec_escape(core.setting_get("address")) .. "]" .. + "field[11.25,0.75;1.3,0.5;te_port;;" .. + core.formspec_escape(core.setting_get("remote_port")) .. "]" .. + "checkbox[0,4.85;cb_public_serverlist;" .. fgettext("Public Serverlist") .. ";" .. + dump(core.setting_getbool("public_serverlist")) .. "]" + + if not core.setting_getbool("public_serverlist") then + retval = retval .. + "button[8,4.9;2,0.5;btn_delete_favorite;" .. fgettext("Delete") .. "]" + end + + retval = retval .. + "button[10,4.9;2,0.5;btn_mp_connect;" .. fgettext("Connect") .. "]" .. + "field[8,1.95;2.95,0.5;te_name;;" .. + core.formspec_escape(core.setting_get("name")) .. "]" .. + "pwdfield[10.78,1.95;1.77,0.5;te_pwd;]" .. + "box[7.73,2.35;4.3,2.28;#999999]" .. + "textarea[8.1,2.4;4.26,2.6;;" + + if tabdata.fav_selected ~= nil and + menudata.favorites[tabdata.fav_selected] ~= nil and + menudata.favorites[tabdata.fav_selected].description ~= nil then + retval = retval .. + core.formspec_escape(menudata.favorites[tabdata.fav_selected].description,true) + end + + retval = retval .. + ";]" + + --favourites + if render_details then + retval = retval .. "tablecolumns[" .. + "color,span=3;" .. + "text,align=right;" .. -- clients + "text,align=center,padding=0.25;" .. -- "/" + "text,align=right,padding=0.25;" .. -- clients_max + image_column(fgettext("Creative mode"), "creative") .. ",padding=1;" .. + image_column(fgettext("Damage enabled"), "damage") .. ",padding=0.25;" .. + image_column(fgettext("PvP enabled"), "pvp") .. ",padding=0.25;" .. + "color,span=1;" .. + "text,padding=1]" -- name + else + retval = retval .. "tablecolumns[text]" + end + retval = retval .. + "table[-0.15,-0.1;7.75,5;favourites;" + + if #menudata.favorites > 0 then + retval = retval .. render_favorite(menudata.favorites[1],render_details) + + for i=2,#menudata.favorites,1 do + retval = retval .. "," .. render_favorite(menudata.favorites[i],render_details) + end + end + + if tabdata.fav_selected ~= nil then + retval = retval .. ";" .. tabdata.fav_selected .. "]" + else + retval = retval .. ";0]" + end + + return retval +end + +-------------------------------------------------------------------------------- +local function main_button_handler(tabview, fields, name, tabdata) + if fields["te_name"] ~= nil then + gamedata.playername = fields["te_name"] + core.setting_set("name", fields["te_name"]) + end + + if fields["favourites"] ~= nil then + local event = core.explode_table_event(fields["favourites"]) + if event.type == "DCL" then + if event.row <= #menudata.favorites then + if not is_server_protocol_compat_or_error(menudata.favorites[event.row].proto_min, + menudata.favorites[event.row].proto_max) then + return true + end + gamedata.address = menudata.favorites[event.row].address + gamedata.port = menudata.favorites[event.row].port + gamedata.playername = fields["te_name"] + if fields["te_pwd"] ~= nil then + gamedata.password = fields["te_pwd"] + end + gamedata.selected_world = 0 + + if menudata.favorites ~= nil then + gamedata.servername = menudata.favorites[event.row].name + gamedata.serverdescription = menudata.favorites[event.row].description + end + + if gamedata.address ~= nil and + gamedata.port ~= nil then + core.setting_set("address",gamedata.address) + core.setting_set("remote_port",gamedata.port) + core.start() + end + end + return true + end + + if event.type == "CHG" then + if event.row <= #menudata.favorites then + local address = menudata.favorites[event.row].address + local port = menudata.favorites[event.row].port + + if address ~= nil and + port ~= nil then + core.setting_set("address",address) + core.setting_set("remote_port",port) + end + + tabdata.fav_selected = event.row + end + + return true + end + end + + if fields["key_up"] ~= nil or + fields["key_down"] ~= nil then + + local fav_idx = core.get_table_index("favourites") + + if fav_idx ~= nil then + if fields["key_up"] ~= nil and fav_idx > 1 then + fav_idx = fav_idx -1 + else if fields["key_down"] and fav_idx < #menudata.favorites then + fav_idx = fav_idx +1 + end end + else + fav_idx = 1 + end + + if menudata.favorites == nil or + menudata.favorites[fav_idx] == nil then + tabdata.fav_selected = 0 + return true + end + + local address = menudata.favorites[fav_idx].address + local port = menudata.favorites[fav_idx].port + + if address ~= nil and + port ~= nil then + core.setting_set("address",address) + core.setting_set("remote_port",port) + end + + tabdata.fav_selected = fav_idx + return true + end + + if fields["cb_public_serverlist"] ~= nil then + core.setting_set("public_serverlist", fields["cb_public_serverlist"]) + + if core.setting_getbool("public_serverlist") then + asyncOnlineFavourites() + else + menudata.favorites = core.get_favorites("local") + end + tabdata.fav_selected = nil + return true + end + + if fields["btn_delete_favorite"] ~= nil then + local current_favourite = core.get_table_index("favourites") + if current_favourite == nil then return end + core.delete_favorite(current_favourite) + menudata.favorites = order_favorite_list(core.get_favorites()) + tabdata.fav_selected = nil + + core.setting_set("address","") + core.setting_set("remote_port","30000") + + return true + end + + if (fields["btn_mp_connect"] ~= nil or + fields["key_enter"] ~= nil) and fields["te_address"] ~= nil and + fields["te_port"] ~= nil then + + gamedata.playername = fields["te_name"] + gamedata.password = fields["te_pwd"] + gamedata.address = fields["te_address"] + gamedata.port = fields["te_port"] + + local fav_idx = core.get_table_index("favourites") + + if fav_idx ~= nil and fav_idx <= #menudata.favorites and + menudata.favorites[fav_idx].address == fields["te_address"] and + menudata.favorites[fav_idx].port == fields["te_port"] then + + gamedata.servername = menudata.favorites[fav_idx].name + gamedata.serverdescription = menudata.favorites[fav_idx].description + + if not is_server_protocol_compat_or_error(menudata.favorites[fav_idx].proto_min, + menudata.favorites[fav_idx].proto_max)then + return true + end + else + gamedata.servername = "" + gamedata.serverdescription = "" + end + + gamedata.selected_world = 0 + + core.setting_set("address", fields["te_address"]) + core.setting_set("remote_port",fields["te_port"]) + + core.start() + return true + end + return false +end + +local function on_change(type,old_tab,new_tab) + if type == "LEAVE" then + return + end + if core.setting_getbool("public_serverlist") then + asyncOnlineFavourites() + else + menudata.favorites = core.get_favorites("local") + end +end + +-------------------------------------------------------------------------------- +tab_multiplayer = { + name = "multiplayer", + caption = fgettext("Client"), + cbf_formspec = get_formspec, + cbf_button_handler = main_button_handler, + on_change = on_change + } diff --git a/builtin/mainmenu/tab_server.lua b/builtin/mainmenu/tab_server.lua new file mode 100644 index 0000000..1ae2a06 --- /dev/null +++ b/builtin/mainmenu/tab_server.lua @@ -0,0 +1,178 @@ +--Minetest +--Copyright (C) 2014 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +-------------------------------------------------------------------------------- +local function get_formspec(tabview, name, tabdata) + + local index = menudata.worldlist:get_current_index( + tonumber(core.setting_get("mainmenu_last_selected_world")) + ) + + local retval = + "button[4,4.15;2.6,0.5;world_delete;" .. fgettext("Delete") .. "]" .. + "button[6.5,4.15;2.8,0.5;world_create;" .. fgettext("New") .. "]" .. + "button[9.2,4.15;2.55,0.5;world_configure;" .. fgettext("Configure") .. "]" .. + "button[8.5,4.95;3.25,0.5;start_server;" .. fgettext("Start Game") .. "]" .. + "label[4,-0.25;" .. fgettext("Select World:") .. "]" .. + "checkbox[0.25,0.25;cb_creative_mode;" .. fgettext("Creative Mode") .. ";" .. + dump(core.setting_getbool("creative_mode")) .. "]" .. + "checkbox[0.25,0.7;cb_enable_damage;" .. fgettext("Enable Damage") .. ";" .. + dump(core.setting_getbool("enable_damage")) .. "]" .. + "checkbox[0.25,1.15;cb_server_announce;" .. fgettext("Public") .. ";" .. + dump(core.setting_getbool("server_announce")) .. "]" .. + "label[0.25,2.2;" .. fgettext("Name/Password") .. "]" .. + "field[0.55,3.2;3.5,0.5;te_playername;;" .. + core.formspec_escape(core.setting_get("name")) .. "]" .. + "pwdfield[0.55,4;3.5,0.5;te_passwd;]" + + local bind_addr = core.setting_get("bind_address") + if bind_addr ~= nil and bind_addr ~= "" then + retval = retval .. + "field[0.55,5.2;2.25,0.5;te_serveraddr;" .. fgettext("Bind Address") .. ";" .. + core.formspec_escape(core.setting_get("bind_address")) .. "]" .. + "field[2.8,5.2;1.25,0.5;te_serverport;" .. fgettext("Port") .. ";" .. + core.formspec_escape(core.setting_get("port")) .. "]" + else + retval = retval .. + "field[0.55,5.2;3.5,0.5;te_serverport;" .. fgettext("Server Port") .. ";" .. + core.formspec_escape(core.setting_get("port")) .. "]" + end + + retval = retval .. + "textlist[4,0.25;7.5,3.7;srv_worlds;" .. + menu_render_worldlist() .. + ";" .. index .. "]" + + return retval +end + +-------------------------------------------------------------------------------- +local function main_button_handler(this, fields, name, tabdata) + + local world_doubleclick = false + + if fields["srv_worlds"] ~= nil then + local event = core.explode_textlist_event(fields["srv_worlds"]) + + if event.type == "DCL" then + world_doubleclick = true + end + if event.type == "CHG" then + core.setting_set("mainmenu_last_selected_world", + menudata.worldlist:get_raw_index(core.get_textlist_index("srv_worlds"))) + return true + end + end + + if menu_handle_key_up_down(fields,"srv_worlds","mainmenu_last_selected_world") then + return true + end + + if fields["cb_creative_mode"] then + core.setting_set("creative_mode", fields["cb_creative_mode"]) + return true + end + + if fields["cb_enable_damage"] then + core.setting_set("enable_damage", fields["cb_enable_damage"]) + return true + end + + if fields["cb_server_announce"] then + core.setting_set("server_announce", fields["cb_server_announce"]) + return true + end + + if fields["start_server"] ~= nil or + world_doubleclick or + fields["key_enter"] then + local selected = core.get_textlist_index("srv_worlds") + if selected ~= nil then + gamedata.playername = fields["te_playername"] + gamedata.password = fields["te_passwd"] + gamedata.port = fields["te_serverport"] + gamedata.address = "" + gamedata.selected_world = menudata.worldlist:get_raw_index(selected) + + core.setting_set("port",gamedata.port) + if fields["te_serveraddr"] ~= nil then + core.setting_set("bind_address",fields["te_serveraddr"]) + end + + --update last game + local world = menudata.worldlist:get_raw_element(gamedata.selected_world) + + local game,index = gamemgr.find_by_gameid(world.gameid) + core.setting_set("menu_last_game",game.id) + core.start() + return true + end + end + + if fields["world_create"] ~= nil then + local create_world_dlg = create_create_world_dlg(true) + create_world_dlg:set_parent(this) + create_world_dlg:show() + this:hide() + return true + end + + if fields["world_delete"] ~= nil then + local selected = core.get_textlist_index("srv_worlds") + if selected ~= nil and + selected <= menudata.worldlist:size() then + local world = menudata.worldlist:get_list()[selected] + if world ~= nil and + world.name ~= nil and + world.name ~= "" then + local index = menudata.worldlist:get_raw_index(selected) + local delete_world_dlg = create_delete_world_dlg(world.name,index) + delete_world_dlg:set_parent(this) + delete_world_dlg:show() + this:hide() + end + end + + return true + end + + if fields["world_configure"] ~= nil then + local selected = core.get_textlist_index("srv_worlds") + if selected ~= nil then + local configdialog = + create_configure_world_dlg( + menudata.worldlist:get_raw_index(selected)) + + if (configdialog ~= nil) then + configdialog:set_parent(this) + configdialog:show() + this:hide() + end + end + return true + end + return false +end + +-------------------------------------------------------------------------------- +tab_server = { + name = "server", + caption = fgettext("Server"), + cbf_formspec = get_formspec, + cbf_button_handler = main_button_handler, + on_change = nil + } diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua new file mode 100644 index 0000000..881a634 --- /dev/null +++ b/builtin/mainmenu/tab_settings.lua @@ -0,0 +1,401 @@ +--Minetest +--Copyright (C) 2013 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +-------------------------------------------------------------------------------- + +local dd_filter_labels = { + fgettext("No Filter"), + fgettext("Bilinear Filter"), + fgettext("Trilinear Filter") +} + +local filters = { + {dd_filter_labels[1]..","..dd_filter_labels[2]..","..dd_filter_labels[3]}, + {"", "bilinear_filter", "trilinear_filter"}, +} + +local dd_mipmap_labels = { + fgettext("No Mipmap"), + fgettext("Mipmap"), + fgettext("Mipmap + Aniso. Filter") +} + +local mipmap = { + {dd_mipmap_labels[1]..","..dd_mipmap_labels[2]..","..dd_mipmap_labels[3]}, + {"", "mip_map", "anisotropic_filter"}, +} + +local function getFilterSettingIndex() + if (core.setting_get(filters[2][3]) == "true") then + return 3 + end + if (core.setting_get(filters[2][3]) == "false" and core.setting_get(filters[2][2]) == "true") then + return 2 + end + return 1 +end + +local function getMipmapSettingIndex() + if (core.setting_get(mipmap[2][3]) == "true") then + return 3 + end + if (core.setting_get(mipmap[2][3]) == "false" and core.setting_get(mipmap[2][2]) == "true") then + return 2 + end + return 1 +end + +local function video_driver_fname_to_name(selected_driver) + local video_drivers = core.get_video_drivers() + + for i=1, #video_drivers do + if selected_driver == video_drivers[i].friendly_name then + return video_drivers[i].name:lower() + end + end + + return "" +end + +local function dlg_confirm_reset_formspec(data) + local retval = + "size[8,3]" .. + "label[1,1;".. fgettext("Are you sure to reset your singleplayer world?") .. "]".. + "button[1,2;2.6,0.5;dlg_reset_singleplayer_confirm;".. + fgettext("Yes") .. "]" .. + "button[4,2;2.8,0.5;dlg_reset_singleplayer_cancel;".. + fgettext("No!!!") .. "]" + return retval +end + +local function dlg_confirm_reset_btnhandler(this, fields, dialogdata) + + if fields["dlg_reset_singleplayer_confirm"] ~= nil then + local worldlist = core.get_worlds() + local found_singleplayerworld = false + + for i=1,#worldlist,1 do + if worldlist[i].name == "singleplayerworld" then + found_singleplayerworld = true + gamedata.worldindex = i + end + end + + if found_singleplayerworld then + core.delete_world(gamedata.worldindex) + end + + core.create_world("singleplayerworld", 1) + + worldlist = core.get_worlds() + + found_singleplayerworld = false + + for i=1,#worldlist,1 do + if worldlist[i].name == "singleplayerworld" then + found_singleplayerworld = true + gamedata.worldindex = i + end + end + end + + this.parent:show() + this:hide() + this:delete() + return true +end + +local function showconfirm_reset(tabview) + local new_dlg = dialog_create("reset_spworld", + dlg_confirm_reset_formspec, + dlg_confirm_reset_btnhandler, + nil) + new_dlg:set_parent(tabview) + tabview:hide() + new_dlg:show() +end + +local function gui_scale_to_scrollbar() + local current_value = tonumber(core.setting_get("gui_scaling")) + + if (current_value == nil) or current_value < 0.25 then + return 0 + end + if current_value <= 1.25 then + return ((current_value - 0.25)/ 1.0) * 700 + end + if current_value <= 6 then + return ((current_value -1.25) * 100) + 700 + end + + return 1000 +end + +local function scrollbar_to_gui_scale(value) + value = tonumber(value) + + if (value <= 700) then + return ((value / 700) * 1.0) + 0.25 + end + if (value <=1000) then + return ((value - 700) / 100) + 1.25 + end + + return 1 +end + +local function formspec(tabview, name, tabdata) + local video_drivers = core.get_video_drivers() + local current_video_driver = core.setting_get("video_driver"):lower() + + local driver_formspec_string = "" + local driver_current_idx = 0 + + for i=2, #video_drivers do + driver_formspec_string = driver_formspec_string .. video_drivers[i].friendly_name + if i ~= #video_drivers then + driver_formspec_string = driver_formspec_string .. "," + end + + if current_video_driver == video_drivers[i].name:lower() then + driver_current_idx = i - 1 + end + end + + local tab_string = + "box[0,0;3.5,3.9;#999999]" .. + "checkbox[0.25,0;cb_smooth_lighting;".. fgettext("Smooth Lighting") + .. ";".. dump(core.setting_getbool("smooth_lighting")) .. "]".. + "checkbox[0.25,0.5;cb_particles;".. fgettext("Enable Particles") .. ";" + .. dump(core.setting_getbool("enable_particles")) .. "]".. + "checkbox[0.25,1;cb_3d_clouds;".. fgettext("3D Clouds") .. ";" + .. dump(core.setting_getbool("enable_3d_clouds")) .. "]".. + "checkbox[0.25,1.5;cb_fancy_trees;".. fgettext("Fancy Trees") .. ";" + .. dump(core.setting_getbool("new_style_leaves")) .. "]".. + "checkbox[0.25,2.0;cb_opaque_water;".. fgettext("Opaque Water") .. ";" + .. dump(core.setting_getbool("opaque_water")) .. "]".. + "checkbox[0.25,2.5;cb_connected_glass;".. fgettext("Connected Glass") .. ";" + .. dump(core.setting_getbool("connected_glass")) .. "]".. + "checkbox[0.25,3.0;cb_node_highlighting;".. fgettext("Node Highlighting") .. ";" + .. dump(core.setting_getbool("enable_node_highlighting")) .. "]".. + "box[3.75,0;3.75,3.45;#999999]" .. + "label[3.85,0.1;".. fgettext("Texturing:") .. "]".. + "dropdown[3.85,0.55;3.85;dd_filters;" .. filters[1][1] .. ";" + .. getFilterSettingIndex() .. "]" .. + "dropdown[3.85,1.35;3.85;dd_mipmap;" .. mipmap[1][1] .. ";" + .. getMipmapSettingIndex() .. "]" .. + "label[3.85,2.15;".. fgettext("Rendering:") .. "]".. + "dropdown[3.85,2.6;3.85;dd_video_driver;" + .. driver_formspec_string .. ";" .. driver_current_idx .. "]" .. + "tooltip[dd_video_driver;" .. + fgettext("Restart minetest for driver change to take effect") .. "]" .. + "box[7.75,0;4,4;#999999]" .. + "checkbox[8,0;cb_shaders;".. fgettext("Shaders") .. ";" + .. dump(core.setting_getbool("enable_shaders")) .. "]" + + if PLATFORM ~= "Android" then + tab_string = tab_string .. + "button[8,4.75;3.75,0.5;btn_change_keys;".. fgettext("Change keys") .. "]" + else + tab_string = tab_string .. + "button[8,4.75;3.75,0.5;btn_reset_singleplayer;".. fgettext("Reset singleplayer world") .. "]" + end + tab_string = tab_string .. + "box[0,4.25;3.5,1.1;#999999]" .. + "label[0.25,4.25;" .. fgettext("GUI scale factor") .. "]" .. + "scrollbar[0.25,4.75;3,0.4;sb_gui_scaling;horizontal;" .. + gui_scale_to_scrollbar() .. "]" .. + "tooltip[sb_gui_scaling;" .. + fgettext("Scaling factor applied to menu elements: ") .. + dump(core.setting_get("gui_scaling")) .. "]" + + if PLATFORM == "Android" then + tab_string = tab_string .. + "box[3.75,3.55;3.75,1.8;#999999]" .. + "checkbox[3.9,3.45;cb_touchscreen_target;".. fgettext("Touch free target") .. ";" + .. dump(core.setting_getbool("touchtarget")) .. "]" + end + + if core.setting_get("touchscreen_threshold") ~= nil then + tab_string = tab_string .. + "label[4.3,4.1;" .. fgettext("Touchthreshold (px)") .. "]" .. + "dropdown[3.85,4.55;3.85;dd_touchthreshold;0,10,20,30,40,50;" .. + ((tonumber(core.setting_get("touchscreen_threshold"))/10)+1) .. "]" + end + + if core.setting_getbool("enable_shaders") then + tab_string = tab_string .. + "checkbox[8,0.5;cb_bumpmapping;".. fgettext("Bumpmapping") .. ";" + .. dump(core.setting_getbool("enable_bumpmapping")) .. "]".. + "checkbox[8,1.0;cb_generate_normalmaps;".. fgettext("Generate Normalmaps") .. ";" + .. dump(core.setting_getbool("generate_normalmaps")) .. "]".. + "checkbox[8,1.5;cb_parallax;".. fgettext("Parallax Occlusion") .. ";" + .. dump(core.setting_getbool("enable_parallax_occlusion")) .. "]".. + "checkbox[8,2.0;cb_waving_water;".. fgettext("Waving Water") .. ";" + .. dump(core.setting_getbool("enable_waving_water")) .. "]".. + "checkbox[8,2.5;cb_waving_leaves;".. fgettext("Waving Leaves") .. ";" + .. dump(core.setting_getbool("enable_waving_leaves")) .. "]".. + "checkbox[8,3.0;cb_waving_plants;".. fgettext("Waving Plants") .. ";" + .. dump(core.setting_getbool("enable_waving_plants")) .. "]" + else + tab_string = tab_string .. + "textlist[8.33,0.7;4,1;;#888888" .. fgettext("Bumpmapping") .. ";0;true]" .. + "textlist[8.33,1.2;4,1;;#888888" .. fgettext("Generate Normalmaps") .. ";0;true]" .. + "textlist[8.33,1.7;4,1;;#888888" .. fgettext("Parallax Occlusion") .. ";0;true]" .. + "textlist[8.33,2.2;4,1;;#888888" .. fgettext("Waving Water") .. ";0;true]" .. + "textlist[8.33,2.7;4,1;;#888888" .. fgettext("Waving Leaves") .. ";0;true]" .. + "textlist[8.33,3.2;4,1;;#888888" .. fgettext("Waving Plants") .. ";0;true]" + end + return tab_string +end + +-------------------------------------------------------------------------------- +local function handle_settings_buttons(this, fields, tabname, tabdata) + if fields["cb_fancy_trees"] then + core.setting_set("new_style_leaves", fields["cb_fancy_trees"]) + return true + end + if fields["cb_smooth_lighting"] then + core.setting_set("smooth_lighting", fields["cb_smooth_lighting"]) + return true + end + if fields["cb_3d_clouds"] then + core.setting_set("enable_3d_clouds", fields["cb_3d_clouds"]) + return true + end + if fields["cb_opaque_water"] then + core.setting_set("opaque_water", fields["cb_opaque_water"]) + return true + end + if fields["cb_shaders"] then + if (core.setting_get("video_driver") == "direct3d8" or core.setting_get("video_driver") == "direct3d9") then + core.setting_set("enable_shaders", "false") + gamedata.errormessage = fgettext("To enable shaders the OpenGL driver needs to be used.") + else + core.setting_set("enable_shaders", fields["cb_shaders"]) + end + return true + end + if fields["cb_connected_glass"] then + core.setting_set("connected_glass", fields["cb_connected_glass"]) + return true + end + if fields["cb_node_highlighting"] then + core.setting_set("enable_node_highlighting", fields["cb_node_highlighting"]) + return true + end + if fields["cb_particles"] then + core.setting_set("enable_particles", fields["cb_particles"]) + return true + end + if fields["cb_bumpmapping"] then + core.setting_set("enable_bumpmapping", fields["cb_bumpmapping"]) + end + if fields["cb_generate_normalmaps"] then + core.setting_set("generate_normalmaps", fields["cb_generate_normalmaps"]) + end + if fields["cb_parallax"] then + core.setting_set("enable_parallax_occlusion", fields["cb_parallax"]) + return true + end + if fields["cb_waving_water"] then + core.setting_set("enable_waving_water", fields["cb_waving_water"]) + return true + end + if fields["cb_waving_leaves"] then + core.setting_set("enable_waving_leaves", fields["cb_waving_leaves"]) + end + if fields["cb_waving_plants"] then + core.setting_set("enable_waving_plants", fields["cb_waving_plants"]) + return true + end + if fields["btn_change_keys"] ~= nil then + core.show_keys_menu() + return true + end + + if fields["sb_gui_scaling"] then + local event = core.explode_scrollbar_event(fields["sb_gui_scaling"]) + + if event.type == "CHG" then + local tosave = string.format("%.2f",scrollbar_to_gui_scale(event.value)) + core.setting_set("gui_scaling",tosave) + return true + end + end + if fields["cb_touchscreen_target"] then + core.setting_set("touchtarget", fields["cb_touchscreen_target"]) + return true + end + if fields["btn_reset_singleplayer"] then + showconfirm_reset(this) + return true + end + + --Note dropdowns have to be handled LAST! + local ddhandled = false + + if fields["dd_touchthreshold"] then + core.setting_set("touchscreen_threshold",fields["dd_touchthreshold"]) + ddhandled = true + end + + if fields["dd_video_driver"] then + core.setting_set("video_driver", + video_driver_fname_to_name(fields["dd_video_driver"])) + ddhandled = true + end + if fields["dd_filters"] == dd_filter_labels[1] then + core.setting_set("bilinear_filter", "false") + core.setting_set("trilinear_filter", "false") + ddhandled = true + end + if fields["dd_filters"] == dd_filter_labels[2] then + core.setting_set("bilinear_filter", "true") + core.setting_set("trilinear_filter", "false") + ddhandled = true + end + if fields["dd_filters"] == dd_filter_labels[3] then + core.setting_set("bilinear_filter", "false") + core.setting_set("trilinear_filter", "true") + ddhandled = true + end + if fields["dd_mipmap"] == dd_mipmap_labels[1] then + core.setting_set("mip_map", "false") + core.setting_set("anisotropic_filter", "false") + ddhandled = true + end + if fields["dd_mipmap"] == dd_mipmap_labels[2] then + core.setting_set("mip_map", "true") + core.setting_set("anisotropic_filter", "false") + ddhandled = true + end + if fields["dd_mipmap"] == dd_mipmap_labels[3] then + core.setting_set("mip_map", "true") + core.setting_set("anisotropic_filter", "true") + ddhandled = true + end + + return ddhandled +end + +tab_settings = { + name = "settings", + caption = fgettext("Settings"), + cbf_formspec = formspec, + cbf_button_handler = handle_settings_buttons +} diff --git a/builtin/mainmenu/tab_simple_main.lua b/builtin/mainmenu/tab_simple_main.lua new file mode 100644 index 0000000..b9a6b65 --- /dev/null +++ b/builtin/mainmenu/tab_simple_main.lua @@ -0,0 +1,212 @@ +--Minetest +--Copyright (C) 2013 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +-------------------------------------------------------------------------------- +local function get_formspec(tabview, name, tabdata) + local retval = "" + + local render_details = dump(core.setting_getbool("public_serverlist")) + + retval = retval .. + "label[8,0.5;".. fgettext("Name/Password") .. "]" .. + "field[0.25,3.25;5.5,0.5;te_address;;" .. + core.formspec_escape(core.setting_get("address")) .."]" .. + "field[5.75,3.25;2.25,0.5;te_port;;" .. + core.formspec_escape(core.setting_get("remote_port")) .."]" .. + "checkbox[8,-0.25;cb_public_serverlist;".. fgettext("Public Serverlist") .. ";" .. + render_details .. "]" + + retval = retval .. + "button[8,2.5;4,1.5;btn_mp_connect;".. fgettext("Connect") .. "]" .. + "field[8.75,1.5;3.5,0.5;te_name;;" .. + core.formspec_escape(core.setting_get("name")) .."]" .. + "pwdfield[8.75,2.3;3.5,0.5;te_pwd;]" + + if render_details then + retval = retval .. "tablecolumns[" .. + "color,span=3;" .. + "text,align=right;" .. -- clients + "text,align=center,padding=0.25;" .. -- "/" + "text,align=right,padding=0.25;" .. -- clients_max + image_column(fgettext("Creative mode"), "creative") .. ",padding=1;" .. + image_column(fgettext("Damage enabled"), "damage") .. ",padding=0.25;" .. + image_column(fgettext("PvP enabled"), "pvp") .. ",padding=0.25;" .. + "color,span=1;" .. + "text,padding=1]" -- name + else + retval = retval .. "tablecolumns[text]" + end + retval = retval .. + "table[-0.05,0;7.55,2.75;favourites;" + + if #menudata.favorites > 0 then + retval = retval .. render_favorite(menudata.favorites[1],render_details) + + for i=2,#menudata.favorites,1 do + retval = retval .. "," .. render_favorite(menudata.favorites[i],render_details) + end + end + + if tabdata.fav_selected ~= nil then + retval = retval .. ";" .. tabdata.fav_selected .. "]" + else + retval = retval .. ";0]" + end + + -- separator + retval = retval .. + "box[-0.3,3.75;12.4,0.1;#FFFFFF]" + + -- checkboxes + retval = retval .. + "checkbox[1.0,3.9;cb_creative;".. fgettext("Creative Mode") .. ";" .. + dump(core.setting_getbool("creative_mode")) .. "]".. + "checkbox[5.0,3.9;cb_damage;".. fgettext("Enable Damage") .. ";" .. + dump(core.setting_getbool("enable_damage")) .. "]" .. + "checkbox[8,3.9;cb_fly_mode;".. fgettext("Fly mode") .. ";" .. + dump(core.setting_getbool("free_move")) .. "]" + -- buttons + retval = retval .. + "button[2.0,4.5;6,1.5;btn_start_singleplayer;" .. fgettext("Start Singleplayer") .. "]" .. + "button[8.25,4.5;2.5,1.5;btn_config_sp_world;" .. fgettext("Config mods") .. "]" + + return retval +end + +-------------------------------------------------------------------------------- +local function main_button_handler(tabview, fields, name, tabdata) + + if fields["btn_start_singleplayer"] then + gamedata.selected_world = gamedata.worldindex + gamedata.singleplayer = true + core.start() + return true + end + + if fields["favourites"] ~= nil then + local event = core.explode_textlist_event(fields["favourites"]) + + if event.type == "CHG" then + if event.index <= #menudata.favorites then + local address = menudata.favorites[event.index].address + local port = menudata.favorites[event.index].port + + if address ~= nil and + port ~= nil then + core.setting_set("address",address) + core.setting_set("remote_port",port) + end + + tabdata.fav_selected = event.index + end + end + return true + end + + if fields["cb_public_serverlist"] ~= nil then + core.setting_set("public_serverlist", fields["cb_public_serverlist"]) + + if core.setting_getbool("public_serverlist") then + asyncOnlineFavourites() + else + menudata.favorites = core.get_favorites("local") + end + return true + end + + if fields["cb_creative"] then + core.setting_set("creative_mode", fields["cb_creative"]) + return true + end + + if fields["cb_damage"] then + core.setting_set("enable_damage", fields["cb_damage"]) + return true + end + + if fields["cb_fly_mode"] then + core.setting_set("free_move", fields["cb_fly_mode"]) + return true + end + + if fields["btn_mp_connect"] ~= nil or + fields["key_enter"] ~= nil then + + gamedata.playername = fields["te_name"] + gamedata.password = fields["te_pwd"] + gamedata.address = fields["te_address"] + gamedata.port = fields["te_port"] + + local fav_idx = core.get_textlist_index("favourites") + + if fav_idx ~= nil and fav_idx <= #menudata.favorites and + menudata.favorites[fav_idx].address == fields["te_address"] and + menudata.favorites[fav_idx].port == fields["te_port"] then + + gamedata.servername = menudata.favorites[fav_idx].name + gamedata.serverdescription = menudata.favorites[fav_idx].description + + if not is_server_protocol_compat_or_error(menudata.favorites[fav_idx].proto_min, + menudata.favorites[fav_idx].proto_max) then + return true + end + else + gamedata.servername = "" + gamedata.serverdescription = "" + end + + gamedata.selected_world = 0 + + core.setting_set("address",fields["te_address"]) + core.setting_set("remote_port",fields["te_port"]) + + core.start() + return true + end + + if fields["btn_config_sp_world"] ~= nil then + local configdialog = create_configure_world_dlg(1) + + if (configdialog ~= nil) then + configdialog:set_parent(tabview) + tabview:hide() + configdialog:show() + end + return true + end +end + +-------------------------------------------------------------------------------- +local function on_activate(type,old_tab,new_tab) + if type == "LEAVE" then + return + end + if core.setting_getbool("public_serverlist") then + asyncOnlineFavourites() + else + menudata.favorites = core.get_favorites("local") + end +end + +-------------------------------------------------------------------------------- +tab_simple_main = { + name = "main", + caption = fgettext("Main"), + cbf_formspec = get_formspec, + cbf_button_handler = main_button_handler, + on_change = on_activate + } diff --git a/builtin/mainmenu/tab_singleplayer.lua b/builtin/mainmenu/tab_singleplayer.lua new file mode 100644 index 0000000..9dc377a --- /dev/null +++ b/builtin/mainmenu/tab_singleplayer.lua @@ -0,0 +1,228 @@ +--Minetest +--Copyright (C) 2014 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +local function current_game() + local last_game_id = core.setting_get("menu_last_game") + local game, index = gamemgr.find_by_gameid(last_game_id) + + return game +end + +local function singleplayer_refresh_gamebar() + + local old_bar = ui.find_by_name("game_button_bar") + + if old_bar ~= nil then + old_bar:delete() + end + + local function game_buttonbar_button_handler(fields) + for key,value in pairs(fields) do + for j=1,#gamemgr.games,1 do + if ("game_btnbar_" .. gamemgr.games[j].id == key) then + mm_texture.update("singleplayer", gamemgr.games[j]) + core.set_topleft_text(gamemgr.games[j].name) + core.setting_set("menu_last_game",gamemgr.games[j].id) + menudata.worldlist:set_filtercriteria(gamemgr.games[j].id) + return true + end + end + end + end + + local btnbar = buttonbar_create("game_button_bar", + game_buttonbar_button_handler, + {x=-0.3,y=5.65}, "horizontal", {x=12.4,y=1.15}) + + for i=1,#gamemgr.games,1 do + local btn_name = "game_btnbar_" .. gamemgr.games[i].id + + local image = nil + local text = nil + local tooltip = core.formspec_escape(gamemgr.games[i].name) + + if gamemgr.games[i].menuicon_path ~= nil and + gamemgr.games[i].menuicon_path ~= "" then + image = core.formspec_escape(gamemgr.games[i].menuicon_path) + else + + local part1 = gamemgr.games[i].id:sub(1,5) + local part2 = gamemgr.games[i].id:sub(6,10) + local part3 = gamemgr.games[i].id:sub(11) + + text = part1 .. "\n" .. part2 + if part3 ~= nil and + part3 ~= "" then + text = text .. "\n" .. part3 + end + end + btnbar:add_button(btn_name, text, image, tooltip) + end +end + +local function get_formspec(tabview, name, tabdata) + local retval = "" + + local index = filterlist.get_current_index(menudata.worldlist, + tonumber(core.setting_get("mainmenu_last_selected_world")) + ) + + retval = retval .. + "button[4,4.15;2.6,0.5;world_delete;".. fgettext("Delete") .. "]" .. + "button[6.5,4.15;2.8,0.5;world_create;".. fgettext("New") .. "]" .. + "button[9.2,4.15;2.55,0.5;world_configure;".. fgettext("Configure") .. "]" .. + "button[8.5,4.95;3.25,0.5;play;".. fgettext("Play") .. "]" .. + "label[4,-0.25;".. fgettext("Select World:") .. "]".. + "checkbox[0.25,0.25;cb_creative_mode;".. fgettext("Creative Mode") .. ";" .. + dump(core.setting_getbool("creative_mode")) .. "]".. + "checkbox[0.25,0.7;cb_enable_damage;".. fgettext("Enable Damage") .. ";" .. + dump(core.setting_getbool("enable_damage")) .. "]".. + "textlist[4,0.25;7.5,3.7;sp_worlds;" .. + menu_render_worldlist() .. + ";" .. index .. "]" + return retval +end + +local function main_button_handler(this, fields, name, tabdata) + + assert(name == "singleplayer") + + local world_doubleclick = false + + if fields["sp_worlds"] ~= nil then + local event = core.explode_textlist_event(fields["sp_worlds"]) + + if event.type == "DCL" then + world_doubleclick = true + end + + if event.type == "CHG" then + core.setting_set("mainmenu_last_selected_world", + menudata.worldlist:get_raw_index(core.get_textlist_index("sp_worlds"))) + return true + end + end + + if menu_handle_key_up_down(fields,"sp_worlds","mainmenu_last_selected_world") then + return true + end + + if fields["cb_creative_mode"] then + core.setting_set("creative_mode", fields["cb_creative_mode"]) + return true + end + + if fields["cb_enable_damage"] then + core.setting_set("enable_damage", fields["cb_enable_damage"]) + return true + end + + if fields["play"] ~= nil or + world_doubleclick or + fields["key_enter"] then + local selected = core.get_textlist_index("sp_worlds") + + if selected ~= nil then + gamedata.selected_world = menudata.worldlist:get_raw_index(selected) + gamedata.singleplayer = true + + core.start() + end + return true + end + + if fields["world_create"] ~= nil then + local create_world_dlg = create_create_world_dlg(true) + create_world_dlg:set_parent(this) + this:hide() + create_world_dlg:show() + mm_texture.update("singleplayer",current_game()) + return true + end + + if fields["world_delete"] ~= nil then + local selected = core.get_textlist_index("sp_worlds") + if selected ~= nil and + selected <= menudata.worldlist:size() then + local world = menudata.worldlist:get_list()[selected] + if world ~= nil and + world.name ~= nil and + world.name ~= "" then + local index = menudata.worldlist:get_raw_index(selected) + local delete_world_dlg = create_delete_world_dlg(world.name,index) + delete_world_dlg:set_parent(this) + this:hide() + delete_world_dlg:show() + mm_texture.update("singleplayer",current_game()) + end + end + + return true + end + + if fields["world_configure"] ~= nil then + local selected = core.get_textlist_index("sp_worlds") + if selected ~= nil then + local configdialog = + create_configure_world_dlg( + menudata.worldlist:get_raw_index(selected)) + + if (configdialog ~= nil) then + configdialog:set_parent(this) + this:hide() + configdialog:show() + mm_texture.update("singleplayer",current_game()) + end + end + + return true + end +end + +local function on_change(type, old_tab, new_tab) + local buttonbar = ui.find_by_name("game_button_bar") + + if ( buttonbar == nil ) then + singleplayer_refresh_gamebar() + buttonbar = ui.find_by_name("game_button_bar") + end + + if (type == "ENTER") then + local game = current_game() + + if game then + menudata.worldlist:set_filtercriteria(game.id) + core.set_topleft_text(game.name) + mm_texture.update("singleplayer",game) + end + buttonbar:show() + else + menudata.worldlist:set_filtercriteria(nil) + buttonbar:hide() + core.set_topleft_text("") + mm_texture.update(new_tab,nil) + end +end + +-------------------------------------------------------------------------------- +tab_singleplayer = { + name = "singleplayer", + caption = fgettext("Singleplayer"), + cbf_formspec = get_formspec, + cbf_button_handler = main_button_handler, + on_change = on_change + } diff --git a/builtin/mainmenu/tab_texturepacks.lua b/builtin/mainmenu/tab_texturepacks.lua new file mode 100644 index 0000000..d32c073 --- /dev/null +++ b/builtin/mainmenu/tab_texturepacks.lua @@ -0,0 +1,118 @@ +--Minetest +--Copyright (C) 2014 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +-------------------------------------------------------------------------------- +local function filter_texture_pack_list(list) + local retval = {"None"} + for _, item in ipairs(list) do + if item ~= "base" then + table.insert(retval, item) + end + end + return retval +end + +-------------------------------------------------------------------------------- +local function render_texture_pack_list(list) + local retval = "" + + for i, v in ipairs(list) do + if v:sub(1,1) ~= "." then + if retval ~= "" then + retval = retval .."," + end + + retval = retval .. core.formspec_escape(v) + end + end + + return retval +end + +-------------------------------------------------------------------------------- +local function get_formspec(tabview, name, tabdata) + + local retval = "label[4,-0.25;".. fgettext("Select texture pack:") .. "]".. + "textlist[4,0.25;7.5,5.0;TPs;" + + local current_texture_path = core.setting_get("texture_path") + local list = filter_texture_pack_list(core.get_dirlist(core.get_texturepath(), true)) + local index = tonumber(core.setting_get("mainmenu_last_selected_TP")) + + if index == nil then index = 1 end + + if current_texture_path == "" then + retval = retval .. + render_texture_pack_list(list) .. + ";" .. index .. "]" + return retval + end + + local infofile = current_texture_path ..DIR_DELIM.."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 = defaulttexturedir .. "no_screenshot.png" + end + + return retval .. + render_texture_pack_list(list) .. + ";" .. index .. "]" .. + "image[0.25,0.25;4.0,3.7;"..core.formspec_escape(screenfile or no_screenshot).."]".. + "textarea[0.6,3.25;3.7,1.5;;"..core.formspec_escape(infotext or "")..";]" +end + +-------------------------------------------------------------------------------- +local function main_button_handler(tabview, fields, name, tabdata) + if fields["TPs"] ~= nil then + local event = core.explode_textlist_event(fields["TPs"]) + if event.type == "CHG" or event.type == "DCL" then + local index = core.get_textlist_index("TPs") + core.setting_set("mainmenu_last_selected_TP", + index) + local list = filter_texture_pack_list(core.get_dirlist(core.get_texturepath(), true)) + local current_index = core.get_textlist_index("TPs") + if current_index ~= nil and #list >= current_index then + local new_path = core.get_texturepath()..DIR_DELIM..list[current_index] + if list[current_index] == "None" then new_path = "" end + + core.setting_set("texture_path", new_path) + end + end + return true + end + return false +end + +-------------------------------------------------------------------------------- +tab_texturepacks = { + name = "texturepacks", + caption = fgettext("Texturepacks"), + cbf_formspec = get_formspec, + cbf_button_handler = main_button_handler, + on_change = nil + } diff --git a/builtin/mainmenu/textures.lua b/builtin/mainmenu/textures.lua new file mode 100644 index 0000000..56992c0 --- /dev/null +++ b/builtin/mainmenu/textures.lua @@ -0,0 +1,166 @@ +--Minetest +--Copyright (C) 2013 sapier +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +mm_texture = {} + +-------------------------------------------------------------------------------- +function mm_texture.init() + mm_texture.defaulttexturedir = core.get_texturepath() .. DIR_DELIM .. "base" .. + DIR_DELIM .. "pack" .. DIR_DELIM + mm_texture.basetexturedir = mm_texture.defaulttexturedir + + mm_texture.texturepack = core.setting_get("texture_path") + + mm_texture.gameid = nil +end + +-------------------------------------------------------------------------------- +function mm_texture.update(tab,gamedetails) + if tab ~= "singleplayer" then + mm_texture.reset() + return + end + + if gamedetails == nil then + return + end + + mm_texture.update_game(gamedetails) +end + +-------------------------------------------------------------------------------- +function mm_texture.reset() + mm_texture.gameid = nil + local have_bg = false + local have_overlay = mm_texture.set_generic("overlay") + + if not have_overlay then + have_bg = mm_texture.set_generic("background") + end + + mm_texture.clear("header") + mm_texture.clear("footer") + core.set_clouds(false) + + mm_texture.set_generic("footer") + mm_texture.set_generic("header") + + if not have_bg then + if core.setting_getbool("menu_clouds") then + core.set_clouds(true) + else + mm_texture.set_dirt_bg() + end + end +end + +-------------------------------------------------------------------------------- +function mm_texture.update_game(gamedetails) + if mm_texture.gameid == gamedetails.id then + return + end + + local have_bg = false + local have_overlay = mm_texture.set_game("overlay",gamedetails) + + if not have_overlay then + have_bg = mm_texture.set_game("background",gamedetails) + end + + mm_texture.clear("header") + mm_texture.clear("footer") + core.set_clouds(false) + + if not have_bg then + + if core.setting_getbool("menu_clouds") then + core.set_clouds(true) + else + mm_texture.set_dirt_bg() + end + end + + mm_texture.set_game("footer",gamedetails) + mm_texture.set_game("header",gamedetails) + + mm_texture.gameid = gamedetails.id +end + +-------------------------------------------------------------------------------- +function mm_texture.clear(identifier) + core.set_background(identifier,"") +end + +-------------------------------------------------------------------------------- +function mm_texture.set_generic(identifier) + --try texture pack first + if mm_texture.texturepack ~= nil then + local path = mm_texture.texturepack .. DIR_DELIM .."menu_" .. + identifier .. ".png" + if core.set_background(identifier,path) then + return true + end + end + + if mm_texture.defaulttexturedir ~= nil then + local path = mm_texture.defaulttexturedir .. DIR_DELIM .."menu_" .. + identifier .. ".png" + if core.set_background(identifier,path) then + return true + end + end + + return false +end + +-------------------------------------------------------------------------------- +function mm_texture.set_game(identifier,gamedetails) + + if gamedetails == nil then + return false + end + + if mm_texture.texturepack ~= nil then + local path = mm_texture.texturepack .. DIR_DELIM .. + gamedetails.id .. "_menu_" .. identifier .. ".png" + if core.set_background(identifier,path) then + return true + end + end + + local path = gamedetails.path .. DIR_DELIM .."menu" .. + DIR_DELIM .. identifier .. ".png" + if core.set_background(identifier,path) then + return true + end + + return false +end + +function mm_texture.set_dirt_bg() + if mm_texture.texturepack ~= nil then + local path = mm_texture.texturepack .. DIR_DELIM .."default_dirt.png" + if core.set_background("background", path, true, 128) then + return true + end + end + + --use base pack + local minimalpath = defaulttexturedir .. "dirt_bg.png" + core.set_background("background", minimalpath, true, 128) +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/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl new file mode 100644 index 0000000..6dc96eb --- /dev/null +++ b/client/shaders/nodes_shader/opengl_fragment.glsl @@ -0,0 +1,121 @@ +uniform sampler2D baseTexture; +uniform sampler2D normalTexture; +uniform sampler2D useNormalmap; + +uniform vec4 skyBgColor; +uniform float fogDistance; +uniform vec3 eyePosition; + +varying vec3 vPosition; +varying vec3 worldPosition; + +varying vec3 eyeVec; +varying vec3 tsEyeVec; +varying vec3 lightVec; +varying vec3 tsLightVec; + +bool normalTexturePresent = false; + +const float e = 2.718281828459; +const float BS = 10.0; + +float intensity (vec3 color){ + return (color.r + color.g + color.b) / 3.0; +} + +float get_rgb_height (vec2 uv){ + return intensity(texture2D(baseTexture,uv).rgb); +} + +vec4 get_normal_map(vec2 uv){ + vec4 bump = texture2D(normalTexture, uv).rgba; + bump.xyz = normalize(bump.xyz * 2.0 -1.0); + bump.y = -bump.y; + return bump; +} + +void main (void) +{ + vec3 color; + vec4 bump; + vec2 uv = gl_TexCoord[0].st; + bool use_normalmap = false; + +#ifdef USE_NORMALMAPS + if (texture2D(useNormalmap,vec2(1.0,1.0)).r > 0.0){ + normalTexturePresent = true; + } +#endif + +#ifdef ENABLE_PARALLAX_OCCLUSION + if (normalTexturePresent){ + vec3 tsEye = normalize(tsEyeVec); + float height = PARALLAX_OCCLUSION_SCALE * texture2D(normalTexture, uv).a - PARALLAX_OCCLUSION_BIAS; + uv = uv + texture2D(normalTexture, uv).z * height * vec2(tsEye.x,-tsEye.y); + } +#endif + +#ifdef USE_NORMALMAPS + if (normalTexturePresent){ + bump = get_normal_map(uv); + use_normalmap = true; + } +#endif + +#ifdef GENERATE_NORMALMAPS + if (use_normalmap == false){ + float tl = get_rgb_height (vec2(uv.x-SAMPLE_STEP,uv.y+SAMPLE_STEP)); + float t = get_rgb_height (vec2(uv.x-SAMPLE_STEP,uv.y-SAMPLE_STEP)); + float tr = get_rgb_height (vec2(uv.x+SAMPLE_STEP,uv.y+SAMPLE_STEP)); + float r = get_rgb_height (vec2(uv.x+SAMPLE_STEP,uv.y)); + float br = get_rgb_height (vec2(uv.x+SAMPLE_STEP,uv.y-SAMPLE_STEP)); + float b = get_rgb_height (vec2(uv.x,uv.y-SAMPLE_STEP)); + float bl = get_rgb_height (vec2(uv.x-SAMPLE_STEP,uv.y-SAMPLE_STEP)); + float l = get_rgb_height (vec2(uv.x-SAMPLE_STEP,uv.y)); + float dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl); + float dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr); + bump = vec4 (normalize(vec3 (dX, -dY, NORMALMAPS_STRENGTH)),1.0); + use_normalmap = true; + } +#endif + +vec4 base = texture2D(baseTexture, uv).rgba; + +#ifdef ENABLE_BUMPMAPPING + if (use_normalmap){ + vec3 L = normalize(lightVec); + vec3 E = normalize(eyeVec); + float specular = pow(clamp(dot(reflect(L, bump.xyz), E), 0.0, 1.0),0.5); + float diffuse = dot(E,bump.xyz); + /* Mathematic optimization + * Original: color = 0.05*base.rgb + diffuse*base.rgb + 0.2*specular*base.rgb; + * This optimization save 2 multiplications (orig: 4 multiplications + 3 additions + * end: 2 multiplications + 3 additions) + */ + color = (0.05 + diffuse + 0.2 * specular) * base.rgb; + } else { + color = base.rgb; + } +#else + color = base.rgb; +#endif + +#if MATERIAL_TYPE == TILE_MATERIAL_LIQUID_TRANSPARENT || MATERIAL_TYPE == TILE_MATERIAL_LIQUID_OPAQUE + float alpha = gl_Color.a; + vec4 col = vec4(color.rgb, alpha); + col *= gl_Color; + 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.rgb, alpha); +#else + vec4 col = vec4(color.rgb, base.a); + col *= gl_Color; + 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.rgb, base.a); +#endif +} diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl new file mode 100644 index 0000000..07684f6 --- /dev/null +++ b/client/shaders/nodes_shader/opengl_vertex.glsl @@ -0,0 +1,144 @@ +uniform mat4 mWorldViewProj; +uniform mat4 mInvWorld; +uniform mat4 mTransWorld; +uniform mat4 mWorld; + +uniform float dayNightRatio; +uniform vec3 eyePosition; +uniform float animationTimer; + +varying vec3 vPosition; +varying vec3 worldPosition; + +varying vec3 eyeVec; +varying vec3 lightVec; +varying vec3 tsEyeVec; +varying vec3 tsLightVec; + +const float e = 2.718281828459; +const float BS = 10.0; + +float smoothCurve( float x ) { + return x * x *( 3.0 - 2.0 * x ); +} +float triangleWave( float x ) { + return abs( fract( x + 0.5 ) * 2.0 - 1.0 ); +} +float smoothTriangleWave( float x ) { + return smoothCurve( triangleWave( x ) ) * 2.0 - 1.0; +} + +void main(void) +{ + gl_TexCoord[0] = gl_MultiTexCoord0; + +#if (MATERIAL_TYPE == TILE_MATERIAL_LIQUID_TRANSPARENT || MATERIAL_TYPE == TILE_MATERIAL_LIQUID_OPAQUE) && ENABLE_WAVING_WATER + vec4 pos = gl_Vertex; + pos.y -= 2.0; + + float posYbuf = (pos.z / WATER_WAVE_LENGTH + animationTimer * WATER_WAVE_SPEED * WATER_WAVE_LENGTH); + + pos.y -= sin(posYbuf) * WATER_WAVE_HEIGHT + sin(posYbuf / 7.0) * WATER_WAVE_HEIGHT; + gl_Position = mWorldViewProj * pos; +#elif MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES && ENABLE_WAVING_LEAVES + vec4 pos = gl_Vertex; + vec4 pos2 = mWorld * gl_Vertex; + + /* + * Mathematic optimization: pos2.x * A + pos2.z * A (2 multiplications + 1 addition) + * replaced with: (pos2.x + pos2.z) * A (1 addition + 1 multiplication) + * And bufferize calcul to a float + */ + float pos2XpZ = pos2.x + pos2.z; + + pos.x += (smoothTriangleWave(animationTimer*10.0 + pos2XpZ * 0.01) * 2.0 - 1.0) * 0.4; + pos.y += (smoothTriangleWave(animationTimer*15.0 + pos2XpZ * -0.01) * 2.0 - 1.0) * 0.2; + pos.z += (smoothTriangleWave(animationTimer*10.0 + pos2XpZ * -0.01) * 2.0 - 1.0) * 0.4; + gl_Position = mWorldViewProj * pos; +#elif MATERIAL_TYPE == TILE_MATERIAL_WAVING_PLANTS && ENABLE_WAVING_PLANTS + vec4 pos = gl_Vertex; + vec4 pos2 = mWorld * gl_Vertex; + if (gl_TexCoord[0].y < 0.05) { + /* + * Mathematic optimization: pos2.x * A + pos2.z * A (2 multiplications + 1 addition) + * replaced with: (pos2.x + pos2.z) * A (1 addition + 1 multiplication) + * And bufferize calcul to a float + */ + float pos2XpZ = pos2.x + pos2.z; + + pos.x += (smoothTriangleWave(animationTimer * 20.0 + pos2XpZ * 0.1) * 2.0 - 1.0) * 0.8; + pos.y -= (smoothTriangleWave(animationTimer * 10.0 + pos2XpZ * -0.5) * 2.0 - 1.0) * 0.4; + } + gl_Position = mWorldViewProj * pos; +#else + gl_Position = mWorldViewProj * gl_Vertex; +#endif + + vPosition = gl_Position.xyz; + worldPosition = (mWorld * gl_Vertex).xyz; + vec3 sunPosition = vec3 (0.0, eyePosition.y * BS + 900.0, 0.0); + + vec3 normal, tangent, binormal; + normal = normalize(gl_NormalMatrix * gl_Normal); + if (gl_Normal.x > 0.5) { + // 1.0, 0.0, 0.0 + tangent = normalize(gl_NormalMatrix * vec3( 0.0, 0.0, -1.0)); + binormal = normalize(gl_NormalMatrix * vec3( 0.0, -1.0, 0.0)); + } else if (gl_Normal.x < -0.5) { + // -1.0, 0.0, 0.0 + tangent = normalize(gl_NormalMatrix * vec3( 0.0, 0.0, 1.0)); + binormal = normalize(gl_NormalMatrix * vec3( 0.0, -1.0, 0.0)); + } else if (gl_Normal.y > 0.5) { + // 0.0, 1.0, 0.0 + tangent = normalize(gl_NormalMatrix * vec3( 1.0, 0.0, 0.0)); + binormal = normalize(gl_NormalMatrix * vec3( 0.0, 0.0, 1.0)); + } else if (gl_Normal.y < -0.5) { + // 0.0, -1.0, 0.0 + tangent = normalize(gl_NormalMatrix * vec3( 1.0, 0.0, 0.0)); + binormal = normalize(gl_NormalMatrix * vec3( 0.0, 0.0, 1.0)); + } else if (gl_Normal.z > 0.5) { + // 0.0, 0.0, 1.0 + tangent = normalize(gl_NormalMatrix * vec3( 1.0, 0.0, 0.0)); + binormal = normalize(gl_NormalMatrix * vec3( 0.0, -1.0, 0.0)); + } else if (gl_Normal.z < -0.5) { + // 0.0, 0.0, -1.0 + tangent = normalize(gl_NormalMatrix * vec3(-1.0, 0.0, 0.0)); + binormal = normalize(gl_NormalMatrix * vec3( 0.0, -1.0, 0.0)); + } + mat3 tbnMatrix = mat3( tangent.x, binormal.x, normal.x, + tangent.y, binormal.y, normal.y, + tangent.z, binormal.z, normal.z); + + lightVec = sunPosition - worldPosition; + tsLightVec = lightVec * tbnMatrix; + eyeVec = (gl_ModelViewMatrix * gl_Vertex).xyz; + tsEyeVec = eyeVec * tbnMatrix; + + vec4 color; + float day = gl_Color.r; + float night = gl_Color.g; + float light_source = gl_Color.b; + + float rg = mix(night, day, dayNightRatio); + rg += light_source * 2.5; // Make light sources brighter + float b = rg; + + // Moonlight is blue + b += (day - night) / 13.0; + rg -= (day - night) / 23.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 = clamp(color,0.0,1.0); +} diff --git a/client/shaders/water_surface_shader/opengl_fragment.glsl b/client/shaders/water_surface_shader/opengl_fragment.glsl new file mode 100644 index 0000000..6dc96eb --- /dev/null +++ b/client/shaders/water_surface_shader/opengl_fragment.glsl @@ -0,0 +1,121 @@ +uniform sampler2D baseTexture; +uniform sampler2D normalTexture; +uniform sampler2D useNormalmap; + +uniform vec4 skyBgColor; +uniform float fogDistance; +uniform vec3 eyePosition; + +varying vec3 vPosition; +varying vec3 worldPosition; + +varying vec3 eyeVec; +varying vec3 tsEyeVec; +varying vec3 lightVec; +varying vec3 tsLightVec; + +bool normalTexturePresent = false; + +const float e = 2.718281828459; +const float BS = 10.0; + +float intensity (vec3 color){ + return (color.r + color.g + color.b) / 3.0; +} + +float get_rgb_height (vec2 uv){ + return intensity(texture2D(baseTexture,uv).rgb); +} + +vec4 get_normal_map(vec2 uv){ + vec4 bump = texture2D(normalTexture, uv).rgba; + bump.xyz = normalize(bump.xyz * 2.0 -1.0); + bump.y = -bump.y; + return bump; +} + +void main (void) +{ + vec3 color; + vec4 bump; + vec2 uv = gl_TexCoord[0].st; + bool use_normalmap = false; + +#ifdef USE_NORMALMAPS + if (texture2D(useNormalmap,vec2(1.0,1.0)).r > 0.0){ + normalTexturePresent = true; + } +#endif + +#ifdef ENABLE_PARALLAX_OCCLUSION + if (normalTexturePresent){ + vec3 tsEye = normalize(tsEyeVec); + float height = PARALLAX_OCCLUSION_SCALE * texture2D(normalTexture, uv).a - PARALLAX_OCCLUSION_BIAS; + uv = uv + texture2D(normalTexture, uv).z * height * vec2(tsEye.x,-tsEye.y); + } +#endif + +#ifdef USE_NORMALMAPS + if (normalTexturePresent){ + bump = get_normal_map(uv); + use_normalmap = true; + } +#endif + +#ifdef GENERATE_NORMALMAPS + if (use_normalmap == false){ + float tl = get_rgb_height (vec2(uv.x-SAMPLE_STEP,uv.y+SAMPLE_STEP)); + float t = get_rgb_height (vec2(uv.x-SAMPLE_STEP,uv.y-SAMPLE_STEP)); + float tr = get_rgb_height (vec2(uv.x+SAMPLE_STEP,uv.y+SAMPLE_STEP)); + float r = get_rgb_height (vec2(uv.x+SAMPLE_STEP,uv.y)); + float br = get_rgb_height (vec2(uv.x+SAMPLE_STEP,uv.y-SAMPLE_STEP)); + float b = get_rgb_height (vec2(uv.x,uv.y-SAMPLE_STEP)); + float bl = get_rgb_height (vec2(uv.x-SAMPLE_STEP,uv.y-SAMPLE_STEP)); + float l = get_rgb_height (vec2(uv.x-SAMPLE_STEP,uv.y)); + float dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl); + float dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr); + bump = vec4 (normalize(vec3 (dX, -dY, NORMALMAPS_STRENGTH)),1.0); + use_normalmap = true; + } +#endif + +vec4 base = texture2D(baseTexture, uv).rgba; + +#ifdef ENABLE_BUMPMAPPING + if (use_normalmap){ + vec3 L = normalize(lightVec); + vec3 E = normalize(eyeVec); + float specular = pow(clamp(dot(reflect(L, bump.xyz), E), 0.0, 1.0),0.5); + float diffuse = dot(E,bump.xyz); + /* Mathematic optimization + * Original: color = 0.05*base.rgb + diffuse*base.rgb + 0.2*specular*base.rgb; + * This optimization save 2 multiplications (orig: 4 multiplications + 3 additions + * end: 2 multiplications + 3 additions) + */ + color = (0.05 + diffuse + 0.2 * specular) * base.rgb; + } else { + color = base.rgb; + } +#else + color = base.rgb; +#endif + +#if MATERIAL_TYPE == TILE_MATERIAL_LIQUID_TRANSPARENT || MATERIAL_TYPE == TILE_MATERIAL_LIQUID_OPAQUE + float alpha = gl_Color.a; + vec4 col = vec4(color.rgb, alpha); + col *= gl_Color; + 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.rgb, alpha); +#else + vec4 col = vec4(color.rgb, base.a); + col *= gl_Color; + 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.rgb, base.a); +#endif +} diff --git a/client/shaders/water_surface_shader/opengl_vertex.glsl b/client/shaders/water_surface_shader/opengl_vertex.glsl new file mode 100644 index 0000000..6e70bbc --- /dev/null +++ b/client/shaders/water_surface_shader/opengl_vertex.glsl @@ -0,0 +1,141 @@ +uniform mat4 mWorldViewProj; +uniform mat4 mInvWorld; +uniform mat4 mTransWorld; +uniform mat4 mWorld; + +uniform float dayNightRatio; +uniform vec3 eyePosition; +uniform float animationTimer; + +varying vec3 vPosition; +varying vec3 worldPosition; + +varying vec3 eyeVec; +varying vec3 lightVec; +varying vec3 tsEyeVec; +varying vec3 tsLightVec; + +const float e = 2.718281828459; +const float BS = 10.0; + +float smoothCurve( float x ) { + return x * x *( 3.0 - 2.0 * x ); +} +float triangleWave( float x ) { + return abs( fract( x + 0.5 ) * 2.0 - 1.0 ); +} +float smoothTriangleWave( float x ) { + return smoothCurve( triangleWave( x ) ) * 2.0 - 1.0; +} + +void main(void) +{ + gl_TexCoord[0] = gl_MultiTexCoord0; + +#if (MATERIAL_TYPE == TILE_MATERIAL_LIQUID_TRANSPARENT || MATERIAL_TYPE == TILE_MATERIAL_LIQUID_OPAQUE) && ENABLE_WAVING_WATER + vec4 pos = gl_Vertex; + pos.y -= 2.0; + + float posYbuf = (pos.z / WATER_WAVE_LENGTH + animationTimer * WATER_WAVE_SPEED * WATER_WAVE_LENGTH); + + pos.y -= sin(posYbuf) * WATER_WAVE_HEIGHT + sin(posYbuf / 7.0) * WATER_WAVE_HEIGHT; + gl_Position = mWorldViewProj * pos; +#elif MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES && ENABLE_WAVING_LEAVES + vec4 pos = gl_Vertex; + vec4 pos2 = mWorld * gl_Vertex; + /* + * Mathematic optimization: pos2.x * A + pos2.z * A (2 multiplications + 1 addition) + * replaced with: (pos2.x + pos2.z) * A (1 addition + 1 multiplication) + * And bufferize calcul to a float + */ + float pos2XpZ = pos2.x + pos2.z; + pos.x += (smoothTriangleWave(animationTimer*10.0 + pos2XpZ * 0.01) * 2.0 - 1.0) * 0.4; + pos.y += (smoothTriangleWave(animationTimer*15.0 + pos2XpZ * -0.01) * 2.0 - 1.0) * 0.2; + pos.z += (smoothTriangleWave(animationTimer*10.0 + pos2XpZ * -0.01) * 2.0 - 1.0) * 0.4; + gl_Position = mWorldViewProj * pos; +#elif MATERIAL_TYPE == TILE_MATERIAL_WAVING_PLANTS && ENABLE_WAVING_PLANTS + vec4 pos = gl_Vertex; + vec4 pos2 = mWorld * gl_Vertex; + if (gl_TexCoord[0].y < 0.05) { + /* + * Mathematic optimization: pos2.x * A + pos2.z * A (2 multiplications + 1 addition) + * replaced with: (pos2.x + pos2.z) * A (1 addition + 1 multiplication) + * And bufferize calcul to a float + */ + float pos2XpZ = pos2.x + pos2.z; + pos.x += (smoothTriangleWave(animationTimer * 20.0 + pos2XpZ * 0.1) * 2.0 - 1.0) * 0.8; + pos.y -= (smoothTriangleWave(animationTimer * 10.0 + pos2XpZ * -0.5) * 2.0 - 1.0) * 0.4; + } + gl_Position = mWorldViewProj * pos; +#else + gl_Position = mWorldViewProj * gl_Vertex; +#endif + + vPosition = gl_Position.xyz; + worldPosition = (mWorld * gl_Vertex).xyz; + vec3 sunPosition = vec3 (0.0, eyePosition.y * BS + 900.0, 0.0); + + vec3 normal, tangent, binormal; + normal = normalize(gl_NormalMatrix * gl_Normal); + if (gl_Normal.x > 0.5) { + // 1.0, 0.0, 0.0 + tangent = normalize(gl_NormalMatrix * vec3( 0.0, 0.0, -1.0)); + binormal = normalize(gl_NormalMatrix * vec3( 0.0, -1.0, 0.0)); + } else if (gl_Normal.x < -0.5) { + // -1.0, 0.0, 0.0 + tangent = normalize(gl_NormalMatrix * vec3( 0.0, 0.0, 1.0)); + binormal = normalize(gl_NormalMatrix * vec3( 0.0, -1.0, 0.0)); + } else if (gl_Normal.y > 0.5) { + // 0.0, 1.0, 0.0 + tangent = normalize(gl_NormalMatrix * vec3( 1.0, 0.0, 0.0)); + binormal = normalize(gl_NormalMatrix * vec3( 0.0, 0.0, 1.0)); + } else if (gl_Normal.y < -0.5) { + // 0.0, -1.0, 0.0 + tangent = normalize(gl_NormalMatrix * vec3( 1.0, 0.0, 0.0)); + binormal = normalize(gl_NormalMatrix * vec3( 0.0, 0.0, 1.0)); + } else if (gl_Normal.z > 0.5) { + // 0.0, 0.0, 1.0 + tangent = normalize(gl_NormalMatrix * vec3( 1.0, 0.0, 0.0)); + binormal = normalize(gl_NormalMatrix * vec3( 0.0, -1.0, 0.0)); + } else if (gl_Normal.z < -0.5) { + // 0.0, 0.0, -1.0 + tangent = normalize(gl_NormalMatrix * vec3(-1.0, 0.0, 0.0)); + binormal = normalize(gl_NormalMatrix * vec3( 0.0, -1.0, 0.0)); + } + mat3 tbnMatrix = mat3(tangent.x, binormal.x, normal.x, + tangent.y, binormal.y, normal.y, + tangent.z, binormal.z, normal.z); + + lightVec = sunPosition - worldPosition; + tsLightVec = lightVec * tbnMatrix; + eyeVec = (gl_ModelViewMatrix * gl_Vertex).xyz; + tsEyeVec = eyeVec * tbnMatrix; + + vec4 color; + float day = gl_Color.r; + float night = gl_Color.g; + float light_source = gl_Color.b; + + float rg = mix(night, day, dayNightRatio); + rg += light_source * 2.5; // Make light sources brighter + float b = rg; + + // Moonlight is blue + b += (day - night) / 13.0; + rg -= (day - night) / 23.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 = clamp(color,0.0,1.0); +} 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..c6f731e --- /dev/null +++ b/cmake/Modules/FindGettextLib.cmake @@ -0,0 +1,84 @@ +# 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") + +if(APPLE) + FIND_LIBRARY(GETTEXT_LIBRARY + NAMES libintl.a + PATHS "${CUSTOM_GETTEXT_PATH}/lib" + DOC "gettext *intl*.lib") + + FIND_LIBRARY(ICONV_LIBRARY + NAMES libiconv.dylib + PATHS "/usr/lib" + DOC "iconv lib") +endif(APPLE) + +# 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..a84765d --- /dev/null +++ b/cmake/Modules/FindIrrlicht.cmake @@ -0,0 +1,88 @@ +#FindIrrlicht.cmake + +set(IRRLICHT_SOURCE_DIR "" CACHE PATH "Path to irrlicht source directory (optional)") + +# 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.so 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..b23553a --- /dev/null +++ b/cmake/Modules/FindSQLite3.cmake @@ -0,0 +1,9 @@ +mark_as_advanced(SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR) + +find_path(SQLITE3_INCLUDE_DIR sqlite3.h) + +find_library(SQLITE3_LIBRARY NAMES sqlite3) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SQLite3 DEFAULT_MSG SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR) + 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/Doxyfile.in b/doc/Doxyfile.in new file mode 100644 index 0000000..7f37225 --- /dev/null +++ b/doc/Doxyfile.in @@ -0,0 +1,32 @@ +DOXYFILE_ENCODING = UTF-8 + +PROJECT_NAME = "Blokel" +PROJECT_NUMBER = @VERSION_STRING@ + +STRIP_FROM_PATH = @CMAKE_CURRENT_SOURCE_DIR@/src +JAVADOC_AUTOBRIEF = YES +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_STATIC = YES +SORT_MEMBERS_CTORS_1ST = YES +WARN_IF_UNDOCUMENTED = NO + +INPUT = @CMAKE_CURRENT_SOURCE_DIR@/src/ \ + @CMAKE_CURRENT_SOURCE_DIR@/src/util \ + @CMAKE_CURRENT_SOURCE_DIR@/src/script \ + @CMAKE_CURRENT_SOURCE_DIR@/src/script/common \ + @CMAKE_CURRENT_SOURCE_DIR@/src/script/cpp_api \ + @CMAKE_CURRENT_SOURCE_DIR@/src/script/lua_api +RECURSIVE = NO + +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +GENERATE_LATEX = NO +PAPER_TYPE = a4wide + +HAVE_DOT = @DOXYGEN_DOT_FOUND@ +CALL_GRAPH = YES +CALLER_GRAPH = YES +MAX_DOT_GRAPH_DEPTH = 3 +DOT_MULTI_TARGETS = YES + diff --git a/doc/README.android b/doc/README.android new file mode 100644 index 0000000..0b17647 --- /dev/null +++ b/doc/README.android @@ -0,0 +1,130 @@ +Minetest Android port +===================== +Date: 2014 06 28 + +Controls +-------- +The Android port doesn't support everything you can do on PC due to the +limited capabilities of common devices. What can be done is described +below: + +While you're playing the game normally (that is, no menu or inventory is +shown), the following controls are available: +* Look around: touch screen and slide finger +* double tap: place a node or use selected item +* long tap: dig node +* touch shown buttons: press button +* Buttons: +** left upper corner: chat +** right lower corner: jump +** right lower corner: crouch +** left lower corner: walk/step... + left up right + down +** left lower corner: display inventory + +When a menu or inventory is displayed: +* double tap outside menu area: close menu +* tap on an item stack: select that stack +* tap on an empty slot: if you selected a stack already, that stack is placed here +* drag and drop: touch stack and hold finger down, move the stack to another + slot, tap another finger while keeping first finger on screen + --> places a single item from dragged stack into current (first touched) slot + +Special settings +---------------- +There are some settings especially useful for Android users. Minetest's config +file can usually be found at /mnt/sdcard/Minetest. + +* gui_scaling: this is a user-specified scaling factor for the GUI- In case + main menu is too big or small on your device, try changing this + value. +* inventory_image_hack: if your inventory items are messed up, try setting + this to true + +Known issues +------------ +Not all issues are fixed by now: + +* Unable to exit from volume menu -- don't use the volume menu, use Android's + volume controls instead. +* 512 MB RAM seems to be inadequate -- this depends on the server you join. + Try to play on more lightweight servers. + +Versioning +---------- +Android version numbers are 4 digits instead of Minetest's 3 digits. The last +number of Android's version represents the Android internal version code. This +version code is strictly incremental. It's incremented for each official +Minetest Android build. + +E.g. prerelease Minetest Android builds have been 0.4.9.3, while the first +official version most likely will be 0.4.10.4 + +Requirements +------------ + +In order to build, your PC has to be set up to build Minetest in the usual +manner (see the regular Minetest documentation for how to get this done). +In addition to what is required for Minetest in general, you will need the +following software packages. The version number in parenthesis denotes the +version that was tested at the time this README was drafted; newer/older +versions may or may not work. + +* android SDK (x86_64 20131030) +* android NDK (r9d) +* wget (1.13.4) + +Additionally, you'll need to have an Internet connection available on the +build system, as the Android build will download some source packages. + +Build +----- + +Debug build: +* Enter "build/android" subdirectory +* Execute "make" +* Answer the questions about where SDK and NDK are located on your filesystem +* Wait for build to finish + +After the build is finished, the resulting apk can be fond in +build/android/bin/. It will be called Minetest-debug.apk + +Release build: + +* In order to make a release build you'll have to have a keystore setup to sign + the resulting apk package. How this is done is not part of this README. There + are different tutorials on the web explaining how to do it + - choose one yourself. + +* Once your keystore is setup, enter build/android subdirectory and create a new + file "ant.properties" there. Add following lines to that file: + + > key.store= + > key.alias=Minetest + +* Execute "make release" +* Enter your keystore as well as your Mintest key password once asked. Be + careful it's shown on console in clear text! +* The result can be found at "bin/Minetest-release.apk" + +Other things that may be nice to know +------------ +* The environment for Android development tools is saved within Android build + build folder. If you want direct access to it do: + + > make envpaths + > . and_env + + After you've done this you'll have your path and path variables set correct + to use adb and all other Android development tools + +* You can build a single dependency by calling make and the dependency's name, + e.g.: + + > make irrlicht + +* You can completely cleanup a dependency by calling make and the "clean" target, + e.g.: + + > make clean_irrlicht diff --git a/doc/fst_api.txt b/doc/fst_api.txt new file mode 100644 index 0000000..c8bcb1e --- /dev/null +++ b/doc/fst_api.txt @@ -0,0 +1,171 @@ +Formspec toolkit api 0.0.3 +========================== + +Formspec toolkit is a set of functions to create basic ui elements. + + +File: fst/ui.lua +---------------- + +ui.lua adds base ui interface to add additional components to. + +ui.add(component) -> returns name of added component +^ add component to ui +^ component: component to add + +ui.delete(component) -> true/false if a component was deleted or not +^ remove a component from ui +^ component: component to delete + +ui.set_default(name) +^ set component to show if not a single component is set visible +^ name: name of component to set as default + +ui.find_by_name(name) --> returns component or nil +^ find a component within ui +^ name: name of component to look for + +File: fst/tabview.lua +--------------------- + +tabview_create(name, size, tabheaderpos) --> returns tabview component +^ create a new tabview component +^ name: name of tabview (has to be unique per ui) +^ size: size of tabview + { + x, + y + } +^ tabheaderpos: upper left position of tabheader (relative to upper left fs corner) + { + x, + y + } + +Class reference tabview: + +methods: +- add_tab(tab) + ^ add a tab to this tabview + ^ tab: + { + name = "tabname", -- name of tab to create + caption = "tab caption", -- text to show for tab header + cbf_button_handler = function(tabview, fields, tabname, tabdata), -- callback for button events + --TODO cbf_events = function(tabview, event, tabname), -- callback for events + cbf_formspec = function(tabview, name, tabdata), -- get formspec + tabsize = + { + x, -- x width + y -- y height + }, -- special size for this tab (only relevant if no parent for tabview set) + on_change = function(type,old_tab,new_tab) -- called on tab chang, type is "ENTER" or "LEAVE" + } +- set_autosave_tab(value) + ^ tell tabview to automatically save current tabname as "tabview_name"_LAST + ^ value: true/false +- set_tab(name) + ^ set's tab to tab named "name", returns true/false on success + ^ name: name of tab to set +- set_global_event_handler(handler) + ^ set a handler to be called prior calling tab specific event handler + ^ handler: function(tabview,event) --> returns true to finish event processing false to continue +- set_global_button_handler(handler) + ^ set a handler to be called prior calling tab specific button handler + ^ handler: function(tabview,fields) --> returns true to finish button processing false to continue +- set_parent(parent) + ^ set parent to attach tabview to. TV's with parent are hidden if their parent + is hidden and they don't set their specified size. + ^ parent: component to attach to +- show() + ^ show tabview +- hide() + ^ hide tabview +- delete() + ^ delete tabview +- set_fixed_size(state) + ^ true/false set to fixed size, variable size + +File: fst/dialog.lua +--------------------- +Only one dialog can be shown at a time. If a dialog is closed it's parent is +gonna be activated and shown again. + +dialog_create(name, cbf_formspec, cbf_button_handler, cbf_events) +^ create a dialog component +^ name: name of component (unique per ui) +^ cbf_formspec: function to be called to get formspec + function(dialogdata) +^ cbf_button_handler: function to handle buttons + function(dialog, fields) +^ cbf_events: function to handle events + function(dialog, event) + +Class reference dialog: + +methods: +- set_parent(parent) + ^ set parent to attach a dialog to + ^ parent: component to attach to +- show() + ^ show dialog +- hide() + ^ hide dialog +- delete() + ^ delete dialog from ui + +members: +- data + ^ variable data attached to this dialog +- parent + ^ parent component to return to on exit + +File: fst/buttonbar.lua +----------------------- + +buttonbar_create(name, cbf_buttonhandler, pos, orientation, size) +^ create a buttonbar +^ name: name of component (unique per ui) +^ cbf_buttonhandler: function to be called on button pressed + function(buttonbar,buttonname,buttondata) +^ pos: position relative to upper left of current shown formspec + { + x, + y + } +^ orientation: "vertical" or "horizontal" +^ size: size of bar + { + width, + height + } + +Class reference buttonbar: + +methods: +- add_button(btn_id, caption, button_image) +- set_parent(parent) + ^ set parent to attach a buttonbar to + ^ parent: component to attach to +- show() + ^ show buttonbar +- hide() + ^ hide buttonbar +- delete() + ^ delete buttonbar from ui + +Developer doc: +============== +Skeleton for any component: +{ + name = "some id", -- unique id + type = "toplevel", -- type of component + -- toplevel: component can be show without additional components + -- addon: component is an addon to be shown along toplevel component + hide = function(this) end, -- called to hide the component + show = function(this) end, -- called to show the component + delete = function(this) end, -- called to delete component from ui + handle_buttons = function(this,fields) -- called upon button press + handle_events = function(this,event) -- called upon event reception + get_formspec = function(this) -- has to return formspec to be displayed +} 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..d7e9560 --- /dev/null +++ b/doc/lua_api.txt @@ -0,0 +1,3320 @@ +Minetest Lua Modding API Reference 0.4.12 +========================================= +* More information at +* Developer Wiki: + +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 +[Programming in Lua](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: `$HOME/.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 = + +e.g. + + 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 (e.g. the distributed windows version): + +* `minetest-0.4.x/games/gameid/mods/` +* `minetest-0.4.x/mods/` (User-installed mods) +* `minetest-0.4.x/worlds/worldname/worldmods/` + +On an installed version on Linux: + +* `/usr/share/minetest/games/gameid/mods/` +* `$HOME/.minetest/mods/` (User-installed mods) +* `$HOME/.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 e.g. 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 + | |-- models + | |-- 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` +A File containing description to be shown within mainmenu. + +### `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. + +### `models` +Models for entities or meshnodes. + +### `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 +In the mod `experimental`, there is the ideal item/node/entity name `tnt`. +So 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, e.g. 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_`, e.g. 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: + +* e.g. `foomod_foothing.png` +* e.g. `foomod_foothing` + +Texture modifiers +----------------- +There are various texture modifiers that can be used +to generate textures on-the-fly. + +### Texture overlaying +Textures can be overlaid by putting a `^` between them. + +Example: + + default_dirt.png^default_grass_side.png + +`default_grass_side.png` is overlayed over `default_dirt.png`. + +### Texture grouping +Textures can be grouped together by enclosing them in `(` and `)`. + +Example: `cobble.png^(thing1.png^thing2.png)` + +A texture for `thing1.png^thing2.png` is created and the resulting +texture is overlaid over `cobble.png`. + +### Advanced texture modifiers + +#### `[crack::

` +* `` = animation frame count +* `

` = current animation frame + +Draw a step of the crack animation on the texture. + +Example: + + default_cobble.png^[crack:10:1 + +#### `[combine:x:,=:,=` +* `` = width +* `` = height +* ``/`` = x positions +* ``/`` = y positions +* ``/`` = textures to combine + +Create a texture of size `` times `` and blit `` to (``,``) +and blit `` to (``,``). + +Example: + + [combine:16x32:0,0=default_cobble.png:0,16=default_wood.png + +#### `[brighten` +Brightens the texture. + +Example: + + tnt_tnt_side.png^[brighten + +#### `[noalpha` +Makes the texture completely opaque. + +Example: + + default_leaves.png^[noalpha + +#### `[makealpha:,,` +Convert one color to transparency. + +Example: + + default_cobble.png^[makealpha:128,128,128 + +#### `[transform` +* `` = transformation(s) to apply + +Rotates and/or flips the image. + +`` can be a number (between 0 and 7) or a transform name. +Rotations are counter-clockwise. + + 0 I identity + 1 R90 rotate by 90 degrees + 2 R180 rotate by 180 degrees + 3 R270 rotate by 270 degrees + 4 FX flip X + 5 FXR90 flip X then rotate by 90 degrees + 6 FY flip Y + 7 FYR90 flip Y then rotate by 90 degrees + +Example: + + default_stone.png^[transformFXR90 + +#### `[inventorycube{{{` +`^` is replaced by `&` in texture names. + +Create an inventory cube texture using the side textures. + +Example: + + [inventorycube{grass.png{dirt.png&grass_side.png{dirt.png&grass_side.png + +Creates an inventorycube with `grass.png`, `dirt.png^grass_side.png` and +`dirt.png^grass_side.png` textures + +#### `[lowpart::` +Blit the lower ``% part of `` on the texture. + +Example: + + base.png^[lowpart:25:overlay.png + +#### `[verticalframe::` +* `` = animation frame count +* `` = current animation frame + +Crops the texture to a frame of a vertical animation. + +Example: + + default_torch_animated.png^[verticalframe:16:8 + +#### `[mask:` +Apply a mask to the base image. + +The mask is applied using binary AND. + +#### `[colorize::` +Colorize the textures with the given color. +`` is specified as a `ColorString`. +`` is an int ranging from 0 to 255, and specifies how much of the +color to apply. If ommitted, the alpha will be used. + +Sounds +------ +Only Ogg Vorbis 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_`, e.g. 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 location-less on all clients + { + gain = 1.0, -- default + } + -- Play location-less 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` +* e.g. `""` +* e.g. `"default_place_node"` +* e.g. `{}` +* e.g. `{name="default_place_node"}` +* e.g. `{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)` + * added to `minetest.registered_entities[name]` + +* `minetest.register_node(name, node definition)` + * added to `minetest.registered_items[name]` + * added to `minetest.registered_nodes[name]` + +* `minetest.register_tool(name, item definition)` + * added to `minetest.registered_items[name]` + +* `minetest.register_craftitem(name, item definition)` + * added to `minetest.registered_items[name]` + +* `minetest.register_ore(ore definition)` + * returns an integer uniquely identifying the registered ore + * added to `minetest.registered_ores` with the key of `ore.name` + * if `ore.name` is nil, the key is the returned ID + +* `minetest.register_decoration(decoration definition)` + * returns an integer uniquely identifying the registered decoration + * added to `minetest.registered_decorations` with the key of `decoration.name` + * if `decoration.name` is nil, the key is the returned ID + +* `minetest.clear_registered_ores()` + * clears all ores currently registered + +* `minetest.clear_registered_decorations()` + * clears all decorations currently registered + +Note that in some cases you will stumble upon things that are not contained +in these tables (e.g. 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 its upper and lower 4 bits + respectively. Allows light to propagate from or through the node with + light value falling by 1 per node. This is essential for a light source + node to spread its light. + +`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" + collision_box = { + type = "fixed", + fixed = { + {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}, + }, + }, + ^ defines list of collision boxes for the node. If empty, collision boxes + will be the same as nodeboxes, in case of any other nodes will be full cube + as in the example above. + +Nodes can also contain extra data. See "Node Metadata". + +Node drawtypes +--------------- +There are a bunch of different looking node types. + +Look for examples in `games/minimal` or `games/minetest_game`. + +* `normal` +* `airlike` +* `liquid` +* `flowingliquid` +* `glasslike` +* `glasslike_framed` +* `glasslike_framed_optional` +* `allfaces` +* `allfaces_optional` +* `torchlike` +* `signlike` +* `plantlike` +* `firelike` +* `fencelike` +* `raillike` +* `nodebox` -- See below. (**Experimental!**) +* `mesh` -- use models for nodes + +`*_optional` drawtypes need less rendering time if deactivated (always client side). + +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 `type = "fixed"`, but `y2` will be automatically +set to level from `param2`. + + +Meshes +------ +If drawtype `mesh` is used, tiles should hold model materials textures. +Only static meshes are implemented. +For supported model formats see Irrlicht engine documentation. + + +Noise Parameters +---------------- +Noise Parameters, or commonly called "`NoiseParams`", define the properties of +perlin noise. + +### `offset` +Offset that the noise is translated by (i.e. added) after calculation. + +### `scale` +Factor that the noise is scaled by (i.e. multiplied) after calculation. + +### `spread` +Vector containing values by which each coordinate is divided by before calculation. +Higher spread values result in larger noise features. + +A value of `{x=250, y=250, z=250}` is common. + +### `seed` +Random seed for the noise. Add the world seed to a seed offset for world-unique noise. +In the case of `minetest.get_perlin()`, this value has the world seed automatically added. + +### `octaves` +Number of times the noise gradient is accumulated into the noise. + +Increase this number to increase the amount of detail in the resulting noise. + +A value of `6` is common. + +### `persistence` +Factor by which the effect of the noise gradient function changes with each successive octave. + +Values less than `1` make the details of successive octaves' noise diminish, while values +greater than `1` make successive octaves stronger. + +A value of `0.6` is common. + +### `lacunarity` +Factor by which the noise feature sizes change with each successive octave. + +A value of `2.0` is common. + +### `flags` +Leave this field unset for no special handling. + +Currently supported are `defaults`, `eased` and `absvalue`. + +#### `defaults` +Specify this if you would like to keep auto-selection of eased/not-eased while specifying +some other flags. + +#### `eased` +Maps noise gradient values onto a quintic S-curve before performing interpolation. +This results in smooth, rolling noise. Disable this (`noeased`) for sharp-looking noise. +If no flags are specified (or defaults is), 2D noise is eased and 3D noise is not eased. + +#### `absvalue` +Accumulates the absolute value of each noise gradient result. + +Noise parameters format example for 2D or 3D perlin noise or perlin noise maps: + np_terrain = { + offset = 0, + scale = 1, + spread = {x=500, y=500, z=500}, + seed = 571347, + octaves = 5, + persist = 0.63, + lacunarity = 2.0, + flags = "defaults, absvalue" + } + ^ A single noise parameter table can be used to get 2D or 3D noise, + when getting 2D noise spread.z is ignored. + + +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_threshold`, 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 threshold, 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. + +### `blob` +Creates a deformed sphere of ore according to 3d perlin noise described by +`noise_params`. The maximum size of the blob is `clust_size`, and +`clust_scarcity` has the same meaning as with the `scatter` type. +### `vein +Creates veins of ore varying in density by according to the intersection of two +instances of 3d perlin noise with diffferent seeds, both described by +`noise_params`. `random_factor` varies the influence random chance has on +placement of an ore inside the vein, which is `1` by default. Note that +modifying this parameter may require adjusting `noise_threshhold`. +The parameters `clust_scarcity`, `clust_num_ores`, and `clust_size` are ignored +by this ore type. This ore type is difficult to control since it is sensitive +to small changes. The following is a decent set of parameters to work from: + + noise_params = { + offset = 0, + scale = 3, + spread = {x=200, y=200, z=200}, + seed = 5390, + octaves = 4, + persist = 0.5, + flags = "eased", + }, + noise_threshhold = 1.6 + +WARNING: Use this ore type *very* sparingly since it is ~200x more +computationally expensive than any other ore. + +Ore attributes +-------------- +See section "Flag Specifier Format". + +Currently supported flags: `absheight` + +### `absheight` +Also produce this same ore between the height range of `-y_max` and `-y_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 1 times `H` times 1 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, +papyri, 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 +-------------------- +See section "Flag Specifier Format". + +Currently supported flags: `place_center_x`, `place_center_y`, + `place_center_z`, `force_placement`. + +* `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. +* `force_placement`: Schematic nodes other than "ignore" will replace existing nodes. + + +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. + +**Note**: `offset` _will_ adapt to screen DPI as well as user defined scaling factor! + +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. +* `size`: If used, will force full-image size to this value (override texture pack image size) + +### `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` + +### `waypoint` +Displays distance to selected world position. + +* `name`: The name of the waypoint. +* `text`: Distance suffix. Can be blank. +* `number:` An integer containing the RGB value of the color used to draw the text. +* `world_pos`: World position of the waypoint. + +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}` + +Flag Specifier Format +--------------------- +Flags using the standardized flag specifier format can be specified in either of +two ways, by string or table. + +The string format is a comma-delimited set of flag names; whitespace and +unrecognized flag fields are ignored. Specifying a flag in the string sets the +flag, and specifying a flag prefixed by the string `"no"` explicitly +clears the flag from whatever the default may be. + +In addition to the standard string flag format, the schematic flags field can +also be a table of flag names to boolean values representing whether or not the +flag is set. Additionally, if a field with the flag name prefixed with `"no"` +is present, mapped to a boolean of any value, the specified flag is unset. + +E.g. A flag field of value + + {place_center_x = true, place_center_y=false, place_center_z=true} + +is equivalent to + + {place_center_x = true, noplace_center_y=true, place_center_z=true} + +which is equivalent to + + "place_center_x, noplace_center_y, place_center_z" + +or even + + "place_center_x, place_center_z" + +since, by default, no schematic attributes are set. + +Items +----- + +### Item types +There are three kinds of items: nodes, tools and craftitems. + +* 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. + +### Item formats +Items and item stacks can exist in three formats: Serializes, table format +and `ItemStack`. + +#### Serialized +This is called "stackstring" or "itemstring": + +* e.g. `'default:dirt 5'` +* e.g. `'default:pick_wood 21323'` +* e.g. `'default:apple'` + +#### Table format +Examples: + +5 dirt nodes: + + {name="default:dirt", count=5, wear=0, metadata=""} + +A wooden pick about 1/3 worn out: + + {name="default:pick_wood", count=1, wear=21323, metadata=""} + +An apple: + + {name="default:apple", count=1, wear=0, metadata=""} + +#### `ItemStack` +A native C++ 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 (e.g. 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 (e.g. {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) + } + +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 e.g. a weapon of a lower level make much less + damage, and get worn 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 with same group value + connect to each other + +### 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; e.g. leaves, small + plants, wire, sheets of metal +* `choppy`: something that can be cut using force; e.g. 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 e.g. 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 definition +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 e.g. 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. (e.g. 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 fulfil 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 inexistent 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 (e.g. 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` equals `nil` and `puncher` does not equal `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", + "size[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 = "size[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 + + size[8,9] + list[context;main;0,0;8,4;] + list[current_player;main;0,5;8,4;] + +#### Furnace + + size[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 + + size[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 +* `fixed_size`: `true`/`false` (optional) +* deprecated: `invsize[,;]` + +#### `list[;;,;,;]` +* Show an inventory list + +#### `list[;;,;,;]` +* Show an inventory list + +#### `listcolors[;]` +* Sets background color of slots as `ColorString` +* Sets background color of slots on mouse hovering + +#### `listcolors[;;]` +* Sets background color of slots as `ColorString` +* Sets background color of slots on mouse hovering +* Sets color of slots border + +#### `listcolors[;;;;]` +* Sets background color of slots as `ColorString` +* Sets background color of slots on mouse hovering +* Sets color of slots border +* Sets default background color of tooltips +* Sets default font color of tooltips + +#### `tooltip[;;,]` +* Adds tooltip for an element +* `` tooltip background color as `ColorString` (optional) +* `` tooltip font color as `ColorString` (optional) + +#### `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 as `ColorString` +* 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 times 16px times 4 times 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 times 16px times 4 times 16px +* If `true` the background is clipped to formspec size + (`x` and `y` are used as offset values, `w` and `h` are ignored) + +#### `pwdfield[,;,;;

, + port = + }, +} +core.delete_favorite(id, location) -> success + +Logging: +core.debug(line) (possible in async calls) +^ Always printed to stderr and logfile (print() is redirected here) +core.log(line) (possible in async calls) +core.log(loglevel, line) (possible in async calls) +^ loglevel one of "error", "action", "info", "verbose" + +Settings: +core.setting_set(name, value) +core.setting_get(name) -> string or nil (possible in async calls) +core.setting_setbool(name, value) +core.setting_getbool(name) -> bool or nil (possible in async calls) +core.setting_save() -> nil, save all settings to config file + +Worlds: +core.get_worlds() -> list of worlds (possible in async calls) +^ returns { + [1] = { + path = , + name = , + gameid = , + }, +} +core.create_world(worldname, gameid) +core.delete_world(index) + +Helpers: +core.gettext(string) -> string +^ look up the translation of a string in the gettext message catalog +fgettext_ne(string, ...) +^ call core.gettext(string), replace "$1"..."$9" with the given +^ extra arguments and return the result +fgettext(string, ...) -> string +^ same as fgettext_ne(), but calls core.formspec_escape before returning result +core.parse_json(string[, nullvalue]) -> something (possible in async calls) +^ see core.parse_json (lua_api.txt) +dump(obj, dumped={}) +^ Return object serialized as a string +string:split(separator) +^ eg. string:split("a,b", ",") == {"a","b"} +string:trim() +^ eg. string.trim("\n \t\tfoo bar\t ") == "foo bar" +core.is_yes(arg) (possible in async calls) +^ returns whether arg can be interpreted as yes + +Version compat: +core.get_min_supp_proto() +^ returns the minimum supported network protocol version +core.get_max_supp_proto() +^ returns the maximum supported network protocol version + +Async: +core.handle_async(async_job,parameters,finished) +^ execute a function asynchronously +^ async_job is a function receiving one parameter and returning one parameter +^ parameters parameter table passed to async_job +^ finished function to be called once async_job has finished +^ the result of async_job is passed to this function + +Limitations of Async operations + -No access to global lua variables, don't even try + -Limited set of available functions + e.g. No access to functions modifying menu like core.start,core.close, + core.file_open_dialog + + +Class reference +---------------- +Settings: see lua_api.txt diff --git a/doc/minetest.6 b/doc/minetest.6 new file mode 100644 index 0000000..cd818e1 --- /dev/null +++ b/doc/minetest.6 @@ -0,0 +1,107 @@ +.\" Minetest man page +.TH minetest 6 "10 September 2013" "" "" + +.SH NAME +minetest \- Multiplayer infinite-world block sandbox + +.SH SYNOPSIS +.B minetest +[ OPTION ... ] + +.SH DESCRIPTION +.B Minetest +is one of the first InfiniMiner/Minecraft(/whatever) inspired games (started October 2010), with a goal of taking the survival multiplayer gameplay to a slightly different direction. +.PP +The main design philosophy is to keep it technically simple, stable and portable. It will be kept lightweight enough to run on fairly old hardware. + +.SH OPTIONS +.TP +\-\-address +Address to connect to +.TP +\-\-config +Load configuration from specified file +.TP +\-\-disable\-unittests +Disable unit tests +.TP +\-\-enable\-unittests +Enable unit tests +.TP +\-\-gameid +Set gameid +.TP +\-\-go +Disable main menu +.TP +\-\-help +Show allowed options +.TP +\-\-version +Show version information +.TP +\-\-logfile +Set logfile path (debug.txt) +.TP +\-\-map\-dir +Same as \-\-world (deprecated) +.TP +\-\-name +Set player name +.TP +\-\-password +Set password +.TP +\-\-port +Set network port (UDP) to use +.TP +\-\-random\-input +Enable random user input, for testing +.TP +\-\-server +Run dedicated server +.TP +\-\-speedtests +Run speed tests +.TP +\-\-videomodes +List available video modes +.TP +\-\-info +Print more information to console +.TP +\-\-verbose +Print even more information to console +.TP +\-\-trace +Print enormous amounts of information to console +.TP +\-\-world +Set world path +.TP +\-\-migrate +Migrate from current map backend to another. Possible values are sqlite3 +and leveldb. Only works when using \-\-server. + +.SH ENVIRONMENT VARIABLES + +.TP +MINETEST_SUBGAME_PATH +Colon delimited list of directories to search for subgames. + +.SH BUGS +Please report all bugs to Perttu Ahola . + +.SH AUTHOR +.PP +Perttu Ahola +and contributors. +.PP +This man page was originally written by +Juhani Numminen . + +.SH WWW +http://www.minetest.net/ + +.SH "SEE ALSO" +.BR minetestserver(6) diff --git a/doc/minetestserver.6 b/doc/minetestserver.6 new file mode 100644 index 0000000..1d4a5f8 --- /dev/null +++ b/doc/minetestserver.6 @@ -0,0 +1,77 @@ +.\" Minetestserver man page +.TH minetestserver 6 "10 September 2013" "" "" + +.SH NAME +minetestserver \- Minetest server + +.SH SYNOPSIS +.B minetestserver +[ OPTION ... ] + +.SH DESCRIPTION +.B Minetest +is one of the first InfiniMiner/Minecraft(/whatever) inspired games (started October 2010), with a goal of taking the survival multiplayer gameplay to a slightly different direction. +.PP +The main design philosophy is to keep it technically simple, stable and portable. It will be kept lightweight enough to run on fairly old hardware. + +.SH OPTIONS +.TP +\-\-config +Load configuration from specified file +.TP +\-\-disable\-unittests +Disable unit tests +.TP +\-\-enable\-unittests +Enable unit tests +.TP +\-\-gameid +Set gameid +.TP +\-\-help +Show allowed options +.TP +\-\-version +Show version information +.TP +\-\-logfile +Set logfile path (debug.txt) +.TP +\-\-map\-dir +Same as \-\-world (deprecated) +.TP +\-\-port +Set network port (UDP) to use +.TP +\-\-info +Print more information to console +.TP +\-\-verbose +Print even more information to console +.TP +\-\-trace +Print enormous amounts of information to console +.TP +\-\-world +Set world path +.TP +\-\-migrate +Migrate from current map backend to another. Possible values are sqlite3 +and leveldb. + +.SH BUGS +Please report all bugs to Perttu Ahola . + +.SH AUTHOR +.PP +Perttu Ahola +and contributors. +.PP +This man page was originally written by +Juhani Numminen . + +.SH WWW +http://www.minetest.net/ + +.SH "SEE ALSO" +.BR minetest(6) diff --git a/doc/old/ancient_main_comment.txt b/doc/old/ancient_main_comment.txt new file mode 100644 index 0000000..d7b0e30 --- /dev/null +++ b/doc/old/ancient_main_comment.txt @@ -0,0 +1,345 @@ +------------------------------------------------------------------ +The ancient comment from the beginning of main.cpp is stored here. +------------------------------------------------------------------ + +/* +=============================== NOTES ============================== +NOTE: Things starting with TODO are sometimes only suggestions. + +NOTE: iostream.imbue(std::locale("C")) is very slow +NOTE: Global locale is now set at initialization + +NOTE: If VBO (EHM_STATIC) is used, remember to explicitly free the + hardware buffer (it is not freed automatically) + +NOTE: A random to-do list saved here as documentation: +A list of "active blocks" in which stuff happens. (+=done) + + Add a never-resetted game timer to the server + + Add a timestamp value to blocks + + The simple rule: All blocks near some player are "active" + - Do stuff in real time in active blocks + + Handle objects + - Grow grass, delete leaves without a tree + - Spawn some mobs based on some rules + - Transform cobble to mossy cobble near water + - Run a custom script + - ...And all kinds of other dynamic stuff + + Keep track of when a block becomes active and becomes inactive + + When a block goes inactive: + + Store objects statically to block + + Store timer value as the timestamp + + When a block goes active: + + Create active objects out of static objects + - Simulate the results of what would have happened if it would have + been active for all the time + - Grow a lot of grass and so on + + Initially it is fine to send information about every active object + to every player. Eventually it should be modified to only send info + about the nearest ones. + + This was left to be done by the old system and it sends only the + nearest ones. + +NOTE: Seeds in 1260:6c77e7dbfd29: +5721858502589302589: + Spawns you on a small sand island with a surface dungeon +2983455799928051958: + Enormous jungle + a surface dungeon at ~(250,0,0) + +Old, wild and random suggestions that probably won't be done: +------------------------------------------------------------- + +SUGG: If player is on ground, mainly fetch ground-level blocks + +SUGG: Expose Connection's seqnums and ACKs to server and client. + - This enables saving many packets and making a faster connection + - This also enables server to check if client has received the + most recent block sent, for example. +SUGG: Add a sane bandwidth throttling system to Connection + +SUGG: More fine-grained control of client's dumping of blocks from + memory + - ...What does this mean in the first place? + +SUGG: A map editing mode (similar to dedicated server mode) + +SUGG: Transfer more blocks in a single packet +SUGG: A blockdata combiner class, to which blocks are added and at + destruction it sends all the stuff in as few packets as possible. +SUGG: Make a PACKET_COMBINED which contains many subpackets. Utilize + it by sending more stuff in a single packet. + - Add a packet queue to RemoteClient, from which packets will be + combined with object data packets + - This is not exactly trivial: the object data packets are + sometimes very big by themselves + - This might not give much network performance gain though. + +SUGG: Precalculate lighting translation table at runtime (at startup) + - This is not doable because it is currently hand-made and not + based on some mathematical function. + - Note: This has been changing lately + +SUGG: A version number to blocks, which increments when the block is + modified (node add/remove, water update, lighting update) + - This can then be used to make sure the most recent version of + a block has been sent to client, for example + +SUGG: Make the amount of blocks sending to client and the total + amount of blocks dynamically limited. Transferring blocks is the + main network eater of this system, so it is the one that has + to be throttled so that RTTs stay low. + +SUGG: Meshes of blocks could be split into 6 meshes facing into + different directions and then only those drawn that need to be + +SUGG: Background music based on cellular automata? + http://www.earslap.com/projectslab/otomata + +SUGG: Simple light color information to air + +SUGG: Server-side objects could be moved based on nodes to enable very + lightweight operation and simple AI + - Not practical; client would still need to show smooth movement. + +SUGG: Make a system for pregenerating quick information for mapblocks, so + that the client can show them as cubes before they are actually sent + or even generated. + +SUGG: Erosion simulation at map generation time + - This might be plausible if larger areas of map were pregenerated + without lighting (which is slow) + - Simulate water flows, which would carve out dirt fast and + then turn stone into gravel and sand and relocate it. + - How about relocating minerals, too? Coal and gold in + downstream sand and gravel would be kind of cool + - This would need a better way of handling minerals, mainly + to have mineral content as a separate field. the first + parameter field is free for this. + - Simulate rock falling from cliffs when water has removed + enough solid rock from the bottom + +SUGG: For non-mapgen FarMesh: Add a per-sector database to store surface + stuff as simple flags/values + - Light? + - A building? + And at some point make the server send this data to the client too, + instead of referring to the noise functions + - Ground height + - Surface ground type + - Trees? + +Gaming ideas: +------------- + +- Aim for something like controlling a single dwarf in Dwarf Fortress +- The player could go faster by a crafting a boat, or riding an animal +- Random NPC traders. what else? + +Game content: +------------- + +- When furnace is destroyed, move items to player's inventory +- Add lots of stuff +- Glass blocks +- Growing grass, decaying leaves + - This can be done in the active blocks I guess. + - Lots of stuff can be done in the active blocks. + - Uh, is there an active block list somewhere? I think not. Add it. +- Breaking weak structures + - This can probably be accomplished in the same way as grass +- Player health points + - When player dies, throw items on map (needs better item-on-map + implementation) +- Cobble to get mossy if near water +- More slots in furnace source list, so that multiple ingredients + are possible. +- Keys to chests? + +- The Treasure Guard; a big monster with a hammer + - The hammer does great damage, shakes the ground and removes a block + - You can drop on top of it, and have some time to attack there + before he shakes you off + +- Maybe the difficulty could come from monsters getting tougher in + far-away places, and the player starting to need something from + there when time goes by. + - The player would have some of that stuff at the beginning, and + would need new supplies of it when it runs out + +- A bomb +- A spread-items-on-map routine for the bomb, and for dying players + +- Fighting: + - Proper sword swing simulation + - Player should get damage from colliding to a wall at high speed + +Documentation: +-------------- + +Build system / running: +----------------------- + +Networking and serialization: +----------------------------- + +SUGG: Fix address to be ipv6 compatible + +User Interface: +--------------- + +Graphics: +--------- + +SUGG: Combine MapBlock's face caches to so big pieces that VBO + can be used + - That is >500 vertices + - This is not easy; all the MapBlocks close to the player would + still need to be drawn separately and combining the blocks + would have to happen in a background thread + +SUGG: Make fetching sector's blocks more efficient when rendering + sectors that have very large amounts of blocks (on client) + - Is this necessary at all? + +SUGG: Draw cubes in inventory directly with 3D drawing commands, so that + animating them is easier. + +SUGG: Option for enabling proper alpha channel for textures + +TODO: Flowing water animation + +TODO: A setting for enabling bilinear filtering for textures + +TODO: Better control of draw_control.wanted_max_blocks + +TODO: Further investigate the use of GPU lighting in addition to the + current one + +TODO: Artificial (night) light could be more yellow colored than sunlight. + - This is technically doable. + - Also the actual colors of the textures could be made less colorful + in the dark but it's a bit more difficult. + +SUGG: Somehow make the night less colorful + +TODO: Occlusion culling + - At the same time, move some of the renderMap() block choosing code + to the same place as where the new culling happens. + - Shoot some rays per frame and when ready, make a new list of + blocks for usage of renderMap and give it a new pointer to it. + +Configuration: +-------------- + +Client: +------- + +TODO: Untie client network operations from framerate + - Needs some input queues or something + - This won't give much performance boost because calculating block + meshes takes so long + +SUGG: Make morning and evening transition more smooth and maybe shorter + +TODO: Don't update all meshes always on single node changes, but + check which ones should be updated + - implement Map::updateNodeMeshes() and the usage of it + - It will give almost always a 4x boost in mesh update performance. + +- A weapon engine + +- Tool/weapon visualization + +FIXME: When disconnected to the menu, memory is not freed properly + +TODO: Investigate how much the mesh generator thread gets used when + transferring map data + +Server: +------- + +SUGG: Make an option to the server to disable building and digging near + the starting position + +FIXME: Server sometimes goes into some infinite PeerNotFoundException loop + +* Fix the problem with the server constantly saving one or a few + blocks? List the first saved block, maybe it explains. + - It is probably caused by oscillating water + - TODO: Investigate if this still happens (this is a very old one) +* Make a small history check to transformLiquids to detect and log + continuous oscillations, in such detail that they can be fixed. + +FIXME: The new optimized map sending doesn't sometimes send enough blocks + from big caves and such +FIXME: Block send distance configuration does not take effect for some reason + +Environment: +------------ + +TODO: Add proper hooks to when adding and removing active blocks + +TODO: Finish the ActiveBlockModifier stuff and use it for something + +Objects: +-------- + +TODO: Get rid of MapBlockObjects and use only ActiveObjects + - Skipping the MapBlockObject data is nasty - there is no "total + length" stored; have to make a SkipMBOs function which contains + enough of the current code to skip them properly. + +SUGG: MovingObject::move and Player::move are basically the same. + combine them. + - NOTE: This is a bit tricky because player has the sneaking ability + - NOTE: Player::move is more up-to-date. + - NOTE: There is a simple move implementation now in collision.{h,cpp} + - NOTE: MovingObject will be deleted (MapBlockObject) + +TODO: Add a long step function to objects that is called with the time + difference when block activates + +Map: +---- + +TODO: Flowing water to actually contain flow direction information + - There is a space for this - it just has to be implemented. + +TODO: Consider smoothening cave floors after generating them + +TODO: Fix make_tree, make_* to use seed-position-consistent pseudorandom + - delta also + +Misc. stuff: +------------ +TODO: Make sure server handles removing grass when a block is placed (etc) + - The client should not do it by itself + - NOTE: I think nobody does it currently... +TODO: Block cube placement around player's head +TODO: Protocol version field +TODO: Think about using same bits for material for fences and doors, for + example + +SUGG: Restart irrlicht completely when coming back to main menu from game. + - This gets rid of everything that is stored in irrlicht's caches. + - This might be needed for texture pack selection in menu + +TODO: Merge bahamada's audio stuff (clean patch available) + +Making it more portable: +------------------------ + +Stuff to do before release: +--------------------------- + +Fixes to the current release: +----------------------------- + +Stuff to do after release: +--------------------------- + +Doing currently: +---------------- + +====================================================================== + +*/ diff --git a/doc/old/changelog.txt b/doc/old/changelog.txt new file mode 100644 index 0000000..1750d71 --- /dev/null +++ b/doc/old/changelog.txt @@ -0,0 +1,147 @@ +Minetest changelog +---------------------- +This should contain all the major changes. +For minor stuff, refer to the commit log of the repository. + +0.3.1: (released on 2011-11-09) +- Fix frustum culling (previous versions have rendered too much stuff that is not actually visible (about 180 degrees, while should have been more like 100.)) +- Add occlusion culling (improves performance a lot) +- Add “3d clouds” on/off checkbox in main menu +- Add “opaque water” on/off checkbox +- Fix some random minor stuff +- Turn mipmapping off (This makes far-away textures a bit noisier but better looking) +- Add Command-line signal handler for Windows (contributed by SpeedProg) +- Fix network layer segmentation fault introduced in 0.3.dev-20111021 +- Fix water-glass and water-lava and lava-glass surfaces + +0.3.0: (released on 2011-11-01) +- Some small fixes +0.3.dev-20111021: +- Modify dungeon masters to only try to shoot players +- Fix object duplication bug at block load/unload bug +- Improve network layer +0.3.dev-20111016: +- Locked chest (contributed) +- Server user limit setting (max_users) +- Wielded tool is shown in HUD (contributed) +- View bobbing (contributed) +- Saplings that drop from leaf blocks and when placed on ground will grow to trees (contributed) +- Optimized map saving (does not re-save everything all the time) +- New mob system and new mob: dungeon master +- Death/respawn screen + +0.2.20110922_3: +- Fix the build for MSVC2010; also released for windows using MSVC2010. + +0.2.20110922_1: +- Make client report a newer version number to the server than 2011-07-31 does and make server disallow old clients + +0.2.20110922: +- Map is saved in an SQLite database file (by Queatz) +- Ladders (MarkTraceur) +- Lava +- Apple trees (sfan5) +- Slightly better looking inventory with transparency +- /me chat command (Oblomov) +- Using chosen map seed possible through fixed_map_seed configuration option (kahrl) +- Fix the long-existed PeerNotFound loop bug +- Some translations and localization-related fixes +- Lots of small fixes + +2011-07-31_3: +- Fixes a bug that made the server to deny non-empty passwords from players connecting the first time + +2011-07-31_2: +- Fixes a bug that caused the server to always read an empty password from the client when a client connected. + +2011-07-31: +- A number of small fixes, build system stuff and such (refer to version control log) +- Map generator no longer crashes at generation limit +- Fixed mapgen producing lots of cut-down trees +- Some minor tweaks in map generator (some contributed) +- Volumetric clouds (contributed) +- Icon added (graphic contributed) +- Key configuration menu (contributed) +- Decorative blocks and items: bookshelf, sandstone, cactus, clay, brick, papyrus, rail, paper, book (contributed) +- Jungles! +- Hotbar is a bit smaller +- Health is now enabled by default; You can now eat cooked rats to heal yourself. +- Finally added sword textures, altough sword is still of no use +- Creative mode now preserves normal mode inventory + +2011-07-04: +- Many small fixes +- Code reorganizing to aid further development +- Renewed map generator + +2011-06-02: +- Password crash on windows fixed +- Optimized server CPU usage a lot +- Furnaces now work also while players are not near to them + +2011-05-29: +- Optimized smooth lighting +- A number of small fixes +- Added clouds and simple skyboxes +- The glass block added +- Added key configuration to config file +- Player privileges on server +- Slightly updated map format +- Player passwords +- All textures first searched from texture_path +- Map directory ("map") has been renamed to "world" (just rename it to load an old world) +- Mouse inversion (invert_mouse) +- Grass doesn't grow immediately anymore +- Fence added + +2011-04-24: +- Smooth lighting with simple ambient occlusion +- Updated main menu + +2011-04-23_0_test: +- Small bug fixes +- Item drop multiplication fixed +- HP added +- Added A simple monster which spawns to dark places at map generation time +- Some code refactoring and cleaning (possibly new bugs) + +2011-04-11: +- Fixed crafting a bit + +2011-04-10_0: +- Asynchronous map generation +- New object system + +2011-04-06: +- Mesh update of node addition/removal is now done asynchronously on client, removing frametime spike +- Node addition/removal is sent directly only to clients that are closer than 100 nodes to the modification. For the others, the modified blocks are set unsent. (and are re-sent when applicable) + +2011-04-05: +- Made furnace usable +- Added cobblestone +- Added wood, stone and steel tools: pickaxes, shovels and axes +- Incremented to version 0.0.2 + +2011-04-04: +- Cleaned client to be completely synchronous, except for the mesh calculation, which is now done with queues in a separate thread. +- Added node metadata support +- Added chests + +2011-02-17: +- Added better handling of textures. Now many file extensions are searched. Also too large textures are not put on the texture atlas, and the construction of the texture atlas is stopped when it is full. + +2011-02-16: +- Better handling of Ctrl-C on POSIX systems + +2011-02-15: +- Fixed a problem of not saving and loading the "lighting expired" value of MapBlocks properly. This caused high server CPU usage. +- Ctrl-C handling on POSIX systems +- Added simple command support to server +- Added settings enable_texture_atlas and texture_path + +2011-02-14: +- Created changelog.txt +- Added sneaking/crouching +- Modified the looks of the hotbar and cleaned code +- Added code to allow generating 3D cube images for inventory + diff --git a/doc/protocol.txt b/doc/protocol.txt new file mode 100644 index 0000000..b151f88 --- /dev/null +++ b/doc/protocol.txt @@ -0,0 +1,108 @@ +Minetest protocol (incomplete, early draft): +Updated 2011-06-18 + +A custom protocol over UDP. +Integers are big endian. +Refer to connection.{h,cpp} for further reference. + +Initialization: +- A dummy reliable packet with peer_id=PEER_ID_INEXISTENT=0 is sent to the server: + - Actually this can be sent without the reliable packet header, too, i guess, + but the sequence number in the header allows the sender to re-send the + packet without accidentally getting a double initialization. + - Packet content: + # Basic header + u32 protocol_id = PROTOCOL_ID = 0x4f457403 + u16 sender_peer_id = PEER_ID_INEXISTENT = 0 + u8 channel = 0 + # Reliable packet header + u8 type = TYPE_RELIABLE = 3 + u16 seqnum = SEQNUM_INITIAL = 65500 + # Original packet header + u8 type = TYPE_ORIGINAL = 1 + # And no actual payload. +- Server responds with something like this: + - Packet content: + # Basic header + u32 protocol_id = PROTOCOL_ID = 0x4f457403 + u16 sender_peer_id = PEER_ID_INEXISTENT = 0 + u8 channel = 0 + # Reliable packet header + u8 type = TYPE_RELIABLE = 3 + u16 seqnum = SEQNUM_INITIAL = 65500 + # Control packet header + u8 type = TYPE_CONTROL = 0 + u8 controltype = CONTROLTYPE_SET_PEER_ID = 1 + u16 peer_id_new = assigned peer id to client (other than 0 or 1) +- Then the connection can be disconnected by sending: + - Packet content: + # Basic header + u32 protocol_id = PROTOCOL_ID = 0x4f457403 + u16 sender_peer_id = whatever was gotten in CONTROLTYPE_SET_PEER_ID + u8 channel = 0 + # Control packet header + u8 type = TYPE_CONTROL = 0 + u8 controltype = CONTROLTYPE_DISCO = 3 + +- Here's a quick untested connect-disconnect done in PHP: +# host: ip of server (use gethostbyname(hostname) to get from a dns name) +# port: port of server +function check_if_minetestserver_up($host, $port) +{ + $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + $timeout = array("sec" => 1, "usec" => 0); + socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout); + $buf = "\x4f\x45\x74\x03\x00\x00\x00\x03\xff\xdc\x01"; + socket_sendto($socket, $buf, strlen($buf), 0, $host, $port); + $buf = socket_read($socket, 1000); + if($buf != "") + { + # We got a reply! read the peer id from it. + $peer_id = substr($buf, 9, 2); + + # Disconnect + $buf = "\x4f\x45\x74\x03".$peer_id."\x00\x00\x03"; + socket_sendto($socket, $buf, strlen($buf), 0, $host, $port); + socket_close($socket); + + return true; + } + return false; +} + +- Here's a Python script for checking if a minetest server is up, confirmed working +#!/usr/bin/env python +import sys, time, socket +address = "" +port = 30000 +if len(sys.argv) <= 1: + print("Usage: %s
" % sys.argv[0]) + exit() +if ':' in sys.argv[1]: + address = sys.argv[1].split(':')[0] + try: + port = int(sys.argv[1].split(':')[1]) + except ValueError: + print("Please specify a valid port") + exit() +else: + address = sys.argv[1] + +try: + start = time.time() + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(2.0) + buf = "\x4f\x45\x74\x03\x00\x00\x00\x01" + sock.sendto(buf, (address, port)) + data, addr = sock.recvfrom(1000) + if data: + peer_id = data[12:14] + buf = "\x4f\x45\x74\x03" + peer_id + "\x00\x00\x03" + sock.sendto(buf, (address, port)) + sock.close() + end = time.time() + print("%s is up (%0.5fms)" % (sys.argv[1],end-start)) + else: + print("%s seems to be down " % sys.argv[1]) +except: + print("%s seems to be down " % sys.argv[1]) diff --git a/fonts/DroidSansFallbackFull.ttf b/fonts/DroidSansFallbackFull.ttf new file mode 100644 index 0000000..a9df005 Binary files /dev/null and b/fonts/DroidSansFallbackFull.ttf differ diff --git a/fonts/liberationmono.ttf b/fonts/liberationmono.ttf new file mode 100644 index 0000000..7260bd6 Binary files /dev/null and b/fonts/liberationmono.ttf differ diff --git a/fonts/liberationsans.ttf b/fonts/liberationsans.ttf new file mode 100644 index 0000000..59d2e25 Binary files /dev/null and b/fonts/liberationsans.ttf differ diff --git a/fonts/lucida_sans_10.xml b/fonts/lucida_sans_10.xml new file mode 100755 index 0000000..d54e9f6 Binary files /dev/null and b/fonts/lucida_sans_10.xml differ diff --git a/fonts/lucida_sans_100.png b/fonts/lucida_sans_100.png new file mode 100755 index 0000000..1c9ca65 Binary files /dev/null and b/fonts/lucida_sans_100.png differ diff --git a/fonts/lucida_sans_11.xml b/fonts/lucida_sans_11.xml new file mode 100755 index 0000000..33d06c3 Binary files /dev/null and b/fonts/lucida_sans_11.xml differ diff --git a/fonts/lucida_sans_110.png b/fonts/lucida_sans_110.png new file mode 100755 index 0000000..7762549 Binary files /dev/null and b/fonts/lucida_sans_110.png differ diff --git a/fonts/lucida_sans_12.xml b/fonts/lucida_sans_12.xml new file mode 100755 index 0000000..382981d Binary files /dev/null and b/fonts/lucida_sans_12.xml differ diff --git a/fonts/lucida_sans_120.png b/fonts/lucida_sans_120.png new file mode 100755 index 0000000..e05ee0f Binary files /dev/null and b/fonts/lucida_sans_120.png differ diff --git a/fonts/lucida_sans_14.xml b/fonts/lucida_sans_14.xml new file mode 100755 index 0000000..99398d7 Binary files /dev/null and b/fonts/lucida_sans_14.xml differ diff --git a/fonts/lucida_sans_140.png b/fonts/lucida_sans_140.png new file mode 100755 index 0000000..5ef95e0 Binary files /dev/null and b/fonts/lucida_sans_140.png differ diff --git a/fonts/lucida_sans_16.xml b/fonts/lucida_sans_16.xml new file mode 100755 index 0000000..b07c111 Binary files /dev/null and b/fonts/lucida_sans_16.xml differ diff --git a/fonts/lucida_sans_160.png b/fonts/lucida_sans_160.png new file mode 100755 index 0000000..cb07e3f Binary files /dev/null and b/fonts/lucida_sans_160.png differ diff --git a/fonts/lucida_sans_18.xml b/fonts/lucida_sans_18.xml new file mode 100755 index 0000000..881aff1 Binary files /dev/null and b/fonts/lucida_sans_18.xml differ diff --git a/fonts/lucida_sans_180.png b/fonts/lucida_sans_180.png new file mode 100755 index 0000000..c739c80 Binary files /dev/null and b/fonts/lucida_sans_180.png differ diff --git a/fonts/lucida_sans_20.xml b/fonts/lucida_sans_20.xml new file mode 100755 index 0000000..329c226 Binary files /dev/null and b/fonts/lucida_sans_20.xml differ diff --git a/fonts/lucida_sans_200.png b/fonts/lucida_sans_200.png new file mode 100755 index 0000000..c3f883f Binary files /dev/null and b/fonts/lucida_sans_200.png differ diff --git a/fonts/lucida_sans_22.xml b/fonts/lucida_sans_22.xml new file mode 100755 index 0000000..14d0cc2 Binary files /dev/null and b/fonts/lucida_sans_22.xml differ diff --git a/fonts/lucida_sans_220.png b/fonts/lucida_sans_220.png new file mode 100755 index 0000000..fb0edfe Binary files /dev/null and b/fonts/lucida_sans_220.png differ diff --git a/fonts/lucida_sans_24.xml b/fonts/lucida_sans_24.xml new file mode 100755 index 0000000..5956f46 Binary files /dev/null and b/fonts/lucida_sans_24.xml differ diff --git a/fonts/lucida_sans_240.png b/fonts/lucida_sans_240.png new file mode 100755 index 0000000..5826e80 Binary files /dev/null and b/fonts/lucida_sans_240.png differ diff --git a/fonts/lucida_sans_26.xml b/fonts/lucida_sans_26.xml new file mode 100755 index 0000000..ae10ff8 Binary files /dev/null and b/fonts/lucida_sans_26.xml differ diff --git a/fonts/lucida_sans_260.png b/fonts/lucida_sans_260.png new file mode 100755 index 0000000..13a9c89 Binary files /dev/null and b/fonts/lucida_sans_260.png differ diff --git a/fonts/lucida_sans_28.xml b/fonts/lucida_sans_28.xml new file mode 100755 index 0000000..c1b3361 Binary files /dev/null and b/fonts/lucida_sans_28.xml differ diff --git a/fonts/lucida_sans_280.png b/fonts/lucida_sans_280.png new file mode 100755 index 0000000..9ffdc01 Binary files /dev/null and b/fonts/lucida_sans_280.png differ diff --git a/fonts/lucida_sans_36.xml b/fonts/lucida_sans_36.xml new file mode 100755 index 0000000..ca300d6 Binary files /dev/null and b/fonts/lucida_sans_36.xml differ diff --git a/fonts/lucida_sans_360.png b/fonts/lucida_sans_360.png new file mode 100755 index 0000000..53b957c Binary files /dev/null and b/fonts/lucida_sans_360.png differ diff --git a/fonts/lucida_sans_4.xml b/fonts/lucida_sans_4.xml new file mode 100755 index 0000000..edc5af9 Binary files /dev/null and b/fonts/lucida_sans_4.xml differ diff --git a/fonts/lucida_sans_40.png b/fonts/lucida_sans_40.png new file mode 100755 index 0000000..954ac0d Binary files /dev/null and b/fonts/lucida_sans_40.png differ diff --git a/fonts/lucida_sans_48.xml b/fonts/lucida_sans_48.xml new file mode 100755 index 0000000..c8c7597 Binary files /dev/null and b/fonts/lucida_sans_48.xml differ diff --git a/fonts/lucida_sans_480.png b/fonts/lucida_sans_480.png new file mode 100755 index 0000000..afc87a5 Binary files /dev/null and b/fonts/lucida_sans_480.png differ diff --git a/fonts/lucida_sans_56.xml b/fonts/lucida_sans_56.xml new file mode 100755 index 0000000..9f2cc83 Binary files /dev/null and b/fonts/lucida_sans_56.xml differ diff --git a/fonts/lucida_sans_560.png b/fonts/lucida_sans_560.png new file mode 100755 index 0000000..04185d8 Binary files /dev/null and b/fonts/lucida_sans_560.png differ diff --git a/fonts/lucida_sans_6.xml b/fonts/lucida_sans_6.xml new file mode 100755 index 0000000..069c9aa Binary files /dev/null and b/fonts/lucida_sans_6.xml differ diff --git a/fonts/lucida_sans_60.png b/fonts/lucida_sans_60.png new file mode 100755 index 0000000..d67af5f Binary files /dev/null and b/fonts/lucida_sans_60.png differ diff --git a/fonts/lucida_sans_8.xml b/fonts/lucida_sans_8.xml new file mode 100755 index 0000000..44f0e6a Binary files /dev/null and b/fonts/lucida_sans_8.xml differ diff --git a/fonts/lucida_sans_80.png b/fonts/lucida_sans_80.png new file mode 100755 index 0000000..726423e Binary files /dev/null and b/fonts/lucida_sans_80.png differ diff --git a/fonts/lucida_sans_9.xml b/fonts/lucida_sans_9.xml new file mode 100755 index 0000000..99aa644 Binary files /dev/null and b/fonts/lucida_sans_9.xml differ diff --git a/fonts/lucida_sans_90.png b/fonts/lucida_sans_90.png new file mode 100755 index 0000000..c13e5d0 Binary files /dev/null and b/fonts/lucida_sans_90.png differ diff --git a/fonts/mono_dejavu_sans_10.xml b/fonts/mono_dejavu_sans_10.xml new file mode 100755 index 0000000..0276ced Binary files /dev/null and b/fonts/mono_dejavu_sans_10.xml differ diff --git a/fonts/mono_dejavu_sans_100.png b/fonts/mono_dejavu_sans_100.png new file mode 100755 index 0000000..28d9e7c Binary files /dev/null and b/fonts/mono_dejavu_sans_100.png differ diff --git a/fonts/mono_dejavu_sans_11.xml b/fonts/mono_dejavu_sans_11.xml new file mode 100755 index 0000000..f727ed2 Binary files /dev/null and b/fonts/mono_dejavu_sans_11.xml differ diff --git a/fonts/mono_dejavu_sans_110.png b/fonts/mono_dejavu_sans_110.png new file mode 100755 index 0000000..4536b51 Binary files /dev/null and b/fonts/mono_dejavu_sans_110.png differ diff --git a/fonts/mono_dejavu_sans_12.xml b/fonts/mono_dejavu_sans_12.xml new file mode 100755 index 0000000..38f6427 Binary files /dev/null and b/fonts/mono_dejavu_sans_12.xml differ diff --git a/fonts/mono_dejavu_sans_120.png b/fonts/mono_dejavu_sans_120.png new file mode 100755 index 0000000..147d9a1 Binary files /dev/null and b/fonts/mono_dejavu_sans_120.png differ diff --git a/fonts/mono_dejavu_sans_14.xml b/fonts/mono_dejavu_sans_14.xml new file mode 100755 index 0000000..b90a349 Binary files /dev/null and b/fonts/mono_dejavu_sans_14.xml differ diff --git a/fonts/mono_dejavu_sans_140.png b/fonts/mono_dejavu_sans_140.png new file mode 100755 index 0000000..f9f0d29 Binary files /dev/null and b/fonts/mono_dejavu_sans_140.png differ diff --git a/fonts/mono_dejavu_sans_16.xml b/fonts/mono_dejavu_sans_16.xml new file mode 100755 index 0000000..3f7d2c2 Binary files /dev/null and b/fonts/mono_dejavu_sans_16.xml differ diff --git a/fonts/mono_dejavu_sans_160.png b/fonts/mono_dejavu_sans_160.png new file mode 100755 index 0000000..0b6af70 Binary files /dev/null and b/fonts/mono_dejavu_sans_160.png differ diff --git a/fonts/mono_dejavu_sans_18.xml b/fonts/mono_dejavu_sans_18.xml new file mode 100755 index 0000000..92865cb Binary files /dev/null and b/fonts/mono_dejavu_sans_18.xml differ diff --git a/fonts/mono_dejavu_sans_180.png b/fonts/mono_dejavu_sans_180.png new file mode 100755 index 0000000..17391f3 Binary files /dev/null and b/fonts/mono_dejavu_sans_180.png differ diff --git a/fonts/mono_dejavu_sans_20.xml b/fonts/mono_dejavu_sans_20.xml new file mode 100755 index 0000000..acd8c77 Binary files /dev/null and b/fonts/mono_dejavu_sans_20.xml differ diff --git a/fonts/mono_dejavu_sans_200.png b/fonts/mono_dejavu_sans_200.png new file mode 100755 index 0000000..58a61d5 Binary files /dev/null and b/fonts/mono_dejavu_sans_200.png differ diff --git a/fonts/mono_dejavu_sans_22.xml b/fonts/mono_dejavu_sans_22.xml new file mode 100755 index 0000000..eafb4de Binary files /dev/null and b/fonts/mono_dejavu_sans_22.xml differ diff --git a/fonts/mono_dejavu_sans_220.png b/fonts/mono_dejavu_sans_220.png new file mode 100755 index 0000000..df6d0d1 Binary files /dev/null and b/fonts/mono_dejavu_sans_220.png differ diff --git a/fonts/mono_dejavu_sans_24.xml b/fonts/mono_dejavu_sans_24.xml new file mode 100755 index 0000000..fc8b623 Binary files /dev/null and b/fonts/mono_dejavu_sans_24.xml differ diff --git a/fonts/mono_dejavu_sans_240.png b/fonts/mono_dejavu_sans_240.png new file mode 100755 index 0000000..0eeb06a Binary files /dev/null and b/fonts/mono_dejavu_sans_240.png differ diff --git a/fonts/mono_dejavu_sans_26.xml b/fonts/mono_dejavu_sans_26.xml new file mode 100755 index 0000000..829f099 Binary files /dev/null and b/fonts/mono_dejavu_sans_26.xml differ diff --git a/fonts/mono_dejavu_sans_260.png b/fonts/mono_dejavu_sans_260.png new file mode 100755 index 0000000..86ce54e Binary files /dev/null and b/fonts/mono_dejavu_sans_260.png differ diff --git a/fonts/mono_dejavu_sans_28.xml b/fonts/mono_dejavu_sans_28.xml new file mode 100755 index 0000000..b5b25bd Binary files /dev/null and b/fonts/mono_dejavu_sans_28.xml differ diff --git a/fonts/mono_dejavu_sans_280.png b/fonts/mono_dejavu_sans_280.png new file mode 100755 index 0000000..da99da8 Binary files /dev/null and b/fonts/mono_dejavu_sans_280.png differ diff --git a/fonts/mono_dejavu_sans_4.xml b/fonts/mono_dejavu_sans_4.xml new file mode 100755 index 0000000..cfebb39 Binary files /dev/null and b/fonts/mono_dejavu_sans_4.xml differ diff --git a/fonts/mono_dejavu_sans_40.png b/fonts/mono_dejavu_sans_40.png new file mode 100755 index 0000000..413817a Binary files /dev/null and b/fonts/mono_dejavu_sans_40.png differ diff --git a/fonts/mono_dejavu_sans_6.xml b/fonts/mono_dejavu_sans_6.xml new file mode 100755 index 0000000..d0e1de2 Binary files /dev/null and b/fonts/mono_dejavu_sans_6.xml differ diff --git a/fonts/mono_dejavu_sans_60.png b/fonts/mono_dejavu_sans_60.png new file mode 100755 index 0000000..7126f4d Binary files /dev/null and b/fonts/mono_dejavu_sans_60.png differ diff --git a/fonts/mono_dejavu_sans_8.xml b/fonts/mono_dejavu_sans_8.xml new file mode 100755 index 0000000..c48bf7c Binary files /dev/null and b/fonts/mono_dejavu_sans_8.xml differ diff --git a/fonts/mono_dejavu_sans_80.png b/fonts/mono_dejavu_sans_80.png new file mode 100755 index 0000000..8ff7924 Binary files /dev/null and b/fonts/mono_dejavu_sans_80.png differ diff --git a/fonts/mono_dejavu_sans_9.xml b/fonts/mono_dejavu_sans_9.xml new file mode 100755 index 0000000..74e8410 Binary files /dev/null and b/fonts/mono_dejavu_sans_9.xml differ diff --git a/fonts/mono_dejavu_sans_90.png b/fonts/mono_dejavu_sans_90.png new file mode 100755 index 0000000..78872dd Binary files /dev/null and b/fonts/mono_dejavu_sans_90.png differ diff --git a/games/minimal/game.conf b/games/minimal/game.conf new file mode 100644 index 0000000..99bfaf0 --- /dev/null +++ b/games/minimal/game.conf @@ -0,0 +1,2 @@ +name = Minimal development test + diff --git a/games/minimal/menu/background.png b/games/minimal/menu/background.png new file mode 100644 index 0000000..ea5fbdc Binary files /dev/null and b/games/minimal/menu/background.png differ diff --git a/games/minimal/menu/icon.png b/games/minimal/menu/icon.png new file mode 100644 index 0000000..8ef6750 Binary files /dev/null and b/games/minimal/menu/icon.png differ diff --git a/games/minimal/mods/bucket/depends.txt b/games/minimal/mods/bucket/depends.txt new file mode 100644 index 0000000..3a7daa1 --- /dev/null +++ b/games/minimal/mods/bucket/depends.txt @@ -0,0 +1,2 @@ +default + diff --git a/games/minimal/mods/bucket/init.lua b/games/minimal/mods/bucket/init.lua new file mode 100644 index 0000000..dcd59ed --- /dev/null +++ b/games/minimal/mods/bucket/init.lua @@ -0,0 +1,95 @@ +-- bucket (Minetest 0.4 mod) +-- A bucket, which can pick up water and lava + +minetest.register_alias("bucket", "bucket:bucket_empty") +minetest.register_alias("bucket_water", "bucket:bucket_water") +minetest.register_alias("bucket_lava", "bucket:bucket_lava") + +minetest.register_craft({ + output = 'bucket:bucket_empty 1', + recipe = { + {'default:steel_ingot', '', 'default:steel_ingot'}, + {'', 'default:steel_ingot', ''}, + } +}) + +bucket = {} +bucket.liquids = {} + +-- Register a new liquid +-- source = name of the source node +-- flowing = name of the flowing node +-- itemname = name of the new bucket item (or nil if liquid is not takeable) +-- inventory_image = texture of the new bucket item (ignored if itemname == nil) +-- This function can be called from any mod (that depends on bucket). +function bucket.register_liquid(source, flowing, itemname, inventory_image) + bucket.liquids[source] = { + source = source, + flowing = flowing, + itemname = itemname, + } + bucket.liquids[flowing] = bucket.liquids[source] + + if itemname ~= nil then + minetest.register_craftitem(itemname, { + inventory_image = inventory_image, + stack_max = 1, + liquids_pointable = true, + on_use = function(itemstack, user, pointed_thing) + -- Must be pointing to node + if pointed_thing.type ~= "node" then + return + end + -- Check if pointing to a liquid + n = minetest.get_node(pointed_thing.under) + if bucket.liquids[n.name] == nil then + -- Not a liquid + minetest.add_node(pointed_thing.above, {name=source}) + elseif n.name ~= source then + -- It's a liquid + minetest.add_node(pointed_thing.under, {name=source}) + end + return {name="bucket:bucket_empty"} + end + }) + end +end + +minetest.register_craftitem("bucket:bucket_empty", { + inventory_image = "bucket.png", + stack_max = 1, + liquids_pointable = true, + on_use = function(itemstack, user, pointed_thing) + -- Must be pointing to node + if pointed_thing.type ~= "node" then + return + end + -- Check if pointing to a liquid source + n = minetest.get_node(pointed_thing.under) + liquiddef = bucket.liquids[n.name] + if liquiddef ~= nil and liquiddef.source == n.name and liquiddef.itemname ~= nil then + minetest.add_node(pointed_thing.under, {name="air"}) + return {name=liquiddef.itemname} + end + end, +}) + +bucket.register_liquid( + "default:water_source", + "default:water_flowing", + "bucket:bucket_water", + "bucket_water.png" +) + +bucket.register_liquid( + "default:lava_source", + "default:lava_flowing", + "bucket:bucket_lava", + "bucket_lava.png" +) + +minetest.register_craft({ + type = "fuel", + recipe = "bucket:bucket_lava", + burntime = 60, +}) diff --git a/games/minimal/mods/bucket/textures/bucket.png b/games/minimal/mods/bucket/textures/bucket.png new file mode 100644 index 0000000..b775a9f Binary files /dev/null and b/games/minimal/mods/bucket/textures/bucket.png differ diff --git a/games/minimal/mods/bucket/textures/bucket_lava.png b/games/minimal/mods/bucket/textures/bucket_lava.png new file mode 100644 index 0000000..889ed61 Binary files /dev/null and b/games/minimal/mods/bucket/textures/bucket_lava.png differ diff --git a/games/minimal/mods/bucket/textures/bucket_water.png b/games/minimal/mods/bucket/textures/bucket_water.png new file mode 100644 index 0000000..a3c9d72 Binary files /dev/null and b/games/minimal/mods/bucket/textures/bucket_water.png differ diff --git a/games/minimal/mods/default/init.lua b/games/minimal/mods/default/init.lua new file mode 100644 index 0000000..01bf65b --- /dev/null +++ b/games/minimal/mods/default/init.lua @@ -0,0 +1,1797 @@ +-- default (Minetest 0.4 mod) +-- Most default stuff + +-- The API documentation in here was moved into doc/lua_api.txt + +WATER_ALPHA = 160 +WATER_VISC = 1 +LAVA_VISC = 7 +LIGHT_MAX = 14 + +-- Definitions made by this mod that other mods can use too +default = {} + +-- Load other files +dofile(minetest.get_modpath("default").."/mapgen.lua") + +-- Set a noticeable inventory formspec for players +minetest.register_on_joinplayer(function(player) + local cb = function(player) + minetest.chat_send_player(player:get_player_name(), "This is the [minimal] \"Minimal Development Test\" game. Use [minetest_game] for the real thing.") + end + minetest.after(2.0, cb, player) +end) + +-- +-- Tool definition +-- + +-- The hand +minetest.register_item(":", { + type = "none", + wield_image = "wieldhand.png", + wield_scale = {x=1,y=1,z=2.5}, + tool_capabilities = { + full_punch_interval = 1.0, + max_drop_level = 0, + groupcaps = { + fleshy = {times={[2]=2.00, [3]=1.00}, uses=0, maxlevel=1}, + crumbly = {times={[2]=3.00, [3]=0.70}, uses=0, maxlevel=1}, + snappy = {times={[3]=0.40}, uses=0, maxlevel=1}, + oddly_breakable_by_hand = {times={[1]=7.00,[2]=4.00,[3]=1.40}, uses=0, maxlevel=3}, + } + } +}) + +minetest.register_tool("default:pick_wood", { + description = "Wooden Pickaxe", + inventory_image = "default_tool_woodpick.png", + tool_capabilities = { + max_drop_level=0, + groupcaps={ + cracky={times={[2]=2.00, [3]=1.20}, uses=10, maxlevel=1} + } + }, +}) +minetest.register_tool("default:pick_stone", { + description = "Stone Pickaxe", + inventory_image = "default_tool_stonepick.png", + tool_capabilities = { + max_drop_level=0, + groupcaps={ + cracky={times={[1]=2.00, [2]=1.20, [3]=0.80}, uses=20, maxlevel=1} + } + }, +}) +minetest.register_tool("default:pick_steel", { + description = "Steel Pickaxe", + inventory_image = "default_tool_steelpick.png", + tool_capabilities = { + max_drop_level=1, + groupcaps={ + cracky={times={[1]=4.00, [2]=1.60, [3]=1.00}, uses=10, maxlevel=2} + } + }, +}) +minetest.register_tool("default:pick_mese", { + description = "Mese Pickaxe", + inventory_image = "default_tool_mesepick.png", + tool_capabilities = { + full_punch_interval = 1.0, + max_drop_level=3, + groupcaps={ + cracky={times={[1]=2.0, [2]=1.0, [3]=0.5}, uses=20, maxlevel=3}, + crumbly={times={[1]=2.0, [2]=1.0, [3]=0.5}, uses=20, maxlevel=3}, + snappy={times={[1]=2.0, [2]=1.0, [3]=0.5}, uses=20, maxlevel=3} + } + }, +}) +minetest.register_tool("default:shovel_wood", { + description = "Wooden Shovel", + inventory_image = "default_tool_woodshovel.png", + tool_capabilities = { + max_drop_level=0, + groupcaps={ + crumbly={times={[1]=2.00, [2]=0.80, [3]=0.50}, uses=10, maxlevel=1} + } + }, +}) +minetest.register_tool("default:shovel_stone", { + description = "Stone Shovel", + inventory_image = "default_tool_stoneshovel.png", + tool_capabilities = { + max_drop_level=0, + groupcaps={ + crumbly={times={[1]=1.20, [2]=0.50, [3]=0.30}, uses=20, maxlevel=1} + } + }, +}) +minetest.register_tool("default:shovel_steel", { + description = "Steel Shovel", + inventory_image = "default_tool_steelshovel.png", + tool_capabilities = { + max_drop_level=1, + groupcaps={ + crumbly={times={[1]=1.00, [2]=0.70, [3]=0.60}, uses=10, maxlevel=2} + } + }, +}) +minetest.register_tool("default:axe_wood", { + description = "Wooden Axe", + inventory_image = "default_tool_woodaxe.png", + tool_capabilities = { + max_drop_level=0, + groupcaps={ + choppy={times={[2]=1.40, [3]=0.80}, uses=10, maxlevel=1}, + fleshy={times={[2]=1.50, [3]=0.80}, uses=10, maxlevel=1} + } + }, +}) +minetest.register_tool("default:axe_stone", { + description = "Stone Axe", + inventory_image = "default_tool_stoneaxe.png", + tool_capabilities = { + max_drop_level=0, + groupcaps={ + choppy={times={[1]=1.50, [2]=1.00, [3]=0.60}, uses=20, maxlevel=1}, + fleshy={times={[2]=1.30, [3]=0.70}, uses=20, maxlevel=1} + } + }, +}) +minetest.register_tool("default:axe_steel", { + description = "Steel Axe", + inventory_image = "default_tool_steelaxe.png", + tool_capabilities = { + max_drop_level=1, + groupcaps={ + choppy={times={[1]=2.00, [2]=1.60, [3]=1.00}, uses=10, maxlevel=2}, + fleshy={times={[2]=1.10, [3]=0.60}, uses=40, maxlevel=1} + } + }, +}) +minetest.register_tool("default:sword_wood", { + description = "Wooden Sword", + inventory_image = "default_tool_woodsword.png", + tool_capabilities = { + full_punch_interval = 1.0, + max_drop_level=0, + groupcaps={ + fleshy={times={[2]=1.10, [3]=0.60}, uses=10, maxlevel=1}, + snappy={times={[2]=1.00, [3]=0.50}, uses=10, maxlevel=1}, + choppy={times={[3]=1.00}, uses=20, maxlevel=0} + } + } +}) +minetest.register_tool("default:sword_stone", { + description = "Stone Sword", + inventory_image = "default_tool_stonesword.png", + tool_capabilities = { + full_punch_interval = 1.0, + max_drop_level=0, + groupcaps={ + fleshy={times={[2]=0.80, [3]=0.40}, uses=20, maxlevel=1}, + snappy={times={[2]=0.80, [3]=0.40}, uses=20, maxlevel=1}, + choppy={times={[3]=0.90}, uses=20, maxlevel=0} + } + } +}) +minetest.register_tool("default:sword_steel", { + description = "Steel Sword", + inventory_image = "default_tool_steelsword.png", + tool_capabilities = { + full_punch_interval = 1.0, + max_drop_level=1, + groupcaps={ + fleshy={times={[1]=2.00, [2]=0.80, [3]=0.40}, uses=10, maxlevel=2}, + snappy={times={[2]=0.70, [3]=0.30}, uses=40, maxlevel=1}, + choppy={times={[3]=0.70}, uses=40, maxlevel=0} + } + } +}) + +-- +-- Crafting definition +-- + +minetest.register_craft({ + output = 'default:wood 4', + recipe = { + {'default:tree'}, + } +}) + +minetest.register_craft({ + output = 'default:stick 4', + recipe = { + {'default:wood'}, + } +}) + +minetest.register_craft({ + output = 'default:fence_wood 2', + recipe = { + {'default:stick', 'default:stick', 'default:stick'}, + {'default:stick', 'default:stick', 'default:stick'}, + } +}) + +minetest.register_craft({ + output = 'default:sign_wall', + recipe = { + {'default:wood', 'default:wood', 'default:wood'}, + {'default:wood', 'default:wood', 'default:wood'}, + {'', 'default:stick', ''}, + } +}) + +minetest.register_craft({ + output = 'default:torch 4', + recipe = { + {'default:coal_lump'}, + {'default:stick'}, + } +}) + +minetest.register_craft({ + output = 'default:pick_wood', + recipe = { + {'default:wood', 'default:wood', 'default:wood'}, + {'', 'default:stick', ''}, + {'', 'default:stick', ''}, + } +}) + +minetest.register_craft({ + output = 'default:pick_stone', + recipe = { + {'default:cobble', 'default:cobble', 'default:cobble'}, + {'', 'default:stick', ''}, + {'', 'default:stick', ''}, + } +}) + +minetest.register_craft({ + output = 'default:pick_steel', + recipe = { + {'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'}, + {'', 'default:stick', ''}, + {'', 'default:stick', ''}, + } +}) + +minetest.register_craft({ + output = 'default:pick_mese', + recipe = { + {'default:mese', 'default:mese', 'default:mese'}, + {'', 'default:stick', ''}, + {'', 'default:stick', ''}, + } +}) + +minetest.register_craft({ + output = 'default:shovel_wood', + recipe = { + {'default:wood'}, + {'default:stick'}, + {'default:stick'}, + } +}) + +minetest.register_craft({ + output = 'default:shovel_stone', + recipe = { + {'default:cobble'}, + {'default:stick'}, + {'default:stick'}, + } +}) + +minetest.register_craft({ + output = 'default:shovel_steel', + recipe = { + {'default:steel_ingot'}, + {'default:stick'}, + {'default:stick'}, + } +}) + +minetest.register_craft({ + output = 'default:axe_wood', + recipe = { + {'default:wood', 'default:wood'}, + {'default:wood', 'default:stick'}, + {'', 'default:stick'}, + } +}) + +minetest.register_craft({ + output = 'default:axe_stone', + recipe = { + {'default:cobble', 'default:cobble'}, + {'default:cobble', 'default:stick'}, + {'', 'default:stick'}, + } +}) + +minetest.register_craft({ + output = 'default:axe_steel', + recipe = { + {'default:steel_ingot', 'default:steel_ingot'}, + {'default:steel_ingot', 'default:stick'}, + {'', 'default:stick'}, + } +}) + +minetest.register_craft({ + output = 'default:sword_wood', + recipe = { + {'default:wood'}, + {'default:wood'}, + {'default:stick'}, + } +}) + +minetest.register_craft({ + output = 'default:sword_stone', + recipe = { + {'default:cobble'}, + {'default:cobble'}, + {'default:stick'}, + } +}) + +minetest.register_craft({ + output = 'default:sword_steel', + recipe = { + {'default:steel_ingot'}, + {'default:steel_ingot'}, + {'default:stick'}, + } +}) + +minetest.register_craft({ + output = 'default:rail 15', + recipe = { + {'default:steel_ingot', '', 'default:steel_ingot'}, + {'default:steel_ingot', 'default:stick', 'default:steel_ingot'}, + {'default:steel_ingot', '', 'default:steel_ingot'}, + } +}) + +minetest.register_craft({ + output = 'default:chest', + recipe = { + {'default:wood', 'default:wood', 'default:wood'}, + {'default:wood', '', 'default:wood'}, + {'default:wood', 'default:wood', 'default:wood'}, + } +}) + +minetest.register_craft({ + output = 'default:chest_locked', + recipe = { + {'default:wood', 'default:wood', 'default:wood'}, + {'default:wood', 'default:steel_ingot', 'default:wood'}, + {'default:wood', 'default:wood', 'default:wood'}, + } +}) + +minetest.register_craft({ + output = 'default:furnace', + recipe = { + {'default:cobble', 'default:cobble', 'default:cobble'}, + {'default:cobble', '', 'default:cobble'}, + {'default:cobble', 'default:cobble', 'default:cobble'}, + } +}) + +minetest.register_craft({ + output = 'default:steelblock', + recipe = { + {'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'}, + {'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'}, + {'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'}, + } +}) + +minetest.register_craft({ + output = 'default:sandstone', + recipe = { + {'default:sand', 'default:sand'}, + {'default:sand', 'default:sand'}, + } +}) + +minetest.register_craft({ + output = 'default:clay', + recipe = { + {'default:clay_lump', 'default:clay_lump'}, + {'default:clay_lump', 'default:clay_lump'}, + } +}) + +minetest.register_craft({ + output = 'default:brick', + recipe = { + {'default:clay_brick', 'default:clay_brick'}, + {'default:clay_brick', 'default:clay_brick'}, + } +}) + +minetest.register_craft({ + output = 'default:paper', + recipe = { + {'default:papyrus', 'default:papyrus', 'default:papyrus'}, + } +}) + +minetest.register_craft({ + output = 'default:book', + recipe = { + {'default:paper'}, + {'default:paper'}, + {'default:paper'}, + } +}) + +minetest.register_craft({ + output = 'default:bookshelf', + recipe = { + {'default:wood', 'default:wood', 'default:wood'}, + {'default:book', 'default:book', 'default:book'}, + {'default:wood', 'default:wood', 'default:wood'}, + } +}) + +minetest.register_craft({ + output = 'default:ladder', + recipe = { + {'default:stick', '', 'default:stick'}, + {'default:stick', 'default:stick', 'default:stick'}, + {'default:stick', '', 'default:stick'}, + } +}) + +-- +-- Crafting (tool repair) +-- +minetest.register_craft({ + type = "toolrepair", + additional_wear = -0.02, +}) + +-- +-- Cooking recipes +-- + +minetest.register_craft({ + type = "cooking", + output = "default:glass", + recipe = "default:sand", +}) + +minetest.register_craft({ + type = "cooking", + output = "default:coal_lump", + recipe = "default:tree", +}) + +minetest.register_craft({ + type = "cooking", + output = "default:coal_lump", + recipe = "default:jungletree", +}) + +minetest.register_craft({ + type = "cooking", + output = "default:stone", + recipe = "default:cobble", +}) + +minetest.register_craft({ + type = "cooking", + output = "default:steel_ingot", + recipe = "default:iron_lump", +}) + +minetest.register_craft({ + type = "cooking", + output = "default:clay_brick", + recipe = "default:clay_lump", +}) + +-- +-- Fuels +-- + +minetest.register_craft({ + type = "fuel", + recipe = "default:tree", + burntime = 30, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:jungletree", + burntime = 30, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:junglegrass", + burntime = 2, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:leaves", + burntime = 1, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:cactus", + burntime = 15, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:papyrus", + burntime = 1, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:bookshelf", + burntime = 30, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:fence_wood", + burntime = 15, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:ladder", + burntime = 5, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:wood", + burntime = 7, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:mese", + burntime = 30, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:lava_source", + burntime = 60, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:torch", + burntime = 4, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:sign_wall", + burntime = 10, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:chest", + burntime = 30, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:chest_locked", + burntime = 30, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:nyancat", + burntime = 1, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:nyancat_rainbow", + burntime = 1, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:sapling", + burntime = 10, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:apple", + burntime = 3, +}) + +minetest.register_craft({ + type = "fuel", + recipe = "default:coal_lump", + burntime = 40, +}) + +-- +-- Node definitions +-- + +-- Default node sounds + +function default.node_sound_defaults(table) + table = table or {} + table.footstep = table.footstep or + {name="", gain=1.0} + table.dug = table.dug or + {name="default_dug_node", gain=1.0} + return table +end + +function default.node_sound_stone_defaults(table) + table = table or {} + table.footstep = table.footstep or + {name="default_hard_footstep", gain=0.2} + default.node_sound_defaults(table) + return table +end + +function default.node_sound_dirt_defaults(table) + table = table or {} + table.footstep = table.footstep or + {name="", gain=0.5} + --table.dug = table.dug or + -- {name="default_dirt_break", gain=0.5} + table.place = table.place or + {name="default_grass_footstep", gain=0.5} + default.node_sound_defaults(table) + return table +end + +function default.node_sound_sand_defaults(table) + table = table or {} + table.footstep = table.footstep or + {name="default_grass_footstep", gain=0.25} + --table.dug = table.dug or + -- {name="default_dirt_break", gain=0.25} + table.dug = table.dug or + {name="", gain=0.25} + default.node_sound_defaults(table) + return table +end + +function default.node_sound_wood_defaults(table) + table = table or {} + table.footstep = table.footstep or + {name="default_hard_footstep", gain=0.3} + default.node_sound_defaults(table) + return table +end + +function default.node_sound_leaves_defaults(table) + table = table or {} + table.footstep = table.footstep or + {name="default_grass_footstep", gain=0.25} + table.dig = table.dig or + {name="default_dig_crumbly", gain=0.4} + table.dug = table.dug or + {name="", gain=1.0} + default.node_sound_defaults(table) + return table +end + +function default.node_sound_glass_defaults(table) + table = table or {} + table.footstep = table.footstep or + {name="default_stone_footstep", gain=0.25} + table.dug = table.dug or + {name="default_break_glass", gain=1.0} + default.node_sound_defaults(table) + return table +end + +-- + +minetest.register_node("default:stone", { + description = "Stone", + tiles ={"default_stone.png"}, + groups = {cracky=3}, + drop = 'default:cobble', + legacy_mineral = true, + sounds = default.node_sound_stone_defaults(), +}) + +minetest.register_node("default:stone_with_coal", { + description = "Stone with coal", + tiles ={"default_stone.png^default_mineral_coal.png"}, + groups = {cracky=3}, + drop = 'default:coal_lump', + sounds = default.node_sound_stone_defaults(), +}) + +minetest.register_node("default:stone_with_iron", { + description = "Stone with iron", + tiles ={"default_stone.png^default_mineral_iron.png"}, + groups = {cracky=3}, + drop = 'default:iron_lump', + sounds = default.node_sound_stone_defaults(), +}) + +minetest.register_node("default:dirt_with_grass", { + description = "Dirt with grass", + tiles ={"default_grass.png", "default_dirt.png", "default_dirt.png^default_grass_side.png"}, + groups = {crumbly=3, soil=1}, + drop = 'default:dirt', + sounds = default.node_sound_dirt_defaults({ + footstep = {name="default_grass_footstep", gain=0.4}, + }), +}) + +minetest.register_node("default:dirt_with_grass_footsteps", { + description = "Dirt with grass and footsteps", + tiles ={"default_grass_footsteps.png", "default_dirt.png", "default_dirt.png^default_grass_side.png"}, + groups = {crumbly=3, soil=1}, + drop = 'default:dirt', + sounds = default.node_sound_dirt_defaults({ + footstep = {name="default_grass_footstep", gain=0.4}, + }), +}) + +minetest.register_node("default:dirt", { + description = "Dirt", + tiles ={"default_dirt.png"}, + groups = {crumbly=3, soil=1}, + sounds = default.node_sound_dirt_defaults(), +}) + +minetest.register_node("default:sand", { + description = "Sand", + tiles ={"default_sand.png"}, + groups = {crumbly=3, falling_node=1}, + sounds = default.node_sound_sand_defaults(), +}) + +minetest.register_node("default:gravel", { + description = "Gravel", + tiles ={"default_gravel.png"}, + groups = {crumbly=2, falling_node=1}, + sounds = default.node_sound_dirt_defaults({ + footstep = {name="default_gravel_footstep", gain=0.45}, + }), +}) + +minetest.register_node("default:sandstone", { + description = "Sandstone", + tiles ={"default_sandstone.png"}, + groups = {crumbly=2,cracky=2}, + drop = 'default:sand', + sounds = default.node_sound_stone_defaults(), +}) + +minetest.register_node("default:clay", { + description = "Clay", + tiles ={"default_clay.png"}, + groups = {crumbly=3}, + drop = 'default:clay_lump 4', + sounds = default.node_sound_dirt_defaults({ + footstep = "", + }), +}) + +minetest.register_node("default:brick", { + description = "Brick", + tiles ={"default_brick.png"}, + groups = {cracky=3}, + drop = 'default:clay_brick 4', + sounds = default.node_sound_stone_defaults(), +}) + +minetest.register_node("default:tree", { + description = "Tree", + tiles ={"default_tree_top.png", "default_tree_top.png", "default_tree.png"}, + groups = {snappy=2,choppy=2,oddly_breakable_by_hand=1}, + sounds = default.node_sound_wood_defaults(), +}) + +minetest.register_node("default:jungletree", { + description = "Jungle Tree", + tiles ={"default_jungletree_top.png", "default_jungletree_top.png", "default_jungletree.png"}, + groups = {snappy=2,choppy=2,oddly_breakable_by_hand=1}, + sounds = default.node_sound_wood_defaults(), +}) + +minetest.register_node("default:junglegrass", { + description = "Jungle Grass", + drawtype = "plantlike", + visual_scale = 1.3, + tiles ={"default_junglegrass.png"}, + inventory_image = "default_junglegrass.png", + wield_image = "default_junglegrass.png", + paramtype = "light", + walkable = false, + groups = {snappy=3,attached_node=1}, + sounds = default.node_sound_leaves_defaults(), +}) + +minetest.register_node("default:leaves", { + description = "Leaves", + drawtype = "allfaces_optional", + visual_scale = 1.3, + tiles ={"default_leaves.png"}, + paramtype = "light", + is_ground_content = false, + groups = {snappy=3}, + drop = { + max_items = 1, + items = { + { + -- player will get sapling with 1/20 chance + items = {'default:sapling'}, + rarity = 20, + }, + { + -- player will get leaves only if he get no saplings, + -- this is because max_items is 1 + items = {'default:leaves'}, + } + } + }, + sounds = default.node_sound_leaves_defaults(), +}) + +minetest.register_node("default:cactus", { + description = "Cactus", + tiles ={"default_cactus_top.png", "default_cactus_top.png", "default_cactus_side.png"}, + groups = {snappy=2,choppy=3}, + sounds = default.node_sound_wood_defaults(), +}) + +minetest.register_node("default:papyrus", { + description = "Papyrus", + drawtype = "plantlike", + tiles ={"default_papyrus.png"}, + inventory_image = "default_papyrus.png", + wield_image = "default_papyrus.png", + paramtype = "light", + walkable = false, + groups = {snappy=3}, + sounds = default.node_sound_leaves_defaults(), +}) + +minetest.register_node("default:bookshelf", { + description = "Bookshelf", + tiles ={"default_wood.png", "default_wood.png", "default_bookshelf.png"}, + groups = {snappy=2,choppy=3,oddly_breakable_by_hand=2}, + sounds = default.node_sound_wood_defaults(), +}) + +minetest.register_node("default:glass", { + description = "Glass", + drawtype = "glasslike", + tiles ={"default_glass.png"}, + paramtype = "light", + sunlight_propagates = true, + groups = {snappy=2,cracky=3,oddly_breakable_by_hand=3}, + sounds = default.node_sound_glass_defaults(), +}) + +minetest.register_node("default:fence_wood", { + description = "Wooden Fence", + drawtype = "fencelike", + tiles ={"default_wood.png"}, + inventory_image = "default_fence.png", + wield_image = "default_fence.png", + paramtype = "light", + selection_box = { + type = "fixed", + fixed = {-1/7, -1/2, -1/7, 1/7, 1/2, 1/7}, + }, + groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2}, + sounds = default.node_sound_wood_defaults(), +}) + +minetest.register_node("default:rail", { + description = "Rail", + drawtype = "raillike", + tiles ={"default_rail.png", "default_rail_curved.png", "default_rail_t_junction.png", "default_rail_crossing.png"}, + inventory_image = "default_rail.png", + wield_image = "default_rail.png", + paramtype = "light", + walkable = false, + selection_box = { + type = "fixed", + fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2}, + }, + groups = {bendy=2,snappy=1,dig_immediate=2}, +}) + +minetest.register_node("default:ladder", { + description = "Ladder", + drawtype = "signlike", + tiles ={"default_ladder.png"}, + inventory_image = "default_ladder.png", + wield_image = "default_ladder.png", + paramtype = "light", + paramtype2 = "wallmounted", + walkable = false, + climbable = true, + selection_box = { + type = "wallmounted", + --wall_top = = + --wall_bottom = = + --wall_side = = + }, + groups = {snappy=2,choppy=2,oddly_breakable_by_hand=3}, + legacy_wallmounted = true, + sounds = default.node_sound_wood_defaults(), +}) + +minetest.register_node("default:wood", { + description = "Wood", + tiles ={"default_wood.png"}, + groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2}, + sounds = default.node_sound_wood_defaults(), +}) + +minetest.register_node("default:mese", { + description = "Mese", + tiles ={"default_mese.png"}, + groups = {cracky=1,level=2}, + sounds = default.node_sound_defaults(), +}) + +minetest.register_node("default:cloud", { + description = "Cloud", + tiles ={"default_cloud.png"}, + sounds = default.node_sound_defaults(), +}) + +minetest.register_node("default:water_flowing", { + description = "Water (flowing)", + inventory_image = minetest.inventorycube("default_water.png"), + drawtype = "flowingliquid", + tiles ={"default_water.png"}, + special_tiles = { + {name="default_water.png", backface_culling=false}, + {name="default_water.png", backface_culling=true}, + }, + alpha = WATER_ALPHA, + paramtype = "light", + walkable = false, + pointable = false, + diggable = false, + buildable_to = true, + drowning = 1, + liquidtype = "flowing", + liquid_alternative_flowing = "default:water_flowing", + liquid_alternative_source = "default:water_source", + liquid_viscosity = WATER_VISC, + post_effect_color = {a=64, r=100, g=100, b=200}, + groups = {water=3, liquid=3}, +}) + +minetest.register_node("default:water_source", { + description = "Water", + inventory_image = minetest.inventorycube("default_water.png"), + drawtype = "liquid", + tiles ={"default_water.png"}, + special_tiles = { + -- New-style water source material (mostly unused) + {name="default_water.png", backface_culling=false}, + }, + alpha = WATER_ALPHA, + paramtype = "light", + walkable = false, + pointable = false, + diggable = false, + buildable_to = true, + drowning = 1, + liquidtype = "source", + liquid_alternative_flowing = "default:water_flowing", + liquid_alternative_source = "default:water_source", + liquid_viscosity = WATER_VISC, + post_effect_color = {a=64, r=100, g=100, b=200}, + groups = {water=3, liquid=3}, +}) + +minetest.register_node("default:lava_flowing", { + description = "Lava (flowing)", + inventory_image = minetest.inventorycube("default_lava.png"), + drawtype = "flowingliquid", + tiles ={"default_lava.png"}, + special_tiles = { + { + image="default_lava_flowing_animated.png", + backface_culling=false, + animation={type="vertical_frames", aspect_w=16, aspect_h=16, length=3.3} + }, + { + image="default_lava_flowing_animated.png", + backface_culling=true, + animation={type="vertical_frames", aspect_w=16, aspect_h=16, length=3.3} + }, + }, + paramtype = "light", + light_source = LIGHT_MAX - 1, + walkable = false, + pointable = false, + diggable = false, + buildable_to = true, + drowning = 1, + liquidtype = "flowing", + liquid_alternative_flowing = "default:lava_flowing", + liquid_alternative_source = "default:lava_source", + liquid_viscosity = LAVA_VISC, + damage_per_second = 4*2, + post_effect_color = {a=192, r=255, g=64, b=0}, + groups = {lava=3, liquid=2, hot=3}, +}) + +minetest.register_node("default:lava_source", { + description = "Lava", + inventory_image = minetest.inventorycube("default_lava.png"), + drawtype = "liquid", + --tiles ={"default_lava.png"}, + tiles ={ + {name="default_lava_source_animated.png", animation={type="vertical_frames", aspect_w=16, aspect_h=16, length=3.0}} + }, + special_tiles = { + -- New-style lava source material (mostly unused) + {name="default_lava.png", backface_culling=false}, + }, + paramtype = "light", + light_source = LIGHT_MAX - 1, + walkable = false, + pointable = false, + diggable = false, + buildable_to = true, + drowning = 1, + liquidtype = "source", + liquid_alternative_flowing = "default:lava_flowing", + liquid_alternative_source = "default:lava_source", + liquid_viscosity = LAVA_VISC, + damage_per_second = 4*2, + post_effect_color = {a=192, r=255, g=64, b=0}, + groups = {lava=3, liquid=2, hot=3}, +}) + +minetest.register_node("default:torch", { + description = "Torch", + drawtype = "torchlike", + tiles ={"default_torch_on_floor.png", "default_torch_on_ceiling.png", "default_torch.png"}, + inventory_image = "default_torch_on_floor.png", + wield_image = "default_torch_on_floor.png", + paramtype = "light", + paramtype2 = "wallmounted", + sunlight_propagates = true, + is_ground_content = false, + walkable = false, + light_source = LIGHT_MAX-1, + selection_box = { + type = "wallmounted", + wall_top = {-0.1, 0.5-0.6, -0.1, 0.1, 0.5, 0.1}, + wall_bottom = {-0.1, -0.5, -0.1, 0.1, -0.5+0.6, 0.1}, + wall_side = {-0.5, -0.3, -0.1, -0.5+0.3, 0.3, 0.1}, + }, + groups = {choppy=2,dig_immediate=3,attached_node=1}, + legacy_wallmounted = true, + sounds = default.node_sound_defaults(), +}) + +minetest.register_node("default:sign_wall", { + description = "Sign", + drawtype = "signlike", + tiles ={"default_sign_wall.png"}, + inventory_image = "default_sign_wall.png", + wield_image = "default_sign_wall.png", + paramtype = "light", + paramtype2 = "wallmounted", + sunlight_propagates = true, + is_ground_content = false, + walkable = false, + selection_box = { + type = "wallmounted", + --wall_top = + --wall_bottom = + --wall_side = + }, + groups = {choppy=2,dig_immediate=2,attached_node=1}, + legacy_wallmounted = true, + sounds = default.node_sound_defaults(), + on_construct = function(pos) + --local n = minetest.get_node(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", "field[text;;${text}]") + meta:set_string("infotext", "\"\"") + end, + on_receive_fields = function(pos, formname, fields, sender) + --print("Sign at "..minetest.pos_to_string(pos).." got "..dump(fields)) + local meta = minetest.get_meta(pos) + fields.text = fields.text or "" + print((sender:get_player_name() or "").." wrote \""..fields.text.. + "\" to sign at "..minetest.pos_to_string(pos)) + meta:set_string("text", fields.text) + meta:set_string("infotext", '"'..fields.text..'"') + end, +}) + +minetest.register_node("default:chest", { + description = "Chest", + tiles ={"default_chest_top.png", "default_chest_top.png", "default_chest_side.png", + "default_chest_side.png", "default_chest_side.png", "default_chest_front.png"}, + paramtype2 = "facedir", + groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2}, + legacy_facedir_simple = true, + is_ground_content = false, + sounds = default.node_sound_wood_defaults(), + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", + "size[8,9]".. + "list[current_name;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) + end, + can_dig = function(pos,player) + local meta = minetest.get_meta(pos); + local inv = meta:get_inventory() + return inv:is_empty("main") + end, +}) + +local function has_locked_chest_privilege(meta, player) + if player:get_player_name() ~= meta:get_string("owner") then + return false + end + return true +end + +minetest.register_node("default:chest_locked", { + description = "Locked Chest", + tiles ={"default_chest_top.png", "default_chest_top.png", "default_chest_side.png", + "default_chest_side.png", "default_chest_side.png", "default_chest_lock.png"}, + paramtype2 = "facedir", + groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2}, + legacy_facedir_simple = true, + is_ground_content = false, + sounds = default.node_sound_wood_defaults(), + after_place_node = function(pos, placer) + local meta = minetest.get_meta(pos) + meta:set_string("owner", placer:get_player_name() or "") + meta:set_string("infotext", "Locked Chest (owned by ".. + meta:get_string("owner")..")") + end, + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", + "size[8,9]".. + "list[current_name;main;0,0;8,4;]".. + "list[current_player;main;0,5;8,4;]") + meta:set_string("infotext", "Locked Chest") + meta:set_string("owner", "") + local inv = meta:get_inventory() + inv:set_size("main", 8*4) + end, + can_dig = function(pos,player) + local meta = minetest.get_meta(pos); + local inv = meta:get_inventory() + return inv:is_empty("main") + end, + allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) + local meta = minetest.get_meta(pos) + if not has_locked_chest_privilege(meta, player) then + minetest.log("action", player:get_player_name().. + " tried to access a locked chest belonging to ".. + meta:get_string("owner").." at ".. + minetest.pos_to_string(pos)) + return 0 + end + return count + end, + allow_metadata_inventory_put = function(pos, listname, index, stack, player) + local meta = minetest.get_meta(pos) + if not has_locked_chest_privilege(meta, player) then + minetest.log("action", player:get_player_name().. + " tried to access a locked chest belonging to ".. + meta:get_string("owner").." at ".. + minetest.pos_to_string(pos)) + return 0 + end + return stack:get_count() + end, + allow_metadata_inventory_take = function(pos, listname, index, stack, player) + local meta = minetest.get_meta(pos) + if not has_locked_chest_privilege(meta, player) then + minetest.log("action", player:get_player_name().. + " tried to access a locked chest belonging to ".. + meta:get_string("owner").." at ".. + minetest.pos_to_string(pos)) + return 0 + end + return stack:get_count() + end, + on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) + minetest.log("action", player:get_player_name().. + " moves stuff in locked chest at "..minetest.pos_to_string(pos)) + end, + on_metadata_inventory_put = function(pos, listname, index, stack, player) + minetest.log("action", player:get_player_name().. + " moves stuff to locked chest at "..minetest.pos_to_string(pos)) + end, + on_metadata_inventory_take = function(pos, listname, index, stack, player) + minetest.log("action", player:get_player_name().. + " takes stuff from locked chest at "..minetest.pos_to_string(pos)) + end, +}) + +default.furnace_inactive_formspec = + "size[8,9]".. + "image[2,2;1,1;default_furnace_fire_bg.png]".. + "list[current_name;fuel;2,3;1,1;]".. + "list[current_name;src;2,1;1,1;]".. + "list[current_name;dst;5,1;2,2;]".. + "list[current_player;main;0,5;8,4;]" + +minetest.register_node("default:furnace", { + description = "Furnace", + tiles ={"default_furnace_side.png", "default_furnace_side.png", "default_furnace_side.png", + "default_furnace_side.png", "default_furnace_side.png", "default_furnace_front.png"}, + paramtype2 = "facedir", + groups = {cracky=2}, + legacy_facedir_simple = true, + is_ground_content = false, + sounds = default.node_sound_stone_defaults(), + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", default.furnace_inactive_formspec) + meta:set_string("infotext", "Furnace") + local inv = meta:get_inventory() + inv:set_size("fuel", 1) + inv:set_size("src", 1) + inv:set_size("dst", 4) + end, + can_dig = function(pos,player) + local meta = minetest.get_meta(pos); + local inv = meta:get_inventory() + if not inv:is_empty("fuel") then + return false + elseif not inv:is_empty("dst") then + return false + elseif not inv:is_empty("src") then + return false + end + return true + end, +}) + +minetest.register_node("default:furnace_active", { + description = "Furnace", + tiles ={"default_furnace_side.png", "default_furnace_side.png", "default_furnace_side.png", + "default_furnace_side.png", "default_furnace_side.png", "default_furnace_front_active.png"}, + paramtype2 = "facedir", + light_source = 8, + drop = "default:furnace", + groups = {cracky=2}, + legacy_facedir_simple = true, + is_ground_content = false, + sounds = default.node_sound_stone_defaults(), + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("formspec", default.furnace_inactive_formspec) + meta:set_string("infotext", "Furnace"); + local inv = meta:get_inventory() + inv:set_size("fuel", 1) + inv:set_size("src", 1) + inv:set_size("dst", 4) + end, + can_dig = function(pos,player) + local meta = minetest.get_meta(pos); + local inv = meta:get_inventory() + if not inv:is_empty("fuel") then + return false + elseif not inv:is_empty("dst") then + return false + elseif not inv:is_empty("src") then + return false + end + return true + end, +}) + +function swap_node(pos,name) + local node = minetest.get_node(pos) + if node.name == name then + return + end + node.name = name + minetest.swap_node(pos, node) +end + +minetest.register_abm({ + nodenames = {"default:furnace","default:furnace_active"}, + interval = 1.0, + chance = 1, + action = function(pos, node, active_object_count, active_object_count_wider) + local meta = minetest.get_meta(pos) + for i, name in ipairs({ + "fuel_totaltime", + "fuel_time", + "src_totaltime", + "src_time" + }) do + if meta:get_string(name) == "" then + meta:set_float(name, 0.0) + end + end + + local inv = meta:get_inventory() + + local srclist = inv:get_list("src") + local cooked = nil + + if srclist then + cooked = minetest.get_craft_result({method = "cooking", width = 1, items = srclist}) + end + + local was_active = false + + if meta:get_float("fuel_time") < meta:get_float("fuel_totaltime") then + was_active = true + meta:set_float("fuel_time", meta:get_float("fuel_time") + 1) + meta:set_float("src_time", meta:get_float("src_time") + 1) + if cooked and cooked.item and meta:get_float("src_time") >= cooked.time then + -- check if there's room for output in "dst" list + if inv:room_for_item("dst",cooked.item) then + -- Put result in "dst" list + inv:add_item("dst", cooked.item) + -- take stuff from "src" list + srcstack = inv:get_stack("src", 1) + srcstack:take_item() + inv:set_stack("src", 1, srcstack) + else + print("Could not insert '"..cooked.item:to_string().."'") + end + meta:set_string("src_time", 0) + end + end + + if meta:get_float("fuel_time") < meta:get_float("fuel_totaltime") then + local percent = math.floor(meta:get_float("fuel_time") / + meta:get_float("fuel_totaltime") * 100) + meta:set_string("infotext","Furnace active: "..percent.."%") + swap_node(pos,"default:furnace_active") + meta:set_string("formspec", + "size[8,9]".. + "image[2,2;1,1;default_furnace_fire_bg.png^[lowpart:".. + (100-percent)..":default_furnace_fire_fg.png]".. + "list[current_name;fuel;2,3;1,1;]".. + "list[current_name;src;2,1;1,1;]".. + "list[current_name;dst;5,1;2,2;]".. + "list[current_player;main;0,5;8,4;]") + return + end + + local fuel = nil + local cooked = nil + local fuellist = inv:get_list("fuel") + local srclist = inv:get_list("src") + + if srclist then + cooked = minetest.get_craft_result({method = "cooking", width = 1, items = srclist}) + end + if fuellist then + fuel = minetest.get_craft_result({method = "fuel", width = 1, items = fuellist}) + end + + if fuel.time <= 0 then + meta:set_string("infotext","Furnace out of fuel") + swap_node(pos,"default:furnace") + meta:set_string("formspec", default.furnace_inactive_formspec) + return + end + + if cooked.item:is_empty() then + if was_active then + meta:set_string("infotext","Furnace is empty") + swap_node(pos,"default:furnace") + meta:set_string("formspec", default.furnace_inactive_formspec) + end + return + end + + meta:set_string("fuel_totaltime", fuel.time) + meta:set_string("fuel_time", 0) + + local stack = inv:get_stack("fuel", 1) + stack:take_item() + inv:set_stack("fuel", 1, stack) + end, +}) + +minetest.register_node("default:cobble", { + description = "Cobble", + tiles ={"default_cobble.png"}, + groups = {cracky=3}, + sounds = default.node_sound_stone_defaults(), +}) + +minetest.register_node("default:mossycobble", { + description = "Mossy Cobble", + tiles ={"default_mossycobble.png"}, + groups = {cracky=3}, + sounds = default.node_sound_stone_defaults(), +}) + +minetest.register_node("default:steelblock", { + description = "Steel Block", + tiles ={"default_steel_block.png"}, + groups = {snappy=1,bendy=2}, + sounds = default.node_sound_stone_defaults(), +}) + +minetest.register_node("default:nyancat", { + description = "Nyancat", + tiles ={"default_nc_side.png", "default_nc_side.png", "default_nc_side.png", + "default_nc_side.png", "default_nc_back.png", "default_nc_front.png"}, + inventory_image = "default_nc_front.png", + paramtype2 = "facedir", + groups = {cracky=2}, + legacy_facedir_simple = true, + is_ground_content = false, + sounds = default.node_sound_defaults(), +}) + +minetest.register_node("default:nyancat_rainbow", { + description = "Nyancat Rainbow", + tiles ={"default_nc_rb.png"}, + inventory_image = "default_nc_rb.png", + is_ground_content = false, + groups = {cracky=2}, + sounds = default.node_sound_defaults(), +}) + +minetest.register_node("default:sapling", { + description = "Sapling", + drawtype = "plantlike", + visual_scale = 1.0, + tiles ={"default_sapling.png"}, + inventory_image = "default_sapling.png", + wield_image = "default_sapling.png", + paramtype = "light", + walkable = false, + groups = {snappy=2,dig_immediate=3,attached_node=1}, + sounds = default.node_sound_defaults(), +}) + +minetest.register_node("default:apple", { + description = "Apple", + drawtype = "plantlike", + visual_scale = 1.0, + tiles ={"default_apple.png"}, + inventory_image = "default_apple.png", + paramtype = "light", + sunlight_propagates = true, + walkable = false, + groups = {fleshy=3,dig_immediate=3}, + on_use = minetest.item_eat(4), + sounds = default.node_sound_defaults(), +}) + + +local c_air = minetest.get_content_id("air") +local c_ignore = minetest.get_content_id("ignore") +local c_tree = minetest.get_content_id("default:tree") +local c_leaves = minetest.get_content_id("default:leaves") +local c_apple = minetest.get_content_id("default:apple") +function default.grow_tree(data, a, pos, is_apple_tree, seed) + --[[ + NOTE: Tree-placing code is currently duplicated in the engine + and in games that have saplings; both are deprecated but not + replaced yet + ]]-- + local pr = PseudoRandom(seed) + local th = pr:next(4, 5) + local x, y, z = pos.x, pos.y, pos.z + for yy = y, y+th-1 do + local vi = a:index(x, yy, z) + if a:contains(x, yy, z) and (data[vi] == c_air or yy == y) then + data[vi] = c_tree + end + end + y = y+th-1 -- (x, y, z) is now last piece of trunk + local leaves_a = VoxelArea:new{MinEdge={x=-2, y=-1, z=-2}, MaxEdge={x=2, y=2, z=2}} + local leaves_buffer = {} + + -- Force leaves near the trunk + local d = 1 + for xi = -d, d do + for yi = -d, d do + for zi = -d, d do + leaves_buffer[leaves_a:index(xi, yi, zi)] = true + end + end + end + + -- Add leaves randomly + for iii = 1, 8 do + local d = 1 + local xx = pr:next(leaves_a.MinEdge.x, leaves_a.MaxEdge.x - d) + local yy = pr:next(leaves_a.MinEdge.y, leaves_a.MaxEdge.y - d) + local zz = pr:next(leaves_a.MinEdge.z, leaves_a.MaxEdge.z - d) + + for xi = 0, d do + for yi = 0, d do + for zi = 0, d do + leaves_buffer[leaves_a:index(xx+xi, yy+yi, zz+zi)] = true + end + end + end + end + + -- Add the leaves + for xi = leaves_a.MinEdge.x, leaves_a.MaxEdge.x do + for yi = leaves_a.MinEdge.y, leaves_a.MaxEdge.y do + for zi = leaves_a.MinEdge.z, leaves_a.MaxEdge.z do + if a:contains(x+xi, y+yi, z+zi) then + local vi = a:index(x+xi, y+yi, z+zi) + if data[vi] == c_air or data[vi] == c_ignore then + if leaves_buffer[leaves_a:index(xi, yi, zi)] then + if is_apple_tree and pr:next(1, 100) <= 10 then + data[vi] = c_apple + else + data[vi] = c_leaves + end + end + end + end + end + end + end +end + +minetest.register_abm({ + nodenames = {"default:sapling"}, + interval = 10, + chance = 50, + action = function(pos, node) + local is_soil = minetest.registered_nodes[minetest.get_node({x=pos.x, y=pos.y-1, z=pos.z}).name].groups.soil + if is_soil == nil or is_soil == 0 then return end + print("A sapling grows into a tree at "..minetest.pos_to_string(pos)) + local vm = minetest.get_voxel_manip() + local minp, maxp = vm:read_from_map({x=pos.x-16, y=pos.y, z=pos.z-16}, {x=pos.x+16, y=pos.y+16, z=pos.z+16}) + local a = VoxelArea:new{MinEdge=minp, MaxEdge=maxp} + local data = vm:get_data() + default.grow_tree(data, a, pos, math.random(1, 4) == 1, math.random(1,100000)) + vm:set_data(data) + vm:write_to_map(data) + vm:update_map() + end +}) + +minetest.register_abm({ + nodenames = {"default:dirt"}, + interval = 2, + chance = 200, + action = function(pos, node) + local above = {x=pos.x, y=pos.y+1, z=pos.z} + local name = minetest.get_node(above).name + local nodedef = minetest.registered_nodes[name] + if nodedef and (nodedef.sunlight_propagates or nodedef.paramtype == "light") + and nodedef.liquidtype == "none" + and (minetest.get_node_light(above) or 0) >= 13 then + if name == "default:snow" or name == "default:snowblock" then + minetest.set_node(pos, {name = "default:dirt_with_snow"}) + else + minetest.set_node(pos, {name = "default:dirt_with_grass"}) + end + end + end +}) + +minetest.register_abm({ + nodenames = {"default:dirt_with_grass"}, + interval = 2, + chance = 20, + action = function(pos, node) + local above = {x=pos.x, y=pos.y+1, z=pos.z} + local name = minetest.get_node(above).name + local nodedef = minetest.registered_nodes[name] + if name ~= "ignore" and nodedef + and not ((nodedef.sunlight_propagates or nodedef.paramtype == "light") + and nodedef.liquidtype == "none") then + minetest.set_node(pos, {name = "default:dirt"}) + end + end +}) + +-- +-- Crafting items +-- + +minetest.register_craftitem("default:stick", { + description = "Stick", + inventory_image = "default_stick.png", +}) + +minetest.register_craftitem("default:paper", { + description = "Paper", + inventory_image = "default_paper.png", +}) + +minetest.register_craftitem("default:book", { + description = "Book", + inventory_image = "default_book.png", +}) + +minetest.register_craftitem("default:coal_lump", { + description = "Lump of coal", + inventory_image = "default_coal_lump.png", +}) + +minetest.register_craftitem("default:iron_lump", { + description = "Lump of iron", + inventory_image = "default_iron_lump.png", +}) + +minetest.register_craftitem("default:clay_lump", { + description = "Lump of clay", + inventory_image = "default_clay_lump.png", +}) + +minetest.register_craftitem("default:steel_ingot", { + description = "Steel ingot", + inventory_image = "default_steel_ingot.png", +}) + +minetest.register_craftitem("default:clay_brick", { + description = "Clay brick", + inventory_image = "default_steel_ingot.png", + inventory_image = "default_clay_brick.png", +}) + +minetest.register_craftitem("default:scorched_stuff", { + description = "Scorched stuff", + inventory_image = "default_scorched_stuff.png", +}) + +-- +-- Aliases for the current map generator outputs +-- + +minetest.register_alias("mapgen_air", "air") +minetest.register_alias("mapgen_stone", "default:stone") +minetest.register_alias("mapgen_tree", "default:tree") +minetest.register_alias("mapgen_leaves", "default:leaves") +minetest.register_alias("mapgen_apple", "default:apple") +minetest.register_alias("mapgen_water_source", "default:water_source") +minetest.register_alias("mapgen_dirt", "default:dirt") +minetest.register_alias("mapgen_sand", "default:sand") +minetest.register_alias("mapgen_gravel", "default:gravel") +minetest.register_alias("mapgen_clay", "default:clay") +minetest.register_alias("mapgen_lava_source", "default:lava_source") +minetest.register_alias("mapgen_cobble", "default:cobble") +minetest.register_alias("mapgen_mossycobble", "default:mossycobble") +minetest.register_alias("mapgen_dirt_with_grass", "default:dirt_with_grass") +minetest.register_alias("mapgen_junglegrass", "default:junglegrass") +minetest.register_alias("mapgen_stone_with_coal", "default:stone_with_coal") +minetest.register_alias("mapgen_stone_with_iron", "default:stone_with_iron") +minetest.register_alias("mapgen_mese", "default:mese") + +-- Support old code +function default.spawn_falling_node(p, nodename) + spawn_falling_node(p, nodename) +end + +-- Horrible crap to support old code +-- Don't use this and never do what this does, it's completely wrong! +-- (More specifically, the client and the C++ code doesn't get the group) +function default.register_falling_node(nodename, texture) + minetest.log("error", debug.traceback()) + minetest.log('error', "WARNING: default.register_falling_node is deprecated") + if minetest.registered_nodes[nodename] then + minetest.registered_nodes[nodename].groups.falling_node = 1 + end +end + +-- +-- Global callbacks +-- + +-- Global environment step function +function on_step(dtime) + -- print("on_step") +end +minetest.register_globalstep(on_step) + +function on_placenode(p, node) + --print("on_placenode") +end +minetest.register_on_placenode(on_placenode) + +function on_dignode(p, node) + --print("on_dignode") +end +minetest.register_on_dignode(on_dignode) + +function on_punchnode(p, node) +end +minetest.register_on_punchnode(on_punchnode) + +-- +-- Test some things +-- + +local function test_get_craft_result() + minetest.log("info", "test_get_craft_result()") + -- normal + local input = { + method = "normal", + width = 2, + items = {"", "default:coal_lump", "", "default:stick"} + } + minetest.log("info", "torch crafting input: "..dump(input)) + local output, decremented_input = minetest.get_craft_result(input) + minetest.log("info", "torch crafting output: "..dump(output)) + minetest.log("info", "torch crafting decremented input: "..dump(decremented_input)) + assert(output.item) + minetest.log("info", "torch crafting output.item:to_table(): "..dump(output.item:to_table())) + assert(output.item:get_name() == "default:torch") + assert(output.item:get_count() == 4) + -- fuel + local input = { + method = "fuel", + width = 1, + items = {"default:coal_lump"} + } + minetest.log("info", "coal fuel input: "..dump(input)) + local output, decremented_input = minetest.get_craft_result(input) + minetest.log("info", "coal fuel output: "..dump(output)) + minetest.log("info", "coal fuel decremented input: "..dump(decremented_input)) + assert(output.time) + assert(output.time > 0) + -- cook + local input = { + method = "cooking", + width = 1, + items = {"default:cobble"} + } + minetest.log("info", "cobble cooking input: "..dump(output)) + local output, decremented_input = minetest.get_craft_result(input) + minetest.log("info", "cobble cooking output: "..dump(output)) + minetest.log("info", "cobble cooking decremented input: "..dump(decremented_input)) + assert(output.time) + assert(output.time > 0) + assert(output.item) + minetest.log("info", "cobble cooking output.item:to_table(): "..dump(output.item:to_table())) + assert(output.item:get_name() == "default:stone") + assert(output.item:get_count() == 1) +end +test_get_craft_result() + +-- +-- Done, print some random stuff +-- + +--print("minetest.registered_entities:") +--dump2(minetest.registered_entities) + +-- END diff --git a/games/minimal/mods/default/mapgen.lua b/games/minimal/mods/default/mapgen.lua new file mode 100644 index 0000000..dd839b9 --- /dev/null +++ b/games/minimal/mods/default/mapgen.lua @@ -0,0 +1,126 @@ +-- minetest/default/mapgen.lua + +-- +-- Aliases for map generator outputs +-- + +minetest.register_alias("mapgen_stone", "default:stone") +minetest.register_alias("mapgen_tree", "default:tree") +minetest.register_alias("mapgen_leaves", "default:leaves") +minetest.register_alias("mapgen_apple", "default:apple") +minetest.register_alias("mapgen_water_source", "default:water_source") +minetest.register_alias("mapgen_dirt", "default:dirt") +minetest.register_alias("mapgen_sand", "default:sand") +minetest.register_alias("mapgen_gravel", "default:gravel") +minetest.register_alias("mapgen_clay", "default:clay") +minetest.register_alias("mapgen_lava_source", "default:lava_source") +minetest.register_alias("mapgen_cobble", "default:cobble") +minetest.register_alias("mapgen_mossycobble", "default:mossycobble") +minetest.register_alias("mapgen_dirt_with_grass", "default:dirt_with_grass") +minetest.register_alias("mapgen_junglegrass", "default:junglegrass") +minetest.register_alias("mapgen_stone_with_coal", "default:stone_with_coal") +minetest.register_alias("mapgen_stone_with_iron", "default:stone_with_iron") +minetest.register_alias("mapgen_mese", "default:mese") +minetest.register_alias("mapgen_stair_cobble", "stairs:stair_cobble") + +-- +-- Ore generation +-- + +minetest.register_ore({ + ore_type = "scatter", + ore = "default:stone_with_coal", + wherein = "default:stone", + clust_scarcity = 8*8*8, + clust_num_ores = 5, + clust_size = 3, + height_min = -31000, + height_max = 64, +}) + +minetest.register_ore({ + ore_type = "scatter", + ore = "default:stone_with_iron", + wherein = "default:stone", + clust_scarcity = 16*16*16, + clust_num_ores = 5, + clust_size = 3, + height_min = -5, + height_max = 7, +}) + +minetest.register_ore({ + ore_type = "scatter", + ore = "default:stone_with_iron", + wherein = "default:stone", + clust_scarcity = 12*12*12, + clust_num_ores = 5, + clust_size = 3, + height_min = -16, + height_max = -5, +}) + +minetest.register_ore({ + ore_type = "scatter", + ore = "default:stone_with_iron", + wherein = "default:stone", + clust_scarcity = 9*9*9, + clust_num_ores = 5, + clust_size = 3, + height_min = -31000, + height_max = -17, +}) + +minetest.register_on_generated(function(minp, maxp, seed) + -- Generate clay + if maxp.y >= 2 and minp.y <= 0 then + -- Assume X and Z lengths are equal + local divlen = 4 + local divs = (maxp.x-minp.x)/divlen+1; + for divx=0+1,divs-1-1 do + for divz=0+1,divs-1-1 do + local cx = minp.x + math.floor((divx+0.5)*divlen) + local cz = minp.z + math.floor((divz+0.5)*divlen) + if minetest.get_node({x=cx,y=1,z=cz}).name == "default:water_source" and + minetest.get_node({x=cx,y=0,z=cz}).name == "default:sand" then + local is_shallow = true + local num_water_around = 0 + if minetest.get_node({x=cx-divlen*2,y=1,z=cz+0}).name == "default:water_source" then + num_water_around = num_water_around + 1 end + if minetest.get_node({x=cx+divlen*2,y=1,z=cz+0}).name == "default:water_source" then + num_water_around = num_water_around + 1 end + if minetest.get_node({x=cx+0,y=1,z=cz-divlen*2}).name == "default:water_source" then + num_water_around = num_water_around + 1 end + if minetest.get_node({x=cx+0,y=1,z=cz+divlen*2}).name == "default:water_source" then + num_water_around = num_water_around + 1 end + if num_water_around >= 2 then + is_shallow = false + end + if is_shallow then + for x1=-divlen,divlen do + for z1=-divlen,divlen do + if minetest.get_node({x=cx+x1,y=0,z=cz+z1}).name == "default:sand" then + minetest.set_node({x=cx+x1,y=0,z=cz+z1}, {name="default:clay"}) + end + end + end + end + end + end + end + end +end) + +-- +-- Register biome for biome API +-- + +minetest.register_biome({ + name = "Grassland", + -- Will use defaults of omitted parameters + y_min = -31000, + y_max = 31000, + heat_point = 50, + humidity_point = 50, +}) + diff --git a/games/minimal/mods/default/sounds/default_grass_footstep.1.ogg b/games/minimal/mods/default/sounds/default_grass_footstep.1.ogg new file mode 100644 index 0000000..ce625d9 Binary files /dev/null and b/games/minimal/mods/default/sounds/default_grass_footstep.1.ogg differ diff --git a/games/minimal/mods/default/textures/bubble.png b/games/minimal/mods/default/textures/bubble.png new file mode 100644 index 0000000..3bca7e1 Binary files /dev/null and b/games/minimal/mods/default/textures/bubble.png differ diff --git a/games/minimal/mods/default/textures/crack_anylength.png b/games/minimal/mods/default/textures/crack_anylength.png new file mode 100644 index 0000000..d9b49f9 Binary files /dev/null and b/games/minimal/mods/default/textures/crack_anylength.png differ diff --git a/games/minimal/mods/default/textures/default_apple.png b/games/minimal/mods/default/textures/default_apple.png new file mode 100644 index 0000000..4473dca Binary files /dev/null and b/games/minimal/mods/default/textures/default_apple.png differ diff --git a/games/minimal/mods/default/textures/default_book.png b/games/minimal/mods/default/textures/default_book.png new file mode 100644 index 0000000..ea14a37 Binary files /dev/null and b/games/minimal/mods/default/textures/default_book.png differ diff --git a/games/minimal/mods/default/textures/default_bookshelf.png b/games/minimal/mods/default/textures/default_bookshelf.png new file mode 100644 index 0000000..f225e34 Binary files /dev/null and b/games/minimal/mods/default/textures/default_bookshelf.png differ diff --git a/games/minimal/mods/default/textures/default_brick.png b/games/minimal/mods/default/textures/default_brick.png new file mode 100644 index 0000000..def1cf0 Binary files /dev/null and b/games/minimal/mods/default/textures/default_brick.png differ diff --git a/games/minimal/mods/default/textures/default_cactus_side.png b/games/minimal/mods/default/textures/default_cactus_side.png new file mode 100644 index 0000000..128a4d2 Binary files /dev/null and b/games/minimal/mods/default/textures/default_cactus_side.png differ diff --git a/games/minimal/mods/default/textures/default_cactus_top.png b/games/minimal/mods/default/textures/default_cactus_top.png new file mode 100644 index 0000000..eda1a0b Binary files /dev/null and b/games/minimal/mods/default/textures/default_cactus_top.png differ diff --git a/games/minimal/mods/default/textures/default_chest_front.png b/games/minimal/mods/default/textures/default_chest_front.png new file mode 100644 index 0000000..55b076c Binary files /dev/null and b/games/minimal/mods/default/textures/default_chest_front.png differ diff --git a/games/minimal/mods/default/textures/default_chest_lock.png b/games/minimal/mods/default/textures/default_chest_lock.png new file mode 100644 index 0000000..4b2d1af Binary files /dev/null and b/games/minimal/mods/default/textures/default_chest_lock.png differ diff --git a/games/minimal/mods/default/textures/default_chest_side.png b/games/minimal/mods/default/textures/default_chest_side.png new file mode 100644 index 0000000..ae4847c Binary files /dev/null and b/games/minimal/mods/default/textures/default_chest_side.png differ diff --git a/games/minimal/mods/default/textures/default_chest_top.png b/games/minimal/mods/default/textures/default_chest_top.png new file mode 100644 index 0000000..ac41551 Binary files /dev/null and b/games/minimal/mods/default/textures/default_chest_top.png differ diff --git a/games/minimal/mods/default/textures/default_clay.png b/games/minimal/mods/default/textures/default_clay.png new file mode 100644 index 0000000..070b69e Binary files /dev/null and b/games/minimal/mods/default/textures/default_clay.png differ diff --git a/games/minimal/mods/default/textures/default_clay_brick.png b/games/minimal/mods/default/textures/default_clay_brick.png new file mode 100644 index 0000000..e25633b Binary files /dev/null and b/games/minimal/mods/default/textures/default_clay_brick.png differ diff --git a/games/minimal/mods/default/textures/default_clay_lump.png b/games/minimal/mods/default/textures/default_clay_lump.png new file mode 100644 index 0000000..ceed6fa Binary files /dev/null and b/games/minimal/mods/default/textures/default_clay_lump.png differ diff --git a/games/minimal/mods/default/textures/default_cloud.png b/games/minimal/mods/default/textures/default_cloud.png new file mode 100644 index 0000000..faf0ec1 Binary files /dev/null and b/games/minimal/mods/default/textures/default_cloud.png differ diff --git a/games/minimal/mods/default/textures/default_coal_lump.png b/games/minimal/mods/default/textures/default_coal_lump.png new file mode 100644 index 0000000..dae47e3 Binary files /dev/null and b/games/minimal/mods/default/textures/default_coal_lump.png differ diff --git a/games/minimal/mods/default/textures/default_cobble.png b/games/minimal/mods/default/textures/default_cobble.png new file mode 100644 index 0000000..2a28d25 Binary files /dev/null and b/games/minimal/mods/default/textures/default_cobble.png differ diff --git a/games/minimal/mods/default/textures/default_dirt.png b/games/minimal/mods/default/textures/default_dirt.png new file mode 100644 index 0000000..7cb9c89 Binary files /dev/null and b/games/minimal/mods/default/textures/default_dirt.png differ diff --git a/games/minimal/mods/default/textures/default_fence.png b/games/minimal/mods/default/textures/default_fence.png new file mode 100644 index 0000000..e3510c5 Binary files /dev/null and b/games/minimal/mods/default/textures/default_fence.png differ diff --git a/games/minimal/mods/default/textures/default_furnace_fire_bg.png b/games/minimal/mods/default/textures/default_furnace_fire_bg.png new file mode 100644 index 0000000..e3370f4 Binary files /dev/null and b/games/minimal/mods/default/textures/default_furnace_fire_bg.png differ diff --git a/games/minimal/mods/default/textures/default_furnace_fire_fg.png b/games/minimal/mods/default/textures/default_furnace_fire_fg.png new file mode 100644 index 0000000..7a126e3 Binary files /dev/null and b/games/minimal/mods/default/textures/default_furnace_fire_fg.png differ diff --git a/games/minimal/mods/default/textures/default_furnace_front.png b/games/minimal/mods/default/textures/default_furnace_front.png new file mode 100644 index 0000000..4da398c Binary files /dev/null and b/games/minimal/mods/default/textures/default_furnace_front.png differ diff --git a/games/minimal/mods/default/textures/default_furnace_front_active.png b/games/minimal/mods/default/textures/default_furnace_front_active.png new file mode 100644 index 0000000..06c4ab3 Binary files /dev/null and b/games/minimal/mods/default/textures/default_furnace_front_active.png differ diff --git a/games/minimal/mods/default/textures/default_furnace_side.png b/games/minimal/mods/default/textures/default_furnace_side.png new file mode 100644 index 0000000..c729de9 Binary files /dev/null and b/games/minimal/mods/default/textures/default_furnace_side.png differ diff --git a/games/minimal/mods/default/textures/default_glass.png b/games/minimal/mods/default/textures/default_glass.png new file mode 100644 index 0000000..fd665a4 Binary files /dev/null and b/games/minimal/mods/default/textures/default_glass.png differ diff --git a/games/minimal/mods/default/textures/default_grass.png b/games/minimal/mods/default/textures/default_grass.png new file mode 100644 index 0000000..1d76708 Binary files /dev/null and b/games/minimal/mods/default/textures/default_grass.png differ diff --git a/games/minimal/mods/default/textures/default_grass_footsteps.png b/games/minimal/mods/default/textures/default_grass_footsteps.png new file mode 100644 index 0000000..8349033 Binary files /dev/null and b/games/minimal/mods/default/textures/default_grass_footsteps.png differ diff --git a/games/minimal/mods/default/textures/default_grass_side.png b/games/minimal/mods/default/textures/default_grass_side.png new file mode 100644 index 0000000..4f4f680 Binary files /dev/null and b/games/minimal/mods/default/textures/default_grass_side.png differ diff --git a/games/minimal/mods/default/textures/default_gravel.png b/games/minimal/mods/default/textures/default_gravel.png new file mode 100644 index 0000000..4b47e23 Binary files /dev/null and b/games/minimal/mods/default/textures/default_gravel.png differ diff --git a/games/minimal/mods/default/textures/default_iron_lump.png b/games/minimal/mods/default/textures/default_iron_lump.png new file mode 100644 index 0000000..6b515f6 Binary files /dev/null and b/games/minimal/mods/default/textures/default_iron_lump.png differ diff --git a/games/minimal/mods/default/textures/default_junglegrass.png b/games/minimal/mods/default/textures/default_junglegrass.png new file mode 100644 index 0000000..7066f7d Binary files /dev/null and b/games/minimal/mods/default/textures/default_junglegrass.png differ diff --git a/games/minimal/mods/default/textures/default_jungletree.png b/games/minimal/mods/default/textures/default_jungletree.png new file mode 100644 index 0000000..a1fd36a Binary files /dev/null and b/games/minimal/mods/default/textures/default_jungletree.png differ diff --git a/games/minimal/mods/default/textures/default_jungletree_top.png b/games/minimal/mods/default/textures/default_jungletree_top.png new file mode 100644 index 0000000..a13fdae Binary files /dev/null and b/games/minimal/mods/default/textures/default_jungletree_top.png differ diff --git a/games/minimal/mods/default/textures/default_ladder.png b/games/minimal/mods/default/textures/default_ladder.png new file mode 100644 index 0000000..46757b8 Binary files /dev/null and b/games/minimal/mods/default/textures/default_ladder.png differ diff --git a/games/minimal/mods/default/textures/default_lava.png b/games/minimal/mods/default/textures/default_lava.png new file mode 100644 index 0000000..a4cf649 Binary files /dev/null and b/games/minimal/mods/default/textures/default_lava.png differ diff --git a/games/minimal/mods/default/textures/default_lava_flowing_animated.png b/games/minimal/mods/default/textures/default_lava_flowing_animated.png new file mode 100644 index 0000000..0bbd136 Binary files /dev/null and b/games/minimal/mods/default/textures/default_lava_flowing_animated.png differ diff --git a/games/minimal/mods/default/textures/default_lava_source_animated.png b/games/minimal/mods/default/textures/default_lava_source_animated.png new file mode 100644 index 0000000..aa9d57c Binary files /dev/null and b/games/minimal/mods/default/textures/default_lava_source_animated.png differ diff --git a/games/minimal/mods/default/textures/default_leaves.png b/games/minimal/mods/default/textures/default_leaves.png new file mode 100644 index 0000000..00ce447 Binary files /dev/null and b/games/minimal/mods/default/textures/default_leaves.png differ diff --git a/games/minimal/mods/default/textures/default_mese.png b/games/minimal/mods/default/textures/default_mese.png new file mode 100644 index 0000000..123f0f4 Binary files /dev/null and b/games/minimal/mods/default/textures/default_mese.png differ diff --git a/games/minimal/mods/default/textures/default_mineral_coal.png b/games/minimal/mods/default/textures/default_mineral_coal.png new file mode 100644 index 0000000..0f52062 Binary files /dev/null and b/games/minimal/mods/default/textures/default_mineral_coal.png differ diff --git a/games/minimal/mods/default/textures/default_mineral_iron.png b/games/minimal/mods/default/textures/default_mineral_iron.png new file mode 100644 index 0000000..6c894ce Binary files /dev/null and b/games/minimal/mods/default/textures/default_mineral_iron.png differ diff --git a/games/minimal/mods/default/textures/default_mossycobble.png b/games/minimal/mods/default/textures/default_mossycobble.png new file mode 100644 index 0000000..5082953 Binary files /dev/null and b/games/minimal/mods/default/textures/default_mossycobble.png differ diff --git a/games/minimal/mods/default/textures/default_nc_back.png b/games/minimal/mods/default/textures/default_nc_back.png new file mode 100644 index 0000000..e479ace Binary files /dev/null and b/games/minimal/mods/default/textures/default_nc_back.png differ diff --git a/games/minimal/mods/default/textures/default_nc_front.png b/games/minimal/mods/default/textures/default_nc_front.png new file mode 100644 index 0000000..c9dd6a3 Binary files /dev/null and b/games/minimal/mods/default/textures/default_nc_front.png differ diff --git a/games/minimal/mods/default/textures/default_nc_rb.png b/games/minimal/mods/default/textures/default_nc_rb.png new file mode 100644 index 0000000..685a22c Binary files /dev/null and b/games/minimal/mods/default/textures/default_nc_rb.png differ diff --git a/games/minimal/mods/default/textures/default_nc_side.png b/games/minimal/mods/default/textures/default_nc_side.png new file mode 100644 index 0000000..bc85427 Binary files /dev/null and b/games/minimal/mods/default/textures/default_nc_side.png differ diff --git a/games/minimal/mods/default/textures/default_paper.png b/games/minimal/mods/default/textures/default_paper.png new file mode 100644 index 0000000..3c31f77 Binary files /dev/null and b/games/minimal/mods/default/textures/default_paper.png differ diff --git a/games/minimal/mods/default/textures/default_papyrus.png b/games/minimal/mods/default/textures/default_papyrus.png new file mode 100644 index 0000000..3707e40 Binary files /dev/null and b/games/minimal/mods/default/textures/default_papyrus.png differ diff --git a/games/minimal/mods/default/textures/default_rail.png b/games/minimal/mods/default/textures/default_rail.png new file mode 100644 index 0000000..777e10c Binary files /dev/null and b/games/minimal/mods/default/textures/default_rail.png differ diff --git a/games/minimal/mods/default/textures/default_rail_crossing.png b/games/minimal/mods/default/textures/default_rail_crossing.png new file mode 100644 index 0000000..a988c47 Binary files /dev/null and b/games/minimal/mods/default/textures/default_rail_crossing.png differ diff --git a/games/minimal/mods/default/textures/default_rail_curved.png b/games/minimal/mods/default/textures/default_rail_curved.png new file mode 100644 index 0000000..f87e826 Binary files /dev/null and b/games/minimal/mods/default/textures/default_rail_curved.png differ diff --git a/games/minimal/mods/default/textures/default_rail_t_junction.png b/games/minimal/mods/default/textures/default_rail_t_junction.png new file mode 100644 index 0000000..fd25b5b Binary files /dev/null and b/games/minimal/mods/default/textures/default_rail_t_junction.png differ diff --git a/games/minimal/mods/default/textures/default_sand.png b/games/minimal/mods/default/textures/default_sand.png new file mode 100644 index 0000000..1a56cc7 Binary files /dev/null and b/games/minimal/mods/default/textures/default_sand.png differ diff --git a/games/minimal/mods/default/textures/default_sandstone.png b/games/minimal/mods/default/textures/default_sandstone.png new file mode 100644 index 0000000..bd9cb86 Binary files /dev/null and b/games/minimal/mods/default/textures/default_sandstone.png differ diff --git a/games/minimal/mods/default/textures/default_sapling.png b/games/minimal/mods/default/textures/default_sapling.png new file mode 100644 index 0000000..9360232 Binary files /dev/null and b/games/minimal/mods/default/textures/default_sapling.png differ diff --git a/games/minimal/mods/default/textures/default_scorched_stuff.png b/games/minimal/mods/default/textures/default_scorched_stuff.png new file mode 100644 index 0000000..c7efc7e Binary files /dev/null and b/games/minimal/mods/default/textures/default_scorched_stuff.png differ diff --git a/games/minimal/mods/default/textures/default_sign_wall.png b/games/minimal/mods/default/textures/default_sign_wall.png new file mode 100644 index 0000000..93c075a Binary files /dev/null and b/games/minimal/mods/default/textures/default_sign_wall.png differ diff --git a/games/minimal/mods/default/textures/default_steel_block.png b/games/minimal/mods/default/textures/default_steel_block.png new file mode 100644 index 0000000..9c0a0e2 Binary files /dev/null and b/games/minimal/mods/default/textures/default_steel_block.png differ diff --git a/games/minimal/mods/default/textures/default_steel_ingot.png b/games/minimal/mods/default/textures/default_steel_ingot.png new file mode 100644 index 0000000..ad133bc Binary files /dev/null and b/games/minimal/mods/default/textures/default_steel_ingot.png differ diff --git a/games/minimal/mods/default/textures/default_stick.png b/games/minimal/mods/default/textures/default_stick.png new file mode 100644 index 0000000..055a6ac Binary files /dev/null and b/games/minimal/mods/default/textures/default_stick.png differ diff --git a/games/minimal/mods/default/textures/default_stone.png b/games/minimal/mods/default/textures/default_stone.png new file mode 100644 index 0000000..a835fe0 Binary files /dev/null and b/games/minimal/mods/default/textures/default_stone.png differ diff --git a/games/minimal/mods/default/textures/default_tnt_bottom.png b/games/minimal/mods/default/textures/default_tnt_bottom.png new file mode 100644 index 0000000..4eda060 Binary files /dev/null and b/games/minimal/mods/default/textures/default_tnt_bottom.png differ diff --git a/games/minimal/mods/default/textures/default_tnt_side.png b/games/minimal/mods/default/textures/default_tnt_side.png new file mode 100644 index 0000000..c259726 Binary files /dev/null and b/games/minimal/mods/default/textures/default_tnt_side.png differ diff --git a/games/minimal/mods/default/textures/default_tnt_top.png b/games/minimal/mods/default/textures/default_tnt_top.png new file mode 100644 index 0000000..a031a34 Binary files /dev/null and b/games/minimal/mods/default/textures/default_tnt_top.png differ diff --git a/games/minimal/mods/default/textures/default_tool_mesepick.png b/games/minimal/mods/default/textures/default_tool_mesepick.png new file mode 100644 index 0000000..2b5e12c Binary files /dev/null and b/games/minimal/mods/default/textures/default_tool_mesepick.png differ diff --git a/games/minimal/mods/default/textures/default_tool_steelaxe.png b/games/minimal/mods/default/textures/default_tool_steelaxe.png new file mode 100644 index 0000000..fae19dd Binary files /dev/null and b/games/minimal/mods/default/textures/default_tool_steelaxe.png differ diff --git a/games/minimal/mods/default/textures/default_tool_steelpick.png b/games/minimal/mods/default/textures/default_tool_steelpick.png new file mode 100644 index 0000000..3a8f586 Binary files /dev/null and b/games/minimal/mods/default/textures/default_tool_steelpick.png differ diff --git a/games/minimal/mods/default/textures/default_tool_steelshovel.png b/games/minimal/mods/default/textures/default_tool_steelshovel.png new file mode 100644 index 0000000..d5b1bc6 Binary files /dev/null and b/games/minimal/mods/default/textures/default_tool_steelshovel.png differ diff --git a/games/minimal/mods/default/textures/default_tool_steelsword.png b/games/minimal/mods/default/textures/default_tool_steelsword.png new file mode 100644 index 0000000..efc61a0 Binary files /dev/null and b/games/minimal/mods/default/textures/default_tool_steelsword.png differ diff --git a/games/minimal/mods/default/textures/default_tool_stoneaxe.png b/games/minimal/mods/default/textures/default_tool_stoneaxe.png new file mode 100644 index 0000000..532e16f Binary files /dev/null and b/games/minimal/mods/default/textures/default_tool_stoneaxe.png differ diff --git a/games/minimal/mods/default/textures/default_tool_stonepick.png b/games/minimal/mods/default/textures/default_tool_stonepick.png new file mode 100644 index 0000000..d9156ee Binary files /dev/null and b/games/minimal/mods/default/textures/default_tool_stonepick.png differ diff --git a/games/minimal/mods/default/textures/default_tool_stoneshovel.png b/games/minimal/mods/default/textures/default_tool_stoneshovel.png new file mode 100644 index 0000000..7bbd2d4 Binary files /dev/null and b/games/minimal/mods/default/textures/default_tool_stoneshovel.png differ diff --git a/games/minimal/mods/default/textures/default_tool_stonesword.png b/games/minimal/mods/default/textures/default_tool_stonesword.png new file mode 100644 index 0000000..533b873 Binary files /dev/null and b/games/minimal/mods/default/textures/default_tool_stonesword.png differ diff --git a/games/minimal/mods/default/textures/default_tool_woodaxe.png b/games/minimal/mods/default/textures/default_tool_woodaxe.png new file mode 100644 index 0000000..6e70f4a Binary files /dev/null and b/games/minimal/mods/default/textures/default_tool_woodaxe.png differ diff --git a/games/minimal/mods/default/textures/default_tool_woodpick.png b/games/minimal/mods/default/textures/default_tool_woodpick.png new file mode 100644 index 0000000..15c61f4 Binary files /dev/null and b/games/minimal/mods/default/textures/default_tool_woodpick.png differ diff --git a/games/minimal/mods/default/textures/default_tool_woodshovel.png b/games/minimal/mods/default/textures/default_tool_woodshovel.png new file mode 100644 index 0000000..e0b3608 Binary files /dev/null and b/games/minimal/mods/default/textures/default_tool_woodshovel.png differ diff --git a/games/minimal/mods/default/textures/default_tool_woodsword.png b/games/minimal/mods/default/textures/default_tool_woodsword.png new file mode 100644 index 0000000..8099af1 Binary files /dev/null and b/games/minimal/mods/default/textures/default_tool_woodsword.png differ diff --git a/games/minimal/mods/default/textures/default_torch.png b/games/minimal/mods/default/textures/default_torch.png new file mode 100644 index 0000000..21d98bd Binary files /dev/null and b/games/minimal/mods/default/textures/default_torch.png differ diff --git a/games/minimal/mods/default/textures/default_torch_on_ceiling.png b/games/minimal/mods/default/textures/default_torch_on_ceiling.png new file mode 100644 index 0000000..dccd380 Binary files /dev/null and b/games/minimal/mods/default/textures/default_torch_on_ceiling.png differ diff --git a/games/minimal/mods/default/textures/default_torch_on_floor.png b/games/minimal/mods/default/textures/default_torch_on_floor.png new file mode 100644 index 0000000..c220107 Binary files /dev/null and b/games/minimal/mods/default/textures/default_torch_on_floor.png differ diff --git a/games/minimal/mods/default/textures/default_tree.png b/games/minimal/mods/default/textures/default_tree.png new file mode 100644 index 0000000..65abfc2 Binary files /dev/null and b/games/minimal/mods/default/textures/default_tree.png differ diff --git a/games/minimal/mods/default/textures/default_tree_top.png b/games/minimal/mods/default/textures/default_tree_top.png new file mode 100644 index 0000000..3e6bd52 Binary files /dev/null and b/games/minimal/mods/default/textures/default_tree_top.png differ diff --git a/games/minimal/mods/default/textures/default_water.png b/games/minimal/mods/default/textures/default_water.png new file mode 100644 index 0000000..3e385ae Binary files /dev/null and b/games/minimal/mods/default/textures/default_water.png differ diff --git a/games/minimal/mods/default/textures/default_wood.png b/games/minimal/mods/default/textures/default_wood.png new file mode 100644 index 0000000..57c1d7c Binary files /dev/null and b/games/minimal/mods/default/textures/default_wood.png differ diff --git a/games/minimal/mods/default/textures/heart.png b/games/minimal/mods/default/textures/heart.png new file mode 100644 index 0000000..c25f43b Binary files /dev/null and b/games/minimal/mods/default/textures/heart.png differ diff --git a/games/minimal/mods/default/textures/player.png b/games/minimal/mods/default/textures/player.png new file mode 100644 index 0000000..cf5d83f Binary files /dev/null and b/games/minimal/mods/default/textures/player.png differ diff --git a/games/minimal/mods/default/textures/player_back.png b/games/minimal/mods/default/textures/player_back.png new file mode 100644 index 0000000..d498674 Binary files /dev/null and b/games/minimal/mods/default/textures/player_back.png differ diff --git a/games/minimal/mods/default/textures/treeprop.png b/games/minimal/mods/default/textures/treeprop.png new file mode 100644 index 0000000..eb8a8e6 Binary files /dev/null and b/games/minimal/mods/default/textures/treeprop.png differ diff --git a/games/minimal/mods/default/textures/wieldhand.png b/games/minimal/mods/default/textures/wieldhand.png new file mode 100644 index 0000000..dbed6ad Binary files /dev/null and b/games/minimal/mods/default/textures/wieldhand.png differ diff --git a/games/minimal/mods/errorhandler_test/init.lua b/games/minimal/mods/errorhandler_test/init.lua new file mode 100644 index 0000000..9d1535c --- /dev/null +++ b/games/minimal/mods/errorhandler_test/init.lua @@ -0,0 +1,106 @@ +-- +-- exception handler test module +-- +-- +-- To avoid this from crashing the module will startup in inactive mode. +-- to make specific errors happen you need to cause them by following +-- chat command: +-- +-- exceptiontest +-- +-- location has to be one of: +-- * mapgen: cause in next on_generate call +-- * entity_step: spawn a entity and make it do error in on_step +-- * globalstep: do error in next globalstep +-- * immediate: cause right in chat handler +-- +-- errortypes defined are: +-- * segv: make sigsegv happen +-- * zerodivision: cause a division by zero to happen +-- * exception: throw an exception + +if core.cause_error == nil or + type(core.cause_error) ~= "function" then + return +end + + +core.log("action", "WARNING: loading exception handler test module!") + +local exceptiondata = { + tocause = "none", + mapgen = false, + entity_step = false, + globalstep = false, +} + +local exception_entity = +{ + on_step = function(self, dtime) + if exceptiondata.entity_step then + core.cause_error(exceptiondata.tocause) + end + end, +} +local exception_entity_name = "errorhandler_test:error_entity" + +local function exception_chat_handler(playername, param) + local parameters = param:split(" ") + + if #parameters ~= 2 then + core.chat_send_player(playername, "Invalid argument count for exceptiontest") + end + + core.log("error", "Causing error at:" .. parameters[1]) + + if parameters[1] == "mapgen" then + exceptiondata.tocause = parameters[2] + exceptiondata.mapgen = true + elseif parameters[1] == "entity_step" then + --spawn entity at player location + local player = core.get_player_by_name(playername) + + if player:is_player() then + local pos = player:getpos() + + core.add_entity(pos, exception_entity_name) + end + + exceptiondata.tocause = parameters[2] + exceptiondata.entity_step = true + + elseif parameters[1] == "globalstep" then + exceptiondata.tocause = parameters[2] + exceptiondata.globalstep = true + + elseif parameters[1] == "immediate" then + core.cause_error(parameters[2]) + + else + core.chat_send_player(playername, "Invalid error location: " .. dump(parameters[1])) + end +end + +core.register_chatcommand("exceptiontest", + { + params = " ", + description = "cause a given error to happen.\n" .. + " location=(mapgen,entity_step,globalstep,immediate)\n" .. + " errortype=(segv,zerodivision,exception)", + func = exception_chat_handler, + privs = { server=true } + }) + +core.register_globalstep(function(dtime) + if exceptiondata.globalstep then + core.cause_error(exceptiondata.tocause) + end +end) + +core.register_on_generated(function(minp, maxp, blockseed) + if exceptiondata.mapgen then + core.cause_error(exceptiondata.tocause) + end +end) + +core.register_entity(exception_entity_name, exception_entity) diff --git a/games/minimal/mods/experimental/depends.txt b/games/minimal/mods/experimental/depends.txt new file mode 100644 index 0000000..3a7daa1 --- /dev/null +++ b/games/minimal/mods/experimental/depends.txt @@ -0,0 +1,2 @@ +default + diff --git a/games/minimal/mods/experimental/init.lua b/games/minimal/mods/experimental/init.lua new file mode 100644 index 0000000..729191b --- /dev/null +++ b/games/minimal/mods/experimental/init.lua @@ -0,0 +1,601 @@ +-- +-- Experimental things +-- + +-- For testing random stuff + +experimental = {} + +function experimental.print_to_everything(msg) + minetest.log("action", msg) + minetest.chat_send_all(msg) +end + +--[[ +experimental.player_visual_index = 0 +function switch_player_visual() + for _, obj in pairs(minetest.get_connected_players()) do + if experimental.player_visual_index == 0 then + obj:set_properties({visual="upright_sprite"}) + else + obj:set_properties({visual="cube"}) + end + end + experimental.player_visual_index = (experimental.player_visual_index + 1) % 2 + minetest.after(1.0, switch_player_visual) +end +minetest.after(1.0, switch_player_visual) +]] + +minetest.register_node("experimental:soundblock", { + tile_images = {"unknown_node.png", "default_tnt_bottom.png", + "default_tnt_side.png", "default_tnt_side.png", + "default_tnt_side.png", "default_tnt_side.png"}, + inventory_image = minetest.inventorycube("unknown_node.png", + "default_tnt_side.png", "default_tnt_side.png"), + groups = {dig_immediate=3}, +}) + +minetest.register_alias("sb", "experimental:soundblock") + +minetest.register_abm({ + nodenames = {"experimental:soundblock"}, + interval = 1, + chance = 1, + action = function(p0, node, _, _) + minetest.sound_play("default_grass_footstep", {pos=p0, gain=0.5}) + end, +}) + +--[[ +stepsound = -1 +function test_sound() + print("test_sound") + stepsound = minetest.sound_play("default_grass_footstep", {gain=1.0}) + minetest.after(2.0, test_sound) + --minetest.after(0.1, test_sound_stop) +end +function test_sound_stop() + print("test_sound_stop") + minetest.sound_stop(stepsound) + minetest.after(2.0, test_sound) +end +test_sound() +--]] + +function on_step(dtime) + -- print("experimental on_step") + --[[ + objs = minetest.get_objects_inside_radius({x=0,y=0,z=0}, 10) + for k, obj in pairs(objs) do + name = obj:get_player_name() + if name then + print(name.." at "..dump(obj:getpos())) + print(name.." dir: "..dump(obj:get_look_dir())) + print(name.." pitch: "..dump(obj:get_look_pitch())) + print(name.." yaw: "..dump(obj:get_look_yaw())) + else + print("Some object at "..dump(obj:getpos())) + end + end + --]] + --[[ + if experimental.t1 == nil then + experimental.t1 = 0 + end + experimental.t1 = experimental.t1 + dtime + if experimental.t1 >= 2 then + experimental.t1 = experimental.t1 - 2 + minetest.log("time of day is "..minetest.get_timeofday()) + if experimental.day then + minetest.log("forcing day->night") + experimental.day = false + minetest.set_timeofday(0.0) + else + minetest.log("forcing night->day") + experimental.day = true + minetest.set_timeofday(0.5) + end + minetest.log("time of day is "..minetest.get_timeofday()) + end + --]] +end +minetest.register_globalstep(on_step) + +-- +-- Random stuff +-- + +-- +-- TNT (not functional) +-- + +minetest.register_craft({ + output = 'experimental:tnt', + recipe = { + {'default:wood'}, + {'default:coal_lump'}, + {'default:wood'} + } +}) + +minetest.register_node("experimental:tnt", { + tile_images = {"default_tnt_top.png", "default_tnt_bottom.png", + "default_tnt_side.png", "default_tnt_side.png", + "default_tnt_side.png", "default_tnt_side.png"}, + inventory_image = minetest.inventorycube("default_tnt_top.png", + "default_tnt_side.png", "default_tnt_side.png"), + drop = '', -- Get nothing + material = { + diggability = "not", + }, +}) + +minetest.register_on_punchnode(function(p, node) + if node.name == "experimental:tnt" then + minetest.remove_node(p) + minetest.add_entity(p, "experimental:tnt") + nodeupdate(p) + end +end) + +local TNT = { + -- Static definition + physical = true, -- Collides with things + -- weight = 5, + collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5}, + visual = "cube", + textures = {"default_tnt_top.png", "default_tnt_bottom.png", + "default_tnt_side.png", "default_tnt_side.png", + "default_tnt_side.png", "default_tnt_side.png"}, + -- Initial value for our timer + timer = 0, + -- Number of punches required to defuse + health = 1, + blinktimer = 0, + blinkstatus = true, +} + +-- Called when a TNT object is created +function TNT:on_activate(staticdata) + print("TNT:on_activate()") + self.object:setvelocity({x=0, y=4, z=0}) + self.object:setacceleration({x=0, y=-10, z=0}) + self.object:settexturemod("^[brighten") + self.object:set_armor_groups({immortal=1}) +end + +-- Called periodically +function TNT:on_step(dtime) + --print("TNT:on_step()") + self.timer = self.timer + dtime + self.blinktimer = self.blinktimer + dtime + if self.blinktimer > 0.5 then + self.blinktimer = self.blinktimer - 0.5 + if self.blinkstatus then + self.object:settexturemod("") + else + self.object:settexturemod("^[brighten") + end + self.blinkstatus = not self.blinkstatus + end +end + +-- Called when object is punched +function TNT:on_punch(hitter) + print("TNT:on_punch()") + self.health = self.health - 1 + if self.health <= 0 then + self.object:remove() + hitter:get_inventory():add_item("main", "experimental:tnt") + --hitter:set_hp(hitter:get_hp() - 1) + end +end + +-- Called when object is right-clicked +function TNT:on_rightclick(clicker) + --pos = self.object:getpos() + --pos = {x=pos.x, y=pos.y+0.1, z=pos.z} + --self.object:moveto(pos, false) +end + +--print("TNT dump: "..dump(TNT)) +--print("Registering TNT"); +minetest.register_entity("experimental:tnt", TNT) + +-- Add TNT's old name also +minetest.register_alias("TNT", "experimental:tnt") + +-- +-- The dummyball! +-- + +minetest.register_entity("experimental:dummyball", { + initial_properties = { + hp_max = 20, + physical = false, + collisionbox = {-0.4,-0.4,-0.4, 0.4,0.4,0.4}, + visual = "sprite", + visual_size = {x=1, y=1}, + textures = {"experimental_dummyball.png"}, + spritediv = {x=1, y=3}, + initial_sprite_basepos = {x=0, y=0}, + }, + + phase = 0, + phasetimer = 0, + + on_activate = function(self, staticdata) + minetest.log("Dummyball activated!") + end, + + on_step = function(self, dtime) + self.phasetimer = self.phasetimer + dtime + if self.phasetimer > 2.0 then + self.phasetimer = self.phasetimer - 2.0 + self.phase = self.phase + 1 + if self.phase >= 3 then + self.phase = 0 + end + self.object:setsprite({x=0, y=self.phase}) + phasearmor = { + [0]={cracky=3}, + [1]={crumbly=3}, + [2]={fleshy=3} + } + self.object:set_armor_groups(phasearmor[self.phase]) + end + end, + + on_punch = function(self, hitter) + end, +}) + +minetest.register_on_chat_message(function(name, message) + local cmd = "/dummyball" + if message:sub(0, #cmd) == cmd then + count = tonumber(message:sub(#cmd+1)) or 1 + if not minetest.get_player_privs(name)["give"] then + minetest.chat_send_player(name, "you don't have permission to spawn (give)") + return true -- Handled chat message + end + if not minetest.get_player_privs(name)["interact"] then + minetest.chat_send_player(name, "you don't have permission to interact") + return true -- Handled chat message + end + if count >= 2 and not minetest.get_player_privs(name)["server"] then + minetest.chat_send_player(name, "you don't have " .. + "permission to spawn multiple " .. + "dummyballs (server)") + return true -- Handled chat message + end + local player = minetest.get_player_by_name(name) + if player == nil then + print("Unable to spawn entity, player is nil") + return true -- Handled chat message + end + local entityname = "experimental:dummyball" + local p = player:getpos() + p.y = p.y + 1 + for i = 1,count do + minetest.add_entity(p, entityname) + end + minetest.chat_send_player(name, '"'..entityname + ..'" spawned '..tostring(count)..' time(s).'); + return true -- Handled chat message + end +end) + +-- +-- A test entity for testing animated and yaw-modulated sprites +-- + +minetest.register_entity("experimental:testentity", { + -- Static definition + physical = true, -- Collides with things + -- weight = 5, + collisionbox = {-0.7,-1.35,-0.7, 0.7,1.0,0.7}, + --collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5}, + visual = "sprite", + visual_size = {x=2, y=3}, + textures = {"dungeon_master.png^[makealpha:128,0,0^[makealpha:128,128,0"}, + spritediv = {x=6, y=5}, + initial_sprite_basepos = {x=0, y=0}, + + on_activate = function(self, staticdata) + print("testentity.on_activate") + self.object:setsprite({x=0,y=0}, 1, 0, true) + --self.object:setsprite({x=0,y=0}, 4, 0.3, true) + + -- Set gravity + self.object:setacceleration({x=0, y=-10, z=0}) + -- Jump a bit upwards + self.object:setvelocity({x=0, y=10, z=0}) + end, + + on_punch = function(self, hitter) + self.object:remove() + hitter:add_to_inventory('craft testobject1 1') + end, +}) + +-- +-- More random stuff +-- + +minetest.register_on_respawnplayer(function(player) + print("on_respawnplayer") + -- player:setpos({x=0, y=30, z=0}) + -- return true +end) + +minetest.register_on_generated(function(minp, maxp) + --print("on_generated: minp="..dump(minp).." maxp="..dump(maxp)) + --cp = {x=(minp.x+maxp.x)/2, y=(minp.y+maxp.y)/2, z=(minp.z+maxp.z)/2} + --minetest.add_node(cp, {name="sand"}) +end) + +-- Example setting get +--print("setting max_users = " .. dump(minetest.setting_get("max_users"))) +--print("setting asdf = " .. dump(minetest.setting_get("asdf"))) + +minetest.register_on_chat_message(function(name, message) + --[[print("on_chat_message: name="..dump(name).." message="..dump(message)) + local cmd = "/testcommand" + if message:sub(0, #cmd) == cmd then + print(cmd.." invoked") + return true + end + local cmd = "/help" + if message:sub(0, #cmd) == cmd then + print("script-overridden help command") + minetest.chat_send_all("script-overridden help command") + return true + end]] +end) + +-- Grow papyrus on TNT every 10 seconds +--[[minetest.register_abm({ + nodenames = {"TNT"}, + interval = 10.0, + chance = 1, + action = function(pos, node, active_object_count, active_object_count_wider) + print("TNT ABM action") + pos.y = pos.y + 1 + minetest.add_node(pos, {name="papyrus"}) + end, +})]] + +-- Replace texts of alls signs with "foo" every 10 seconds +--[[minetest.register_abm({ + nodenames = {"sign_wall"}, + interval = 10.0, + chance = 1, + action = function(pos, node, active_object_count, active_object_count_wider) + print("ABM: Sign text changed") + local meta = minetest.get_meta(pos) + meta:set_text("foo") + end, +})]] + +--[[local ncpos = nil +local ncq = 1 +local ncstuff = { + {2, 1, 0, 3}, {3, 0, 1, 2}, {4, -1, 0, 1}, {5, -1, 0, 1}, {6, 0, -1, 0}, + {7, 0, -1, 0}, {8, 1, 0, 3}, {9, 1, 0, 3}, {10, 1, 0, 3}, {11, 0, 1, 2}, + {12, 0, 1, 2}, {13, 0, 1, 2}, {14, -1, 0, 1}, {15, -1, 0, 1}, {16, -1, 0, 1}, + {17, -1, 0, 1}, {18, 0, -1, 0}, {19, 0, -1, 0}, {20, 0, -1, 0}, {21, 0, -1, 0}, + {22, 1, 0, 3}, {23, 1, 0, 3}, {24, 1, 0, 3}, {25, 1, 0, 3}, {10, 0, 1, 2} +} +local ncold = {} +local nctime = nil + +minetest.register_abm({ + nodenames = {"dirt_with_grass"}, + interval = 100000.0, + chance = 1, + action = function(pos, node, active_object_count, active_object_count_wider) + if ncpos ~= nil then + return + end + + if pos.x % 16 ~= 8 or pos.z % 16 ~= 8 then + return + end + + pos.y = pos.y + 1 + n = minetest.get_node(pos) + print(dump(n)) + if n.name ~= "air" then + return + end + + pos.y = pos.y + 2 + ncpos = pos + nctime = os.clock() + minetest.add_node(ncpos, {name="nyancat"}) + end +}) + +minetest.register_abm({ + nodenames = {"nyancat"}, + interval = 1.0, + chance = 1, + action = function(pos, node, active_object_count, active_object_count_wider) + if ncpos == nil then + return + end + if pos.x == ncpos.x and pos.y == ncpos.y and pos.z == ncpos.z then + clock = os.clock() + if clock - nctime < 0.1 then + return + end + nctime = clock + + s0 = ncstuff[ncq] + ncq = s0[1] + s1 = ncstuff[ncq] + p0 = pos + p1 = {x = p0.x + s0[2], y = p0.y, z = p0.z + s0[3]} + p2 = {x = p1.x + s1[2], y = p1.y, z = p1.z + s1[3]} + table.insert(ncold, 1, p0) + while #ncold >= 10 do + minetest.add_node(ncold[#ncold], {name="air"}) + table.remove(ncold, #ncold) + end + minetest.add_node(p0, {name="nyancat_rainbow"}) + minetest.add_node(p1, {name="nyancat", param1=s0[4]}) + minetest.add_node(p2, {name="air"}) + ncpos = p1 + end + end, +})--]] + +minetest.register_node("experimental:tester_node_1", { + description = "Tester Node 1 (construct/destruct/timer)", + tile_images = {"wieldhand.png"}, + groups = {oddly_breakable_by_hand=2}, + sounds = default.node_sound_wood_defaults(), + -- This was known to cause a bug in minetest.item_place_node() when used + -- via minetest.place_node(), causing a placer with no position + paramtype2 = "facedir", + + on_construct = function(pos) + experimental.print_to_everything("experimental:tester_node_1:on_construct("..minetest.pos_to_string(pos)..")") + local meta = minetest.get_meta(pos) + meta:set_string("mine", "test") + local timer = minetest.get_node_timer(pos) + timer:start(4, 3) + end, + + after_place_node = function(pos, placer) + experimental.print_to_everything("experimental:tester_node_1:after_place_node("..minetest.pos_to_string(pos)..")") + local meta = minetest.get_meta(pos) + if meta:get_string("mine") == "test" then + experimental.print_to_everything("correct metadata found") + else + experimental.print_to_everything("incorrect metadata found") + end + end, + + on_destruct = function(pos) + experimental.print_to_everything("experimental:tester_node_1:on_destruct("..minetest.pos_to_string(pos)..")") + end, + + after_destruct = function(pos) + experimental.print_to_everything("experimental:tester_node_1:after_destruct("..minetest.pos_to_string(pos)..")") + end, + + after_dig_node = function(pos, oldnode, oldmetadata, digger) + experimental.print_to_everything("experimental:tester_node_1:after_dig_node("..minetest.pos_to_string(pos)..")") + end, + + on_timer = function(pos, elapsed) + experimental.print_to_everything("on_timer(): elapsed="..dump(elapsed)) + return true + end, +}) + +minetest.register_craftitem("experimental:tester_tool_1", { + description = "Tester Tool 1", + inventory_image = "experimental_tester_tool_1.png", + on_use = function(itemstack, user, pointed_thing) + --print(dump(pointed_thing)) + if pointed_thing.type == "node" then + if minetest.get_node(pointed_thing.under).name == "experimental:tester_node_1" then + local p = pointed_thing.under + minetest.log("action", "Tester tool used at "..minetest.pos_to_string(p)) + minetest.dig_node(p) + else + local p = pointed_thing.above + minetest.log("action", "Tester tool used at "..minetest.pos_to_string(p)) + minetest.place_node(p, {name="experimental:tester_node_1"}) + end + end + end, +}) + +minetest.register_craft({ + output = 'experimental:tester_tool_1', + recipe = { + {'group:crumbly'}, + {'group:crumbly'}, + } +}) + +--[[minetest.register_on_joinplayer(function(player) + minetest.after(3, function() + player:set_inventory_formspec("size[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;]") + end) +end)]] + +-- Create a detached inventory +local inv = minetest.create_detached_inventory("test_inventory", { + allow_move = function(inv, from_list, from_index, to_list, to_index, count, player) + experimental.print_to_everything("allow move asked") + return count -- Allow all + end, + allow_put = function(inv, listname, index, stack, player) + experimental.print_to_everything("allow put asked") + return 1 -- Allow only 1 + end, + allow_take = function(inv, listname, index, stack, player) + experimental.print_to_everything("allow take asked") + return 4 -- Allow 4 at max + end, + on_move = function(inv, from_list, from_index, to_list, to_index, count, player) + experimental.print_to_everything(player:get_player_name().." moved items") + end, + on_put = function(inv, listname, index, stack, player) + experimental.print_to_everything(player:get_player_name().." put items") + end, + on_take = function(inv, listname, index, stack, player) + experimental.print_to_everything(player:get_player_name().." took items") + end, +}) +inv:set_size("main", 4*6) +inv:add_item("main", "experimental:tester_tool_1") +inv:add_item("main", "experimental:tnt 5") + +minetest.register_chatcommand("test1", { + params = "", + description = "Test 1: Modify player's inventory view", + func = function(name, param) + local player = minetest.get_player_by_name(name) + if not player then + return + end + player:set_inventory_formspec( + "size[13,7.5]".. + "image[6,0.6;1,2;player.png]".. + "list[current_player;main;5,3.5;8,4;]".. + "list[current_player;craft;8,0;3,3;]".. + "list[current_player;craftpreview;12,1;1,1;]".. + "list[detached:test_inventory;main;0,0;4,6;0]".. + "button[0.5,7;2,1;button1;Button 1]".. + "button_exit[2.5,7;2,1;button2;Exit Button]" + ) + minetest.chat_send_player(name, "Done."); + end, +}) + +minetest.register_on_player_receive_fields(function(player, formname, fields) + experimental.print_to_everything("Inventory fields 1: player="..player:get_player_name()..", fields="..dump(fields)) +end) +minetest.register_on_player_receive_fields(function(player, formname, fields) + experimental.print_to_everything("Inventory fields 2: player="..player:get_player_name()..", fields="..dump(fields)) + return true -- Disable the first callback +end) +minetest.register_on_player_receive_fields(function(player, formname, fields) + experimental.print_to_everything("Inventory fields 3: player="..player:get_player_name()..", fields="..dump(fields)) +end) + +minetest.log("experimental modname="..dump(minetest.get_current_modname())) +minetest.log("experimental modpath="..dump(minetest.get_modpath("experimental"))) +minetest.log("experimental worldpath="..dump(minetest.get_worldpath())) + +-- END diff --git a/games/minimal/mods/experimental/textures/experimental_dummyball.png b/games/minimal/mods/experimental/textures/experimental_dummyball.png new file mode 100644 index 0000000..256414f Binary files /dev/null and b/games/minimal/mods/experimental/textures/experimental_dummyball.png differ diff --git a/games/minimal/mods/experimental/textures/experimental_tester_tool_1.png b/games/minimal/mods/experimental/textures/experimental_tester_tool_1.png new file mode 100644 index 0000000..587923c Binary files /dev/null and b/games/minimal/mods/experimental/textures/experimental_tester_tool_1.png differ diff --git a/games/minimal/mods/give_initial_stuff/depends.txt b/games/minimal/mods/give_initial_stuff/depends.txt new file mode 100644 index 0000000..3a7daa1 --- /dev/null +++ b/games/minimal/mods/give_initial_stuff/depends.txt @@ -0,0 +1,2 @@ +default + diff --git a/games/minimal/mods/give_initial_stuff/init.lua b/games/minimal/mods/give_initial_stuff/init.lua new file mode 100644 index 0000000..29b835c --- /dev/null +++ b/games/minimal/mods/give_initial_stuff/init.lua @@ -0,0 +1,16 @@ +minetest.register_on_newplayer(function(player) + print("[minimal] giving initial stuff to player") + player:get_inventory():add_item('main', 'default:pick_stone') + player:get_inventory():add_item('main', 'default:torch 99') + player:get_inventory():add_item('main', 'default:cobble 99') + player:get_inventory():add_item('main', 'default:wood 99') + player:get_inventory():add_item('main', 'default:axe_steel') + player:get_inventory():add_item('main', 'default:shovel_steel') + player:get_inventory():add_item('main', 'default:pick_wood') + player:get_inventory():add_item('main', 'default:pick_steel') + player:get_inventory():add_item('main', 'default:pick_mese') + player:get_inventory():add_item('main', 'default:mese 99') + player:get_inventory():add_item('main', 'default:water_source 99') + player:get_inventory():add_item('main', 'experimental:tester_tool_1') +end) + diff --git a/games/minimal/mods/legacy/depends.txt b/games/minimal/mods/legacy/depends.txt new file mode 100644 index 0000000..3a7daa1 --- /dev/null +++ b/games/minimal/mods/legacy/depends.txt @@ -0,0 +1,2 @@ +default + diff --git a/games/minimal/mods/legacy/init.lua b/games/minimal/mods/legacy/init.lua new file mode 100644 index 0000000..98ad69b --- /dev/null +++ b/games/minimal/mods/legacy/init.lua @@ -0,0 +1,128 @@ +-- legacy (Minetest 0.4 mod) +-- Provides as much backwards-compatibility as feasible + +-- +-- Aliases to support loading 0.3 and old 0.4 worlds and inventories +-- + +minetest.register_alias("stone", "default:stone") +minetest.register_alias("stone_with_coal", "default:stone_with_coal") +minetest.register_alias("stone_with_iron", "default:stone_with_iron") +minetest.register_alias("dirt_with_grass", "default:dirt_with_grass") +minetest.register_alias("dirt_with_grass_footsteps", "default:dirt_with_grass_footsteps") +minetest.register_alias("dirt", "default:dirt") +minetest.register_alias("sand", "default:sand") +minetest.register_alias("gravel", "default:gravel") +minetest.register_alias("sandstone", "default:sandstone") +minetest.register_alias("clay", "default:clay") +minetest.register_alias("brick", "default:brick") +minetest.register_alias("tree", "default:tree") +minetest.register_alias("jungletree", "default:jungletree") +minetest.register_alias("junglegrass", "default:junglegrass") +minetest.register_alias("leaves", "default:leaves") +minetest.register_alias("cactus", "default:cactus") +minetest.register_alias("papyrus", "default:papyrus") +minetest.register_alias("bookshelf", "default:bookshelf") +minetest.register_alias("glass", "default:glass") +minetest.register_alias("wooden_fence", "default:fence_wood") +minetest.register_alias("rail", "default:rail") +minetest.register_alias("ladder", "default:ladder") +minetest.register_alias("wood", "default:wood") +minetest.register_alias("mese", "default:mese") +minetest.register_alias("cloud", "default:cloud") +minetest.register_alias("water_flowing", "default:water_flowing") +minetest.register_alias("water_source", "default:water_source") +minetest.register_alias("lava_flowing", "default:lava_flowing") +minetest.register_alias("lava_source", "default:lava_source") +minetest.register_alias("torch", "default:torch") +minetest.register_alias("sign_wall", "default:sign_wall") +minetest.register_alias("furnace", "default:furnace") +minetest.register_alias("chest", "default:chest") +minetest.register_alias("locked_chest", "default:chest_locked") +minetest.register_alias("cobble", "default:cobble") +minetest.register_alias("mossycobble", "default:mossycobble") +minetest.register_alias("steelblock", "default:steelblock") +minetest.register_alias("nyancat", "default:nyancat") +minetest.register_alias("nyancat_rainbow", "default:nyancat_rainbow") +minetest.register_alias("sapling", "default:sapling") +minetest.register_alias("apple", "default:apple") + +minetest.register_alias("WPick", "default:pick_wood") +minetest.register_alias("STPick", "default:pick_stone") +minetest.register_alias("SteelPick", "default:pick_steel") +minetest.register_alias("MesePick", "default:pick_mese") +minetest.register_alias("WShovel", "default:shovel_wood") +minetest.register_alias("STShovel", "default:shovel_stone") +minetest.register_alias("SteelShovel", "default:shovel_steel") +minetest.register_alias("WAxe", "default:axe_wood") +minetest.register_alias("STAxe", "default:axe_stone") +minetest.register_alias("SteelAxe", "default:axe_steel") +minetest.register_alias("WSword", "default:sword_wood") +minetest.register_alias("STSword", "default:sword_stone") +minetest.register_alias("SteelSword", "default:sword_steel") + +minetest.register_alias("Stick", "default:stick") +minetest.register_alias("paper", "default:paper") +minetest.register_alias("book", "default:book") +minetest.register_alias("lump_of_coal", "default:coal_lump") +minetest.register_alias("lump_of_iron", "default:iron_lump") +minetest.register_alias("lump_of_clay", "default:clay_lump") +minetest.register_alias("steel_ingot", "default:steel_ingot") +minetest.register_alias("clay_brick", "default:clay_brick") +minetest.register_alias("scorched_stuff", "default:scorched_stuff") + +-- +-- Old items +-- + +minetest.register_craftitem(":rat", { + description = "Rat", + inventory_image = "rat.png", + on_drop = function(item, dropper, pos) + item:take_item() + return item + end, + on_place = function(item, dropped, pointed) + pos = minetest.get_pointed_thing_position(pointed, true) + if pos ~= nil then + item:take_item() + return item + end + end +}) + +minetest.register_craftitem(":cooked_rat", { + description = "Cooked rat", + inventory_image = "cooked_rat.png", + on_use = minetest.item_eat(6), +}) + +minetest.register_craftitem(":firefly", { + description = "Firefly", + inventory_image = "firefly.png", + on_drop = function(item, dropper, pos) + item:take_item() + return item + end, + on_place = function(item, dropped, pointed) + pos = minetest.get_pointed_thing_position(pointed, true) + if pos ~= nil then + item:take_item() + return item + end + end +}) + +minetest.register_craft({ + type = "cooking", + output = "cooked_rat", + recipe = "rat", +}) + +minetest.register_craft({ + type = "cooking", + output = "scorched_stuff", + recipe = "cooked_rat", +}) + +-- END diff --git a/games/minimal/mods/legacy/textures/apple_iron.png b/games/minimal/mods/legacy/textures/apple_iron.png new file mode 100644 index 0000000..db59458 Binary files /dev/null and b/games/minimal/mods/legacy/textures/apple_iron.png differ diff --git a/games/minimal/mods/legacy/textures/cooked_rat.png b/games/minimal/mods/legacy/textures/cooked_rat.png new file mode 100644 index 0000000..776dc4e Binary files /dev/null and b/games/minimal/mods/legacy/textures/cooked_rat.png differ diff --git a/games/minimal/mods/legacy/textures/dungeon_master.png b/games/minimal/mods/legacy/textures/dungeon_master.png new file mode 100644 index 0000000..d52d8cc Binary files /dev/null and b/games/minimal/mods/legacy/textures/dungeon_master.png differ diff --git a/games/minimal/mods/legacy/textures/fireball.png b/games/minimal/mods/legacy/textures/fireball.png new file mode 100644 index 0000000..124469c Binary files /dev/null and b/games/minimal/mods/legacy/textures/fireball.png differ diff --git a/games/minimal/mods/legacy/textures/firefly.png b/games/minimal/mods/legacy/textures/firefly.png new file mode 100644 index 0000000..ea95a6a Binary files /dev/null and b/games/minimal/mods/legacy/textures/firefly.png differ diff --git a/games/minimal/mods/legacy/textures/oerkki1.png b/games/minimal/mods/legacy/textures/oerkki1.png new file mode 100644 index 0000000..061709c Binary files /dev/null and b/games/minimal/mods/legacy/textures/oerkki1.png differ diff --git a/games/minimal/mods/legacy/textures/oerkki1_damaged.png b/games/minimal/mods/legacy/textures/oerkki1_damaged.png new file mode 100644 index 0000000..b2a3033 Binary files /dev/null and b/games/minimal/mods/legacy/textures/oerkki1_damaged.png differ diff --git a/games/minimal/mods/legacy/textures/rat.png b/games/minimal/mods/legacy/textures/rat.png new file mode 100644 index 0000000..04cf9b8 Binary files /dev/null and b/games/minimal/mods/legacy/textures/rat.png differ diff --git a/games/minimal/mods/stairs/depends.txt b/games/minimal/mods/stairs/depends.txt new file mode 100644 index 0000000..4ad96d5 --- /dev/null +++ b/games/minimal/mods/stairs/depends.txt @@ -0,0 +1 @@ +default diff --git a/games/minimal/mods/stairs/init.lua b/games/minimal/mods/stairs/init.lua new file mode 100644 index 0000000..4929d13 --- /dev/null +++ b/games/minimal/mods/stairs/init.lua @@ -0,0 +1,93 @@ +stairs = {} + +-- Node will be called stairs:stair_ +function stairs.register_stair(subname, recipeitem, groups, images, description) + minetest.register_node("stairs:stair_" .. subname, { + description = description, + drawtype = "nodebox", + tile_images = images, + paramtype = "light", + paramtype2 = "facedir", + is_ground_content = true, + groups = groups, + node_box = { + type = "fixed", + fixed = { + {-0.5, -0.5, -0.5, 0.5, 0, 0.5}, + {-0.5, 0, 0, 0.5, 0.5, 0.5}, + }, + }, + }) + + minetest.register_craft({ + output = 'stairs:stair_' .. subname .. ' 4', + recipe = { + {recipeitem, "", ""}, + {recipeitem, recipeitem, ""}, + {recipeitem, recipeitem, recipeitem}, + }, + }) +end + +-- Node will be called stairs:slab_ +function stairs.register_slab(subname, recipeitem, groups, images, description) + minetest.register_node("stairs:slab_" .. subname, { + description = description, + drawtype = "nodebox", + tile_images = images, + paramtype = "light", + is_ground_content = true, + groups = groups, + node_box = { + type = "fixed", + fixed = {-0.5, -0.5, -0.5, 0.5, 0, 0.5}, + }, + selection_box = { + type = "fixed", + fixed = {-0.5, -0.5, -0.5, 0.5, 0, 0.5}, + }, + }) + + minetest.register_craft({ + output = 'stairs:slab_' .. subname .. ' 3', + recipe = { + {recipeitem, recipeitem, recipeitem}, + }, + }) +end + +-- Nodes will be called stairs:{stair,slab}_ +function stairs.register_stair_and_slab(subname, recipeitem, groups, images, desc_stair, desc_slab) + stairs.register_stair(subname, recipeitem, groups, images, desc_stair) + stairs.register_slab(subname, recipeitem, groups, images, desc_slab) +end + +stairs.register_stair_and_slab("wood", "default:wood", + {snappy=2,choppy=2,oddly_breakable_by_hand=2}, + {"default_wood.png"}, + "Wooden stair", + "Wooden slab") + +stairs.register_stair_and_slab("stone", "default:stone", + {cracky=3}, + {"default_stone.png"}, + "Stone stair", + "Stone slab") + +stairs.register_stair_and_slab("cobble", "default:cobble", + {cracky=3}, + {"default_cobble.png"}, + "Cobble stair", + "Cobble slab") + +stairs.register_stair_and_slab("brick", "default:brick", + {cracky=3}, + {"default_brick.png"}, + "Brick stair", + "Brick slab") + +stairs.register_stair_and_slab("sandstone", "default:sandstone", + {crumbly=2,cracky=2}, + {"default_sandstone.png"}, + "Sandstone stair", + "Sandstone slab") diff --git a/games/minimal/mods/test/init.lua b/games/minimal/mods/test/init.lua new file mode 100644 index 0000000..051b479 --- /dev/null +++ b/games/minimal/mods/test/init.lua @@ -0,0 +1,11 @@ +-- +-- Minimal Development Test +-- Mod: test +-- + +-- Try out PseudoRandom +pseudo = PseudoRandom(13) +assert(pseudo:next() == 22290) +assert(pseudo:next() == 13854) + + diff --git a/minetest.conf.example b/minetest.conf.example new file mode 100644 index 0000000..055ab87 --- /dev/null +++ b/minetest.conf.example @@ -0,0 +1,538 @@ +# This file is read by default from: +# ../minetest.conf +# ../../minetest.conf +# Any other path can be chosen by passing the path as a parameter +# to the program, eg. "minetest.exe --config ../minetest.conf.example". + +# By default, all the settings are commented and not functional. +# Uncomment settings by removing the preceding #. + +# Further documentation: +# http://wiki.minetest.net/ + +# NOTE: This file might not be up-to-date, refer to the +# defaultsettings.cpp file for an up-to-date list: +# https://github.com/minetest/minetest/blob/master/src/defaultsettings.cpp + +# A vim command to convert most of defaultsettings.cpp to conf file format: +# :'<,'>s/\tsettings->setDefault("\([^"]*\)", "\([^"]*\)");.*/#\1 = \2/g +# Note: Some of the settings are implemented in Lua. + +# +# Client and server +# + +# Name of player, on a server this is the main admin +#name = + +# +# Client stuff +# + +# Port to connect to (UDP) +#remote_port = +# Key mappings. +# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3 +#keymap_forward = KEY_KEY_W +#keymap_backward = KEY_KEY_S +#keymap_left = KEY_KEY_A +#keymap_right = KEY_KEY_D +#keymap_jump = KEY_SPACE +#keymap_sneak = KEY_LSHIFT +#keymap_inventory = KEY_KEY_I +# Go down ladder / go down in fly mode / go fast in fast mode +#keymap_special1 = KEY_KEY_E +#keymap_chat = KEY_KEY_T +#keymap_cmd = / +#keyman_console = KEY_F10 +#keymap_rangeselect = KEY_KEY_R +#keymap_freemove = KEY_KEY_K +#keymap_fastmove = KEY_KEY_J +#keymap_cinematic = KEY_F8 +#keymap_screenshot = KEY_F12 +# If true, keymap_special1 instead of keymap_sneak is used for climbing down and descending +#aux1_descends = false +# Double-tapping the jump key toggles fly mode +#doubletap_jump = false +# If false aux1 is used to fly fast +#always_fly_fast = true +# Some (temporary) keys for debugging +#keymap_print_debug_stacks = KEY_KEY_P +#keymap_quicktune_prev = KEY_HOME +#keymap_quicktune_next = KEY_END +#keymap_quicktune_dec = KEY_NEXT +#keymap_quicktune_inc = KEY_PRIOR + +# If set to true, you can place blocks at the position (feet + eye level) where you stand. +# This is helpful when working with nodeboxes. +#enable_build_where_you_stand = false +# Minimum FPS. +# The amount of rendered stuff is dynamically set according to this. +#wanted_fps = 30 +# If FPS would go higher than this, limit it by sleeping +# to not waste CPU power for no benefit. +#fps_max = 60 +# Maximum FPS when game is paused +#pause_fps_max = 20 +# The allowed adjustment range for the automatic rendering range adjustment +#viewing_range_nodes_max = 160 +#viewing_range_nodes_min = 35 +# Initial window size +#screenW = 800 +#screenH = 600 +#fullscreen = false +#fullscreen_bpp = 24 +# Experimental option, might cause visible spaces between blocks +# when set to higher number than 0. +#fsaa = 0 +#vsync = false +# Field of view in degrees +#fov = 72 +# Address to connect to (#blank = start local server) +#address = +# Enable random user input, for testing +#random_input = false +# Timeout for client to remove unused map data from memory +#client_unload_unused_data_timeout = 600 +# Whether to fog out the end of the visible area +#enable_fog = true +# Whether to show the client debug info (has the same effect as hitting F5) +#show_debug = false +# Enable a bit lower water surface; disable for speed (not quite optimized) +#new_style_water = false +# Max liquids processed per step +#liquid_loop_max = 100000 +# The time (in seconds) that the liquids queue may grow beyond processing +# capacity until an attempt is made to decrease its size by dumping old queue +# items. A value of 0 disables the functionality. +#liquid_queue_purge_time = 0 +# Liquid update interval in seconds +#liquid_update = 1.0 +# Enable transparent leaf textures, disable for speed +#new_style_leaves = true +# Connects glass if supported by node +#connected_glass = false +# Enable smooth lighting with simple ambient occlusion. +# Disable for speed or for different looks. +#smooth_lighting = true +# Adjust the gamma encoding for the light tables. Valid values are in the range +# 1.1 to 3.0 (inclusive); lower numbers are brighter. This setting is for the +# client only and is ignored by the server +#display_gamma = 1.8 +# Path to texture directory. All textures are first searched from here. +#texture_path = +# Video back-end. +# Possible values: null, software, burningsvideo, direct3d8, direct3d9, opengl. +#video_driver = opengl +# Unobstructed movement without physics, downwards key is keymap_special1 +#free_move = false +# Continuous forward movement (for testing) +#continuous_forward = false +# Enable cinematic mode +#cinematic = false +# Camera smoothing - smooths rotation of camera. 0 is no smoothing. +# Must be equal to or greater than 0, and less than 1. +#camera_smoothing = 0.0 +# Camera smoothing when in cinematic mode +#cinematic_camera_smoothing = 0.7 +# Fast movement (keymap_special1) +#fast_move = false +# Invert mouse +#invert_mouse = false +# Enable/disable clouds +#enable_clouds = true +#cloud_height = 120 +#enable_3d_clouds = true +# Use a cloud animation for the main menu background +#menu_clouds = true +# Path for screenshots +#screenshot_path = +# Amount of view bobbing (0 = no view bobbing, 1.0 = normal, 2.0 = double) +#view_bobbing_amount = 1.0 +# Amount of fall bobbing (0 = no fall bobbing, 1.0 = normal, 2.0 = double) +#fall_bobbing_amount = 0.0 +# 3d support. +# Currently: +# "none" = no 3d output. +# "anaglyph" = cyan/magenta color 3d. +# "interlaced" = odd/even line based polarisation screen support. +# "topbottom" = split screen top/bottom. +# "sidebyside" = split screen side by side. +#3d_mode = none +#3d_parallax_strength = 0.025 +# In-game chat console background color (R,G,B) +#console_color = (0,0,0) +# In-game chat console background alpha (opaqueness, between 0 and 255) +#console_alpha = 200 +# Selection box border color (R,G,B) +#selectionbox_color = (0,0,0) +# Crosshair color (R,G,B) +#crosshair_color = (255,255,255) +# Cross alpha (opaqueness, between 0 and 255) +#crosshair_alpha = 255 +# Scale gui by a user specified value +#gui_scaling = 1.0 +# Sensitivity multiplier +#mouse_sensitivity = 0.2 +# Sound settings +#enable_sound = true +#sound_volume = 0.7 +# Whether node texture animations should be desynchronized per mapblock +#desynchronize_mapblock_texture_animation = true +# Width of the selectionbox's lines (Between 1 and 5) +#selectionbox_width = 2 +# Maximum proportion of current window to be used for hotbar. +# Useful if there's something to be displayed right or left of hotbar. +#hud_hotbar_max_width = 1.0 +# Save the map received by the client on disk +#enable_local_map_saving = false +# Enable selection highlighting for nodes (disables selectionbox) +#enable_node_highlighting = true +# Texture filtering settings +#mip_map = false +#anisotropic_filter = false +#bilinear_filter = false +#trilinear_filter = false +# Set to true to pre-generate all item visuals +#preload_item_visuals = false +# Set to true to enable shaders. Disable them if video_driver = direct3d9/8. +#enable_shaders = true +# Set to true to enable textures bumpmapping. Requires shaders enabled. +#enable_bumpmapping = false +# Set to true enables parallax occlusion mapping. Requires shaders enabled. +#generate_normalmaps = false +# Set to true enables on the fly normalmap generation (Emboss effect). +# Requires bumpmapping enabled. +#normalmaps_strength = 0.6 +# Strength of generated normalmaps +#normalmaps_smooth = 1 +# Defines sampling step of texture (0 - 2). +# A higher value results in smoother normal maps. +#enable_parallax_occlusion = false +# Scale of parallax occlusion effect +#parallax_occlusion_scale = 0.08 +# Bias of parallax occlusion effect, usually scale/2 +#parallax_occlusion_bias = 0.04 +# Set to true enables waving water. Requires shaders enabled. +#enable_waving_water = false +# Parameters for waving water: +#water_wave_height = 1.0 +#water_wave_length = 20.0 +#water_wave_speed = 5.0 +# Set to true enables waving leaves. Requires shaders enabled. +#enable_waving_leaves = false +# Set to true enables waving plants. Requires shaders enabled. +#enable_waving_plants = false +# Enables caching of facedir rotated meshes +#ambient_occlusion_gamma = 2.2 +# The strength (darkness) of node ambient-occlusion shading. +# Lower is darker, Higher is lighter. The valid range of values for this +# setting is 0.25 to 4.0 inclusive. If the value is out of range it will be +# set to the nearest valid value. +#enable_mesh_cache = true +# The time in seconds it takes between repeated +# right clicks when holding the right mouse button. +#repeat_rightclick_time = 0.25 +# Make fog and sky colors depend on daytime (dawn/sunset) and view direction +#directional_colored_fog = true +# Delay showing tooltips, stated in milliseconds +#tooltip_show_delay = 400 +# Adjust dpi configuration to your screen (non X11/Android only) e.g. for 4k screens +#screen_dpi = 72 +# Default timeout for cURL, stated in milliseconds. +# Only has an effect if compiled with cURL. +#curl_timeout = 5000 +# Limits number of parallel HTTP requests. Affects: +# Media fetch if server uses remote_media setting. +# Serverlist download and server announcement. +# Downloads performed by main menu (e.g. mod manager). +# Only has an effect if compiled with cURL. +#curl_parallel_limit = 8 +# Maximum time in ms a file download (e.g. a mod download) may take +#curl_file_download_timeout = 300000 +# Enable usage of remote media server (if provided by server) +#enable_remote_media_server = true +# Url to the server list displayed in the Multiplayer Tab +#serverlist_url = servers.minetest.net +# File in client/serverlist/ that contains your favorite servers displayed in the Multiplayer Tab +#serverlist_file = favoriteservers.txt +# Whether freetype fonts are used, requires freetype support to be compiled in +#freetype = true +# Path to TrueTypeFont or bitmap +#font_path = fonts/liberationsans.ttf +#font_size = 15 +# Font shadow offset, if 0 then shadow will not be drawn +#font_shadow = 1 +# Font shadow alpha (opaqueness, between 0 and 255) +#font_shadow_alpha = 128 +#mono_font_path = fonts/liberationmono.ttf +#mono_font_size = 15 +# This font will be used for certain languages +#fallback_font_path = fonts/DroidSansFallbackFull.ttf +#fallback_font_size = 15 +#fallback_font_shadow = 1 +#fallback_font_shadow_alpha = 128 +# Override language. When no value is provided (default) system language is used. +# Check "locale" directory for the list of available translations. +#language = +#main_menu_script = +#main_menu_game_mgr = 0 +#main_menu_mod_mgr = 1 +#modstore_download_url = https://forum.minetest.net/media/ +#modstore_listmods_url = https://forum.minetest.net/mmdb/mods/ +#modstore_details_url = https://forum.minetest.net/mmdb/mod/*/ +# Makes DirectX work with LuaJIT. Disable if it causes troubles. +#high_precision_fpu = true + +# +# Server stuff +# + +# Network port to listen (UDP) +#port = 30000 +# Bind address +#bind_address = +# Name of server +#server_name = Minetest server +# Description of server +#server_description = mine here +# Domain name of server +#server_address = game.minetest.net +# Homepage of server +#server_url = http://minetest.net +# Automaticaly report to masterserver +#server_announce = 0 +# Announce to this masterserver. +# If you want to announce your ipv6 address - use serverlist_url = v6.servers.minetest.net. +#serverlist_url = servers.minetest.net +# Default game (default when creating a new world) +#default_game = minetest +# World directory (everything in the world is stored here) +#map-dir = /custom/world +# Message of the Day +#motd = Welcome to this awesome Minetest server! +# Maximum number of players connected simultaneously +#max_users = 15 +# Set to true to disallow old clients from connecting +#strict_protocol_version_checking = false +# Time in seconds for item entity to live. Default value: 900s. +# Setting it to -1 disables the feature. +#item_entity_ttl = 900 +# Set to true to enable creative mode (unlimited inventory) +#creative_mode = false +# Enable players getting damage and dying +#enable_damage = false +# A chosen map seed for a new map, leave empty for random +#fixed_map_seed = +# Gives some stuff to players at the beginning +#give_initial_stuff = false +# New users need to input this password +#default_password = +# Available privileges: interact, shout, teleport, settime, privs, ... +# See /privs in game for a full list on your server and mod configuration. +#default_privs = interact, shout +# Whether players are shown to clients without any range limit. +# Deprecated, use the setting player_transfer_distance instead. +#unlimited_player_transfer_distance = true +# Defines the maximal player transfer distance in blocks (0 = unlimited) +#player_transfer_distance = 0 +# Whether to enable players killing each other +#enable_pvp = true +# If this is set, players will always (re)spawn at the given position +#static_spawnpoint = 0, 10, 0 +# If true, new players cannot join with an empty password +#disallow_empty_password = false +# If true, disable cheat prevention in multiplayer +#disable_anticheat = false +# If true, actions are recorded for rollback +# This option is only read when server starts +#enable_rollback_recording = false +# Handling for deprecated lua api calls: +# "legacy" = (try to) mimic old behaviour (default for release). +# "log" = mimic and log backtrace of deprecated call (default for debug). +# "error" = abort on usage of deprecated call (suggested for mod developers). +#deprecated_lua_api_handling = legacy +# Mod profiler +#mod_profiling = false +# Detailed mod profile data +#detailed_profiling = false +# Profiler data print interval. #0 = disable. +#profiler_print_interval = 0 +#enable_mapgen_debug_info = false +# From how far client knows about objects +#active_object_send_range_blocks = 3 +# How large area of blocks are subject to the active block stuff. +# Active = objects are loaded and ABMs run. +#active_block_range = 2 +# How many blocks are flying in the wire simultaneously per client +#max_simultaneous_block_sends_per_client = 10 +# How many blocks are flying in the wire simultaneously per server +#max_simultaneous_block_sends_server_total = 40 +# From how far blocks are sent to clients, stated in mapblocks (16 nodes) +#max_block_send_distance = 10 +# From how far blocks are generated for clients, stated in mapblocks (16 nodes) +#max_block_generate_distance = 6 +# Number of extra blocks that can be loaded by /clearobjects at once. +# This is a trade-off between sqlite transaction overhead and +# memory consumption (4096=100MB, as a rule of thumb). +#max_clearobjects_extra_loaded_blocks = 4096 +# Maximum number of forceloaded blocks +#max_forceloaded_blocks = 16 +# Interval of sending time of day to clients +#time_send_interval = 5 +# Controls length of day/night cycle. +# 72=20min, 360=4min, 1=24hour, 0=day/night/whatever stays unchanged. +#time_speed = 72 +# Length of year in days for seasons change. +# With default time_speed 365 days = 5 real days for year, 30 days = 10 real hours. +#year_days = 30 +#server_unload_unused_data_timeout = 29 +# Maximum number of statically stored objects in a block +#max_objects_per_block = 49 +# Interval of saving important changes in the world, stated in seconds +#server_map_save_interval = 5.3 +# http://www.sqlite.org/pragma.html#pragma_synchronous only numeric values: 0 1 2 +#sqlite_synchronous = 2 +# To reduce lag, block transfers are slowed down when a player is building something. +# This determines how long they are slowed down after placing or removing a node. +#full_block_send_enable_min_time_from_building = 2.0 +# Length of a server tick and the interval at which objects are generally updated over network +#dedicated_server_step = 0.1 +# Can be set to true to disable shutting down on invalid world data +#ignore_world_load_errors = false +# Specifies URL from which client fetches media instead of using UDP. +# $filename should be accessible from $remote_media$filename via cURL +# (obviously, remote_media should end with a slash). +# Files that are not present would be fetched the usual way. +#remote_media = +# Level of logging to be written to debug.txt: +# 0 = none, 1 = errors and debug, 2 = action, 3 = info, 4 = verbose. +#debug_log_level = 2 +# Maximum number of blocks that can be queued for loading +#emergequeue_limit_total = 256 +# Maximum number of blocks to be queued that are to be loaded from file. +# Set to blank for an appropriate amount to be chosen automatically. +#emergequeue_limit_diskonly = 32 +# Maximum number of blocks to be queued that are to be generated. +# Set to blank for an appropriate amount to be chosen automatically. +#emergequeue_limit_generate = 32 +# Number of emerge threads to use. Make this field blank, or increase this number +# to use multiple threads. On multiprocessor systems, this will improve mapgen speed greatly +# at the cost of slightly buggy caves. +#num_emerge_threads = 1 +# Maximum number of packets sent per send step, if you have a slow connection +# try reducing it, but don't reduce it to a number below double of targeted +# client number. +#max_packets_per_iteration = 1024 +# Enable/disable IPv6 +#enable_ipv6 = true +# Enable/disable running an IPv6 server. An IPv6 server may be restricted +# to IPv6 clients, depending on system configuration. +# Ignored if bind_address is set. +#ipv6_server = false + +# +# Physics stuff +# + +#movement_acceleration_default = 3 +#movement_acceleration_air = 2 +#movement_acceleration_fast = 10 +#movement_speed_walk = 4 +#movement_speed_crouch = 1.35 +#movement_speed_fast = 20 +#movement_speed_climb = 2 +#movement_speed_jump = 6.5 +#movement_speed_descend = 6 +#movement_liquid_fluidity = 1 +#movement_liquid_fluidity_smooth = 0.5 +#movement_liquid_sink = 10 +#movement_gravity = 9.81 + +# +# Mapgen stuff +# + +# Name of map generator to be used. Currently supported: v5, v6, v7, singlenode. +#mg_name = v6 +# Water surface level of map +#water_level = 1 +# Size of chunks to be generated, stated in mapblocks (16 nodes) +#chunksize = 5 +# Global map generation attributes. Currently supported: trees, caves, flat, dungeons, light. +# Flags that are not specified in the flag string are not modified from the default. +# To explicitly turn off a flag, prepend "no" to the beginning, e.g. nolight. +#mg_flags = trees, caves +# Map generation attributes specific to Mapgen V6. +# Currently supported: biomeblend, jungles, mudflow. +#mgv6_spflags = biomeblend, jungles, mudflow +# Controls size of deserts and beaches in Mapgen V6 +#mgv6_freq_desert = 0.45 +#mgv6_freq_beach = 0.15 +# Enable/disable floating dungeons and dungeon slices +#enable_floating_dungeons = true + +# Perlin noise attributes for different map generation parameters. +# Noise parameters can be specified as a set of positional values: +# Offset, scale, (spread factors), seed offset, number of octaves, persistence, lacunarity. +#mgv6_np_terrain_base = -4, 20, (250, 250, 250), 82341, 5, 0.6, 2.0 +# Or the new group format can be used instead: +#mgv6_np_terrain_base = { +# offset = -4 +# scale = 20 +# spread = (250, 250, 250) +# seed = 82341 +# octaves = 5 +# persistence = 0.6 +# lacunarity = 2.0 +# flags = "defaults" +#} +# Only the group format supports noise flags which are needed for eased noise. +# Mgv5 uses eased noise for np_ground so this is shown in group format, +# other noise parameters are shown in positional format to save space. + +#mgv5_np_filler_depth = 0, 1, (150, 150, 150), 261, 4, 0.7, 2.0 +#mgv5_np_factor = 0, 1, (250, 250, 250), 920381, 3, 0.45, 2.0 +#mgv5_np_height = 0, 10, (250, 250, 250), 84174, 4, 0.5, 2.0 +#mgv5_np_cave1 = 0, 12, (50, 50, 50), 52534, 4, 0.5, 2.0 +#mgv5_np_cave2 = 0, 12, (50, 50, 50), 10325, 4, 0.5, 2.0 +#mgv5_np_ground = { +# offset = 0 +# scale = 40 +# spread = (80, 80, 80) +# seed = 983240 +# octaves = 4 +# persistence = 0.55 +# lacunarity = 2.0 +# flags = "eased" +#} + +#mgv6_spflags = biomeblend, jungles, mudflow +#mgv6_np_terrain_base = -4, 20, (250, 250, 250), 82341, 5, 0.6, 2.0 +#mgv6_np_terrain_higher = 20, 16, (500, 500, 500), 85039, 5, 0.6, 2.0 +#mgv6_np_steepness = 0.85, 0.5, (125, 125, 125), -932, 5, 0.7, 2.0 +#mgv6_np_height_select = 0.5, 1, (250, 250, 250), 4213, 5, 0.69, 2.0 +#mgv6_np_mud = 4, 2, (200, 200, 200), 91013, 3, 0.55, 2.0 +#mgv6_np_beach = 0, 1, (250, 250, 250), 59420, 3, 0.50, 2.0 +#mgv6_np_biome = 0, 1, (250, 250, 250), 9130, 3, 0.50, 2.0 +#mgv6_np_cave = 6, 6, (250, 250, 250), 34329, 3, 0.50, 2.0 +#mgv6_np_humidity = 0.5, 0.5, (500, 500, 500), 72384, 4, 0.66, 2.0 +#mgv6_np_trees = 0, 1, (125, 125, 125), 2, 4, 0.66, 2.0 +#mgv6_np_apple_trees = 0, 1, (100, 100, 100), 342902, 3, 0.45, 2.0 + +#mgv7_spflags = mountains, ridges +#mgv7_np_terrain_base = 4, 70, (300, 300, 300), 82341, 6, 0.7, 2.0 +#mgv7_np_terrain_alt = 4, 25, (600, 600, 600), 5934, 5, 0.6, 2.0 +#mgv7_np_terrain_persist = 0.6, 0.1, (500, 500, 500), 539, 3, 0.6, 2.0 +#mgv7_np_height_select = -0.5, 1, (250, 250, 250), 4213, 5, 0.69, 2.0 +#mgv7_np_filler_depth = 0, 1.2, (150, 150, 150), 261, 4, 0.7, 2.0 +#mgv7_np_mount_height = 100, 30, (500, 500, 500), 72449, 4, 0.6, 2.0 +#mgv7_np_ridge_uwater = 0, 1, (500, 500, 500), 85039, 4, 0.6, 2.0 +#mgv7_np_mountain = -0.6, 1, (250, 350, 250), 5333, 5, 0.68, 2.0 +#mgv7_np_ridge = 0, 1, (100, 100, 100), 6467, 4, 0.75, 2.0 +#mgv7_np_cave1 = 0, 12, (100, 100, 100), 52534, 4, 0.5, 2.0 +#mgv7_np_cave2 = 0, 12, (100, 100, 100), 10325, 4, 0.5, 2.0 + +# Noise parameters for biome API temperature and humidity +#mg_biome_np_heat = 50, 50, (500, 500, 500), 5349, 3, 0.5, 2.0 +#mg_biome_np_humidity = 50, 50, (500, 500, 500), 842, 3, 0.5, 2.0 diff --git a/misc/Info.plist b/misc/Info.plist new file mode 100644 index 0000000..848ccfa --- /dev/null +++ b/misc/Info.plist @@ -0,0 +1,14 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ../Resources/minetest + CFBundleIconFile + minetest.icns + CFBundleIdentifier + net.minetest.minetest + + diff --git a/misc/minetest-icon-24x24.png b/misc/minetest-icon-24x24.png new file mode 100644 index 0000000..334e2f6 Binary files /dev/null and b/misc/minetest-icon-24x24.png differ diff --git a/misc/minetest-icon.icns b/misc/minetest-icon.icns new file mode 100644 index 0000000..14731c2 Binary files /dev/null and b/misc/minetest-icon.icns differ diff --git a/misc/minetest-icon.ico b/misc/minetest-icon.ico new file mode 100644 index 0000000..82af67b Binary files /dev/null and b/misc/minetest-icon.ico differ diff --git a/misc/minetest-icon.svg b/misc/minetest-icon.svg new file mode 100644 index 0000000..46c9ac7 --- /dev/null +++ b/misc/minetest-icon.svg @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/minetest.appdata.xml b/misc/minetest.appdata.xml new file mode 100644 index 0000000..6ef8b7f --- /dev/null +++ b/misc/minetest.appdata.xml @@ -0,0 +1,36 @@ + + + minetest.desktop + CC0-1.0 + LGPL-2.1+ and CC-BY-SA-3.0 and MIT and Apache-2.0 + Minetest + Multiplayer infinite-world block sandbox game + +

+ Minetest is an infinite-world block sandbox game and game engine. +

+ Players can create and destroy various types of blocks in a + three-dimensional open world. This allows forming structures in + every possible creation, on multiplayer servers or in singleplayer. +

+ Minetest is designed to be simple, stable, and portable. + It is lightweight enough to run on fairly old hardware. +

+ Minetest has many features, including: +

+
    +
  • Ability to walk around, dig, and build in a near-infinite voxel world
  • +
  • Crafting of items from raw materials
  • +
  • Fast and able to run on old and slow hardware
  • +
  • A simple modding API that supports many additions and modifications to the game
  • +
  • Multiplayer support via servers hosted by users
  • +
  • Beautiful lightning-fast map generator
  • +
+
+ + http://minetest.net/_media/screen2.png + http://minetest.net/_media/screenshot_4032289578.png + + http://minetest.net + sfan5@live.de +
diff --git a/misc/minetest.desktop b/misc/minetest.desktop new file mode 100644 index 0000000..250a4fa --- /dev/null +++ b/misc/minetest.desktop @@ -0,0 +1,15 @@ +[Desktop Entry] +Name=Minetest +GenericName=Minetest +Comment=Multiplayer infinite-world block sandbox +Comment[fr]=Jeu multijoueurs de type bac à sable avec des mondes infinis +Comment[de]=Mehrspieler-Sandkastenspiel mit unendlichen Blockwelten +Comment[tr]=Tek-Çok oyuncuyla küplerden sonsuz dünyalar inşa et +Exec=minetest +Icon=minetest-icon +Terminal=false +Type=Application +Categories=Game; +StartupNotify=false +Keywords=sandbox;world;mining;crafting;blocks;nodes;multiplayer;roleplaying; + diff --git a/misc/winresource.rc b/misc/winresource.rc new file mode 100644 index 0000000..ecb314c --- /dev/null +++ b/misc/winresource.rc @@ -0,0 +1,57 @@ +#include +#include +#include +#define USE_CMAKE_CONFIG_H +#include "config.h" +#undef USE_CMAKE_CONFIG_H + +#if RUN_IN_PLACE == 1 + #define BUILDMODE "RUN_IN_PLACE=1\0" +#else + #define BUILDMODE "RUN_IN_PLACE=0\0" +#endif + +LANGUAGE 0, SUBLANG_NEUTRAL +130 ICON "minetest-icon.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +1 VERSIONINFO + FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH_ORIG,0 + PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH_ORIG,0 + FILEFLAGSMASK 0x3fL +#ifndef NDEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "\0" + VALUE "CompanyName", "Minetest Community\0" + VALUE "FileDescription", "Minetest engine core main application\0" + VALUE "FileVersion", VERSION_STRING + VALUE "InternalName", "Minetest engine\0" + VALUE "LegalCopyright", "(c) 2014 celeron55\0" + VALUE "LegalTrademarks", """Minetest"" is property of Minetest community, don't use the name for your application without permission!\0" + VALUE "OriginalFilename", "minetest.exe\0" + VALUE "PrivateBuild", VERSION_EXTRA_STRING + VALUE "ProductName", "Minetest\0" + VALUE "ProductVersion", PRODUCT_VERSION_STRING + VALUE "SpecialBuild", BUILDMODE + END +END +BLOCK "VarFileInfo" +BEGIN + VALUE "Translation", 0x409, 1200 +END +END diff --git a/po/be/minetest.po b/po/be/minetest.po new file mode 100644 index 0000000..0e60035 --- /dev/null +++ b/po/be/minetest.po @@ -0,0 +1,974 @@ +# Belarusian translation for Minetest. +# Copyright (C) 2010 celeron55, Pertu Ahola +# This file is distributed under the same license as the Minetest package. +# Selat , 2014. +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2013-11-23 17:37+0100\n" +"Last-Translator: Selat \n" +"Language-Team: Belarusian\n" +"Language: be\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:48 +msgid "Depends:" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:68 +msgid "Enable MP" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:70 +msgid "Disable MP" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:82 +msgid "Enable all" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:343 +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +msgid "Downloading" +msgstr "" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +msgid "Shortname:" +msgstr "" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:140 +msgid "Fancy Trees" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:142 +msgid "Opaque Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:144 +msgid "Connected Glass" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:167 +msgid "Reset singleplayer world" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +msgid "Bumpmapping" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +msgid "Start Singleplayer" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:72 +msgid "Config mods" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:191 +msgid "Main" +msgstr "" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +msgid "Texturepacks" +msgstr "" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "" + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "" + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "" + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "" + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "" + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "" + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "" diff --git a/po/cs/minetest.po b/po/cs/minetest.po new file mode 100644 index 0000000..563275b --- /dev/null +++ b/po/cs/minetest.po @@ -0,0 +1,1218 @@ +# Czech translations for minetest. +# Copyright (C) 2011 celeron +# This file is distributed under the same license as the minetest. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-02-12 13:13+0100\n" +"PO-Revision-Date: 2015-02-12 16:16+0100\n" +"Last-Translator: Jakub Vanek \n" +"Language-Team: Czech <>\n" +"Language: cs\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" + +#: builtin/fstk/ui.lua:67 builtin/mainmenu/store.lua:165 +msgid "Ok" +msgstr "OK" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "Svět:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "Skrýt vnitřní" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "Skrýt obsahy balíčků" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "Mod:" + +#: builtin/mainmenu/dlg_config_world.lua:48 builtin/mainmenu/tab_mods.lua:99 +msgid "Depends:" +msgstr "Závislosti:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Uložit" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Zrušit" + +#: builtin/mainmenu/dlg_config_world.lua:68 +msgid "Enable MP" +msgstr "Povolit balíček" + +#: builtin/mainmenu/dlg_config_world.lua:70 +msgid "Disable MP" +msgstr "Zakázat balíček" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "povoleno" + +#: builtin/mainmenu/dlg_config_world.lua:82 +msgid "Enable all" +msgstr "Povolit vše" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Název světa" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "Seedové číslo" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "Generátor mapy" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Hra" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Vytvořit" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "Nemáte nainstalované žádné podhry." + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "Stáhněte si jednu z minetest.net" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "Varování: \"Minimal development test\" je zamýšlen pouze pro vývojáře." + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "Stáhněte si z minetest.net podhru, například minetest_game." + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "Svět s názvem \"$1\" už existuje" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "Nebyla vybrána podhra nebo název" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "Skutečně chcete odstranit \"$1\"?" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:79 +msgid "Yes" +msgstr "Ano" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "Jistě že ne!" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "Modmgr: Nepodařilo se odstranit \"$1\"" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "Modmgr: Neplatná cesta k modu \"$1\"" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "Doopravdy chcete smazat svět \"$1\"?" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Ne" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "Přejmenovat balíček modů:" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "Přijmout" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "Instalace modu: ze souboru: \"$1\"" + +#: builtin/mainmenu/modmgr.lua:343 +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" +"\n" +"Instalace modu: špatný archiv nebo nepodporovaný typ souboru \"$1\"" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "Selhala instalace $1 do $2" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "Instalace modu: nenalezen vhodný adresář s příslušným názvem pro balíček $1" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "Instalace modu: nenašel jsem skutečné jméno modu: $1" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "Neřazené" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:580 +msgid "Search" +msgstr "Hledání" + +#: builtin/mainmenu/store.lua:126 +msgid "Downloading $1, please wait..." +msgstr "Stahuji $1, prosím čekejte..." + +#: builtin/mainmenu/store.lua:160 +msgid "Successfully installed:" +msgstr "Úspěšně nainstalováno:" + +#: builtin/mainmenu/store.lua:162 +msgid "Shortname:" +msgstr "Zkratka:" + +#: builtin/mainmenu/store.lua:472 +msgid "Rating" +msgstr "Hodnocení" + +#: builtin/mainmenu/store.lua:497 +msgid "re-Install" +msgstr "Přeinstalovat" + +#: builtin/mainmenu/store.lua:499 +msgid "Install" +msgstr "Instalovat" + +#: builtin/mainmenu/store.lua:518 +msgid "Close store" +msgstr "Zavřít obchod" + +#: builtin/mainmenu/store.lua:526 +msgid "Page $1 of $2" +msgstr "Strana $1 z $2" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Autoři" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "Vývojáři jádra" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "Aktivní přispěvatelé" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "Bývalí přispěvatelé" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "Nainstalované mody:" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "Online repozitář modů" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "Popis modu není dostupný" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "Informace o modu:" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "Přejmenovat" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "Odinstalovat označený balíček" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "Odinstalovat vybraný mod" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "Vybrat soubor s modem:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "Mody" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address / Port :" +msgstr "Adresa / Port :" + +#: builtin/mainmenu/tab_multiplayer.lua:24 +msgid "Name / Password :" +msgstr "Jméno / Heslo :" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "Seznam veřejných serverů" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Smazat" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Připojit" + +#: builtin/mainmenu/tab_multiplayer.lua:62 +#: builtin/mainmenu/tab_simple_main.lua:45 +msgid "Creative mode" +msgstr "Kreativní mód" + +#: builtin/mainmenu/tab_multiplayer.lua:63 +#: builtin/mainmenu/tab_simple_main.lua:46 +msgid "Damage enabled" +msgstr "Poškození povoleno" + +#: builtin/mainmenu/tab_multiplayer.lua:64 +#: builtin/mainmenu/tab_simple_main.lua:47 +msgid "PvP enabled" +msgstr "PvP povoleno" + +#: builtin/mainmenu/tab_multiplayer.lua:247 +msgid "Client" +msgstr "Klient" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Nový" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Nastavit" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "Spustit hru" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Vyber svět:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:75 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Kreativní mód" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:77 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Povolit poškození" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Veřejný" + +#: builtin/mainmenu/tab_server.lua:37 builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Jméno/Heslo" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "Svázat adresu" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "Port" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "Port serveru" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "Server" + +#: builtin/mainmenu/tab_settings.lua:21 +msgid "No Filter" +msgstr "Žádný filtr" + +#: builtin/mainmenu/tab_settings.lua:22 +msgid "Bilinear Filter" +msgstr "Bilineární filtr" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Trilinear Filter" +msgstr "Trilineární filtr" + +#: builtin/mainmenu/tab_settings.lua:32 +msgid "No Mipmap" +msgstr "Žádné Mipmapy" + +#: builtin/mainmenu/tab_settings.lua:33 +msgid "Mipmap" +msgstr "Mipmapa" + +#: builtin/mainmenu/tab_settings.lua:34 +msgid "Mipmap + Aniso. Filter" +msgstr "Mipmapa + Anizo. filtr" + +#: builtin/mainmenu/tab_settings.lua:77 +msgid "Are you sure to reset your singleplayer world?" +msgstr "Jste si jisti, že chcete resetovat místní svět?" + +#: builtin/mainmenu/tab_settings.lua:81 +msgid "No!!!" +msgstr "Ne!!!" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Smooth Lighting" +msgstr "Plynulé osvětlení" + +#: builtin/mainmenu/tab_settings.lua:183 +msgid "Enable Particles" +msgstr "Povolit částice" + +#: builtin/mainmenu/tab_settings.lua:185 +msgid "3D Clouds" +msgstr "3D mraky" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Fancy Trees" +msgstr "Ozdobné stromy" + +#: builtin/mainmenu/tab_settings.lua:189 +msgid "Opaque Water" +msgstr "Neprůhledná voda" + +#: builtin/mainmenu/tab_settings.lua:191 +msgid "Connected Glass" +msgstr "Propojené sklo" + +#: builtin/mainmenu/tab_settings.lua:193 +msgid "Node Highlighting" +msgstr "Zvýraznění bloků" + +#: builtin/mainmenu/tab_settings.lua:196 +msgid "Texturing:" +msgstr "Texturování:" + +#: builtin/mainmenu/tab_settings.lua:201 +msgid "Rendering:" +msgstr "Renderování:" + +#: builtin/mainmenu/tab_settings.lua:205 +msgid "Restart minetest for driver change to take effect" +msgstr "Aby se změna ovladače projevila, restartujte Minetest." + +#: builtin/mainmenu/tab_settings.lua:207 +msgid "Shaders" +msgstr "Shadery" + +#: builtin/mainmenu/tab_settings.lua:212 +msgid "Change keys" +msgstr "Změnit nastavení kláves" + +#: builtin/mainmenu/tab_settings.lua:215 +msgid "Reset singleplayer world" +msgstr "Reset místního světa" + +#: builtin/mainmenu/tab_settings.lua:219 +msgid "GUI scale factor" +msgstr "Měřítko GUI" + +#: builtin/mainmenu/tab_settings.lua:223 +msgid "Scaling factor applied to menu elements: " +msgstr "Měřítko aplikované na prvky menu: " + +#: builtin/mainmenu/tab_settings.lua:229 +msgid "Touch free target" +msgstr "Středový kurzor" + +#: builtin/mainmenu/tab_settings.lua:235 +msgid "Touchthreshold (px)" +msgstr "Dosah dotyku (px)" + +#: builtin/mainmenu/tab_settings.lua:242 builtin/mainmenu/tab_settings.lua:256 +msgid "Bumpmapping" +msgstr "Bump mapování" + +#: builtin/mainmenu/tab_settings.lua:244 builtin/mainmenu/tab_settings.lua:257 +msgid "Generate Normalmaps" +msgstr "Generovat normálové mapy" + +#: builtin/mainmenu/tab_settings.lua:246 builtin/mainmenu/tab_settings.lua:258 +msgid "Parallax Occlusion" +msgstr "Parallax Occlusion" + +#: builtin/mainmenu/tab_settings.lua:248 builtin/mainmenu/tab_settings.lua:259 +msgid "Waving Water" +msgstr "Vlnění vody" + +#: builtin/mainmenu/tab_settings.lua:250 builtin/mainmenu/tab_settings.lua:260 +msgid "Waving Leaves" +msgstr "Vlnění listů" + +#: builtin/mainmenu/tab_settings.lua:252 builtin/mainmenu/tab_settings.lua:261 +msgid "Waving Plants" +msgstr "Vlnění rostlin" + +#: builtin/mainmenu/tab_settings.lua:287 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "Pro povolení shaderů musíte používat OpenGL ovladač." + +#: builtin/mainmenu/tab_settings.lua:398 +msgid "Settings" +msgstr "Nastavení" + +#: builtin/mainmenu/tab_simple_main.lua:79 +msgid "Fly mode" +msgstr "Létací režim" + +#: builtin/mainmenu/tab_simple_main.lua:83 +msgid "Start Singleplayer" +msgstr "Start místní hry" + +#: builtin/mainmenu/tab_simple_main.lua:84 +msgid "Config mods" +msgstr "Nastavení modů" + +#: builtin/mainmenu/tab_simple_main.lua:203 +msgid "Main" +msgstr "Hlavní nabídka" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Hrát" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Místní hra" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "Vyberte balíček textur:" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "Informace nejsou dostupné" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +msgid "Texturepacks" +msgstr "Balíčky textur" + +#: src/client.cpp:2788 +msgid "Loading textures..." +msgstr "Načítám textury..." + +#: src/client.cpp:2798 +msgid "Rebuilding shaders..." +msgstr "Sestavuji shadery..." + +#: src/client.cpp:2805 +msgid "Initializing nodes..." +msgstr "Inicializuji bloky..." + +#: src/client.cpp:2820 +msgid "Item textures..." +msgstr "Textury věcí..." + +#: src/client.cpp:2845 +msgid "Done!" +msgstr "Hotovo!" + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "no" + +#: src/game.cpp:1057 src/guiFormSpecMenu.cpp:2006 +msgid "Proceed" +msgstr "Pokračovat" + +#: src/game.cpp:1077 +msgid "You died." +msgstr "Zemřel jsi." + +#: src/game.cpp:1078 +msgid "Respawn" +msgstr "Znovu stvořit" + +#: src/game.cpp:1097 +msgid "" +"Default Controls:\n" +"No menu visible:\n" +"- single tap: button activate\n" +"- double tap: place/use\n" +"- slide finger: look around\n" +"Menu/Inventory visible:\n" +"- double tap (outside):\n" +" -->close\n" +"- touch stack, touch slot:\n" +" --> move stack\n" +"- touch&drag, tap 2nd finger\n" +" --> place single item to slot\n" +msgstr "" +"Výchozí ovládání:\n" +"Bez menu:\n" +"- klik: aktivace tlačítka\n" +"- dvojklik: položit/použít\n" +"- pohyb prstem: rozhlížení\n" +"Menu/Inventář zobrazen:\n" +"- dvojklik (mimo):\n" +" -->zavřít\n" +"- stisk hromádky, přihrádky :\n" +" --> přesunutí hromádky\n" +"- stisk a přesun, klik druhým prstem\n" +" --> umístit samostatnou věc do přihrádky\n" + +#: src/game.cpp:1111 +msgid "" +"Default Controls:\n" +"- WASD: move\n" +"- Space: jump/climb\n" +"- Shift: sneak/go down\n" +"- Q: drop item\n" +"- I: inventory\n" +"- Mouse: turn/look\n" +"- Mouse left: dig/punch\n" +"- Mouse right: place/use\n" +"- Mouse wheel: select item\n" +"- T: chat\n" +msgstr "" +"Výchozí ovládání:\n" +"- WASD: pohyb\n" +"- Mezera: skákání/šplhání\n" +"- Shift: plížení\n" +"- Q: zahodit věc\n" +"- I: inventář\n" +"- Myš: otáčení,rozhlížení\n" +"- Myš(levé tl.): kopat, štípat\n" +"- Myš(pravé tl.): položit, použít\n" +"- Myš(kolečko): vybrat přihrádku\n" +"- T: chat\n" + +#: src/game.cpp:1130 +msgid "Continue" +msgstr "Pokračovat" + +#: src/game.cpp:1134 +msgid "Change Password" +msgstr "Změnit heslo" + +#: src/game.cpp:1139 +msgid "Sound Volume" +msgstr "Hlasitost" + +#: src/game.cpp:1141 +msgid "Change Keys" +msgstr "Změnit klávesy" + +#: src/game.cpp:1144 +msgid "Exit to Menu" +msgstr "Odejít do nabídky" + +#: src/game.cpp:1146 +msgid "Exit to OS" +msgstr "Ukončit hru" + +#: src/game.cpp:1809 +msgid "Shutting down..." +msgstr "Vypínání..." + +#: src/game.cpp:1858 +msgid "Loading..." +msgstr "Nahrávám..." + +#: src/game.cpp:1915 +msgid "Creating server..." +msgstr "Vytvářím server..." + +#: src/game.cpp:1952 +msgid "Creating client..." +msgstr "Vytvářím klienta..." + +#: src/game.cpp:2125 +msgid "Resolving address..." +msgstr "Překládám adresu..." + +#: src/game.cpp:2216 +msgid "Connecting to server..." +msgstr "Připojuji se k serveru..." + +#: src/game.cpp:2274 +msgid "Item definitions..." +msgstr "Definice věcí..." + +#: src/game.cpp:2279 +msgid "Node definitions..." +msgstr "Definice bloků..." + +#: src/game.cpp:2286 +msgid "Media..." +msgstr "Média..." + +#: src/game.cpp:2291 +msgid " KB/s" +msgstr " KB/s" + +#: src/game.cpp:2295 +msgid " MB/s" +msgstr " MB/s" + +#: src/game.cpp:4210 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Pro detaily se podívejte do debug.txt." + +#: src/guiFormSpecMenu.cpp:2797 +msgid "Enter " +msgstr "Zadejte " + +#: src/guiFormSpecMenu.cpp:2817 +msgid "ok" +msgstr "OK" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "Nastavení kláves (Pokud tohle menu nebude v pořádku, odstraňte nastavení z " +"minetest.conf)" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "\"Použít\" = slézt dolů" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "Dvojstisk klávesy \"skok\" zapne létání" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "Klávesa je již používána" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "stiskni klávesu" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Vpřed" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Vzad" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Doleva" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Doprava" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Použít" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Skok" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Plížit se" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Zahodit" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Inventář" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Chat" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Příkaz" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Konzole" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Létání" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Turbo" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "Duch" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "Změna dohledu" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "Vypsat hromádky" + +#: src/guiPasswordChange.cpp:108 +msgid "Old Password" +msgstr "Staré heslo" + +#: src/guiPasswordChange.cpp:124 +msgid "New Password" +msgstr "Nové heslo" + +#: src/guiPasswordChange.cpp:139 +msgid "Confirm Password" +msgstr "Potvrdit heslo" + +#: src/guiPasswordChange.cpp:155 +msgid "Change" +msgstr "Změnit" + +#: src/guiPasswordChange.cpp:164 +msgid "Passwords do not match!" +msgstr "Hesla se neshodují!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "Hlasitost: " + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Odejít" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Levé tlačítko myši" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Prostřední tlačítko myši" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Pravé tlačítko myši" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "X Tlačítko 1" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "Zpět" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "Vyčistit" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "Vrátit" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tabulátor" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "X Tlačítko 2" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "Klávesa velkého písmene" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Control" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Kana" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Nabídka" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Pauza" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Shift" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "Convert" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Esc" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "Final" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Kanji" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "Nonconvert" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "End" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Home" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "Změna režimu" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Další" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Předchozí" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Mezerník" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Dolů" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Spustit" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Print Screen" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Vybrat" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Nahoru" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Pomoc" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Insert" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "Snapshot" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "Levá klávesa Windows" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "Aplikace" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Numerická klávesnice: 0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Numerická klávesnice: 1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "Pravá klávesa Windows" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Spánek" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Numerická klávesnice: 2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Numerická klávesnice: 3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Numerická klávesnice: 4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Numerická klávesnice: 5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Numerická klávesnice: 6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Numerická klávesnice: 7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Numerická klávesnice: *" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Numerická klávesnice: +" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Numerická klávesnice: -" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Numerická klávesnice: /" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Numerická klávesnice: 8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Numerická klávesnice: 9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Num Lock" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Scroll Lock" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Levý Shift" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Pravý Shift" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Levý Control" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Levá klávesa Menu" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Pravý Control" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Pravá klávesa Menu" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Čárka" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Mínus" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "Tečka" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Plus" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "Attn" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "CrSel" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "Smazat EOF" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "ExSel" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "OEM Clear" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PA1" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "Přiblížení" + +#: src/main.cpp:1688 +msgid "Main Menu" +msgstr "Hlavní nabídka" + +#: src/main.cpp:1726 +msgid "Player name too long." +msgstr "Jméno hráče je příliš dlouhé." + +#: src/main.cpp:1764 +msgid "Connection error (timed out?)" +msgstr "Chyba spojení (vypršel čas?)" + +#: src/main.cpp:1929 +msgid "No world selected and no address provided. Nothing to do." +msgstr "Nebyl vybrán žádný svět a nebyla poskytnuta žádná adresa. Nemám co dělat." + +#: src/main.cpp:1936 +msgid "Provided world path doesn't exist: " +msgstr "Uvedená cesta ke světu neexistuje: " + +#: src/main.cpp:1945 +msgid "Could not find or load game \"" +msgstr "Hru nebylo možné nahrát nebo najít \"" + +#: src/main.cpp:1963 +msgid "Invalid gamespec." +msgstr "Neplatná specifikace hry." + +msgid "Downloading" +msgstr "Stahuji" + +#~ msgid "Game Name" +#~ msgstr "Název hry" + +#~ msgid "Gamemgr: Unable to copy mod \"$1\" to game \"$2\"" +#~ msgstr "Gamemgr: Nepovedlo se zkopírovat mod \"$1\" do hry \"$2\"" + +#~ msgid "GAMES" +#~ msgstr "HRY" + +#~ msgid "Games" +#~ msgstr "Hry" + +#~ msgid "Mods:" +#~ msgstr "Mody:" + +#~ msgid "edit game" +#~ msgstr "upravit hru" + +#~ msgid "new game" +#~ msgstr "nová hra" + +#~ msgid "EDIT GAME" +#~ msgstr "UPRAVIT HRU" + +#~ msgid "Remove selected mod" +#~ msgstr "Odstranit vybraný mod" + +#~ msgid "<<-- Add mod" +#~ msgstr "<<-- Přidat mod" + +#~ msgid "CLIENT" +#~ msgstr "KLIENT" + +#~ msgid "Favorites:" +#~ msgstr "Oblíbené:" + +#~ msgid "START SERVER" +#~ msgstr "MÍSTNÍ SERVER" + +#~ msgid "Name" +#~ msgstr "Jméno" + +#~ msgid "Password" +#~ msgstr "Heslo" + +#~ msgid "SETTINGS" +#~ msgstr "NASTAVENÍ" + +#~ msgid "Preload item visuals" +#~ msgstr "Přednačíst textury předmětů" + +#~ msgid "Finite Liquid" +#~ msgstr "Konečná voda" + +#~ msgid "SINGLE PLAYER" +#~ msgstr "HRA JEDNOHO HRÁČE" + +#~ msgid "TEXTURE PACKS" +#~ msgstr "BALÍČKY TEXTUR" + +#~ msgid "MODS" +#~ msgstr "MODY" + +#~ msgid "Add mod:" +#~ msgstr "Přidat mod:" + +#~ msgid "Local install" +#~ msgstr "Místní instalace" + +#~ msgid "Left click: Move all items, Right click: Move single item" +#~ msgstr "Levý klik: Přesunout všechny předměty, Pravý klik: Přesunout jeden předmět" + +#~ msgid "Anisotropic Filtering" +#~ msgstr "Anizotropní filtrování" + +#~ msgid "Mip-Mapping" +#~ msgstr "Mip-Mapování" diff --git a/po/da/minetest.po b/po/da/minetest.po new file mode 100644 index 0000000..35bce6c --- /dev/null +++ b/po/da/minetest.po @@ -0,0 +1,1135 @@ +# German translations for minetest-c55 package. +# Copyright (C) 2011 celeron +# This file is distributed under the same license as the minetest-c55 package. +# Frederik Helth , 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: 0.0.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2013-02-17 00:41+0200\n" +"Last-Translator: Rune Biskopstö Christensen \n" +"Language-Team: \n" +"Language: da\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Weblate 1.4-dev\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:26 +#, fuzzy +msgid "World:" +msgstr "Vælg verden:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +#, fuzzy +msgid "Hide Game" +msgstr "Spil" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:48 +#, fuzzy +msgid "Depends:" +msgstr "afhænger af:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Gem" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Anuller" + +#: builtin/mainmenu/dlg_config_world.lua:68 +#, fuzzy +msgid "Enable MP" +msgstr "Aktivér alle" + +#: builtin/mainmenu/dlg_config_world.lua:70 +#, fuzzy +msgid "Disable MP" +msgstr "Deaktivér alle" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "aktiveret" + +#: builtin/mainmenu/dlg_config_world.lua:82 +#, fuzzy +msgid "Enable all" +msgstr "Aktivér alle" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Verdens navn" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Spil" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Skab" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +#, fuzzy +msgid "A world named \"$1\" already exists" +msgstr "Kan ikke skabe verden: en verden med dette navn eksisterer allerede" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Ja" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +#, fuzzy +msgid "Delete World \"$1\"?" +msgstr "Slet verden" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Nej" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "Accepter" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:343 +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:363 +#, fuzzy +msgid "Failed to install $1 to $2" +msgstr "Mislykkedes i at initialisere verden" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +#, fuzzy +msgid "Downloading" +msgstr "Ned" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +#, fuzzy +msgid "Shortname:" +msgstr "Verdens navn" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Skabt af" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:121 +#, fuzzy +msgid "Select Mod File:" +msgstr "Vælg verden:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "Adresse/port" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Navn/kodeord" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Slet" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Forbind" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Ny" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Konfigurér" + +#: builtin/mainmenu/tab_server.lua:29 +#, fuzzy +msgid "Start Game" +msgstr "Start spil / Forbind" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Vælg verden:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Kreativ tilstand" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Aktivér skade" + +#: builtin/mainmenu/tab_server.lua:35 +#, fuzzy +msgid "Public" +msgstr "Vis offentlig" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "Glat belysning" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "Aktivér partikler" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "3D skyer" + +#: builtin/mainmenu/tab_settings.lua:140 +#, fuzzy +msgid "Fancy Trees" +msgstr "\"Smarte\" træer" + +#: builtin/mainmenu/tab_settings.lua:142 +#, fuzzy +msgid "Opaque Water" +msgstr "Opakt (uigennemsigtigt) vand" + +#: builtin/mainmenu/tab_settings.lua:144 +#, fuzzy +msgid "Connected Glass" +msgstr "Forbind" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "Mip-mapping" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "Anisotropisk filtréring" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "Bi-lineær filtréring" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "Tri-lineær filtréring" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "Shadere" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "Skift bindinger" + +#: builtin/mainmenu/tab_settings.lua:167 +#, fuzzy +msgid "Reset singleplayer world" +msgstr "Enligspiller" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +#, fuzzy +msgid "Bumpmapping" +msgstr "Mip-mapping" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "Indstillinger" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +#, fuzzy +msgid "Start Singleplayer" +msgstr "Enligspiller" + +#: builtin/mainmenu/tab_simple_main.lua:72 +#, fuzzy +msgid "Config mods" +msgstr "Konfigurér" + +#: builtin/mainmenu/tab_simple_main.lua:191 +#, fuzzy +msgid "Main" +msgstr "Hovedmenu" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Afspil" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Enligspiller" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +msgid "Texturepacks" +msgstr "" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "" + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "Genopstå" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "" + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "" + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "" + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Tjek debug.txt for detaljer." + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "Fortsæt" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" +"Tastebindinger. (Hvis denne menu fucker op, fjern elementer fra minetest." +"conf)" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "\"Brug\" = klatre ned" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "" +"Tryk på \"hop\" hurtigt to gange for at skifte frem og tilbage mellem flyve-" +"tilstand" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "Tast allerede i brug" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "Tryk på en tast" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Fremad" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Baglæns" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Venstre" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Højre" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Brug" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Hop" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Snige" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Slip" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Beholdning" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Snak" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Kommando" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Konsol" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Omstil flyvning" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Omstil hurtig" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "Omstil fylde" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "Afstands vælg" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "Udskriv stakke" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "Gammelt kodeord" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "Nyt kodeord" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "Bekræft kodeord" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "Skift" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "Kodeordene er ikke ens!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "" + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Venstre knap" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Midterste knap" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Højre knap" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "X knap 1" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "Tilbage" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "Ryd" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "Enter" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tabulator" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "X knap 2" + +#: src/keycode.cpp:226 +#, fuzzy +msgid "Capital" +msgstr "Store bogstaver" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Control" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Kana" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Menu" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Pause" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Shift" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "Konvertér" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Escape" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "Endelig" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Kanji" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "Nonconvert" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "End" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Home" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "Tilstandsskift" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Næste" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Foregående" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Mellemrum" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Ned" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Eksekvér" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Udskriv" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Vælg" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Op" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Hjælp" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Insert" + +#: src/keycode.cpp:230 +#, fuzzy +msgid "Snapshot" +msgstr "Tilstandsbillede" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "Venstre meta" + +#: src/keycode.cpp:234 +#, fuzzy +msgid "Apps" +msgstr "Applikationer" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Numpad 0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Numpad 1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "Højre meta" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Sov" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Numpad 2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Numpad 3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Numpad 4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Numpad 5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Numpad 6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Numpad 7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Numpad *" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Numpad +" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Numpad -" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Numpad /" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Numpad 8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Numpad 9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Num Lock" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Scroll Lock" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Venstre Skift" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Højre Skift" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Venstre Control" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Venstre Menu" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Højre Control" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Højre Menu" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Komma" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Minus" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "Punktum" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Plus" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "Giv agt" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "CrSel" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "Udvisk Slut-På-Fil" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "ExSel" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "OEM Ryd" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PA1" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "Zoom" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "Hovedmenu" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "Forbindelses fejl (udløbelse af tidsfrist?)" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "Ingen verden valgt og ingen adresse angivet. Ingen opgave at lave." + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "Kunne ikke finde eller indlæse spil \"" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "Ugyldig spilspecifikationer." + +#~ msgid "Left click: Move all items, Right click: Move single item" +#~ msgstr "Venstre klik: flyt alle enheder. Højre klik: flyt en enkelt enhed" + +#~ msgid "is required by:" +#~ msgstr "er påkrævet af:" + +#~ msgid "Configuration saved. " +#~ msgstr "Konfiguration gemt. " + +#~ msgid "Warning: Configuration not consistent. " +#~ msgstr "Advarsel: konfigurationen er ikke sammenhængende. " + +#~ msgid "Cannot create world: Name contains invalid characters" +#~ msgstr "Kan ikke skabe verden: navnet indeholder ugyldige bogstaver" + +#~ msgid "Multiplayer" +#~ msgstr "Flerspiller" + +#~ msgid "Advanced" +#~ msgstr "Avanceret" + +#~ msgid "Show Public" +#~ msgstr "Vis offentlig" + +#~ msgid "Show Favorites" +#~ msgstr "Vis favoritter" + +#~ msgid "Leave address blank to start a local server." +#~ msgstr "Lad adresse-feltet være tomt for at starte en lokal server." + +#~ msgid "Create world" +#~ msgstr "Skab verden" + +#~ msgid "Address required." +#~ msgstr "Adresse påkrævet." + +#~ msgid "Cannot delete world: Nothing selected" +#~ msgstr "Kan ikke slette verden: ingenting valgt" + +#~ msgid "Files to be deleted" +#~ msgstr "Filer som slettes" + +#~ msgid "Cannot create world: No games found" +#~ msgstr "Kan ikke skabe verden: ingen spil fundet" + +#~ msgid "Cannot configure world: Nothing selected" +#~ msgstr "Kan ikke konfigurere verden: ingenting valgt" + +#~ msgid "Failed to delete all world files" +#~ msgstr "Mislykkedes i at slette alle verdenens filer" + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: Walk\n" +#~ "- Mouse left: dig/hit\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- 0...9: select item\n" +#~ "- Shift: sneak\n" +#~ "- R: Toggle viewing all loaded chunks\n" +#~ "- I: Inventory menu\n" +#~ "- ESC: This menu\n" +#~ "- T: Chat\n" +#~ msgstr "" +#~ "Standard bindinger:\n" +#~ "- WASD: bevægelse\n" +#~ "- Venstre musetast: grav/slå\n" +#~ "- Højre musetast: anbring/brug\n" +#~ "- Musehjul: vælg genstand\n" +#~ "- 0...9: vælg genstand\n" +#~ "- Shift: snige\n" +#~ "- R: omstil se alle indlæste klumper\n" +#~ "- I: beholdning\n" +#~ "- ESC: denne menu\n" +#~ "- T: snak\n" + +#~ msgid "Delete map" +#~ msgstr "Slet mappen" + +#~ msgid "" +#~ "Warning: Some configured mods are missing.\n" +#~ "Their setting will be removed when you save the configuration. " +#~ msgstr "" +#~ "Advarsel: nogle konfigurerede modifikationer mangler.\n" +#~ "Deres indstillinger vil blive fjernet når du gemmer konfigurationen. " + +#~ msgid "" +#~ "Warning: Some mods are not configured yet.\n" +#~ "They will be enabled by default when you save the configuration. " +#~ msgstr "" +#~ "Advarsel: nogle modifikationer er endnu ikke konfigureret.\n" +#~ "De vil blive aktiveret som standard når du gemmer konfigurationen. " + +#~ msgid "Exit to OS" +#~ msgstr "Afslut til operativsystemet" + +#~ msgid "Exit to Menu" +#~ msgstr "Afslut til menu" + +#~ msgid "Change Password" +#~ msgstr "Skift kodeord" + +#~ msgid "Continue" +#~ msgstr "Fortsæt" + +#~ msgid "You died." +#~ msgstr "Du døde." + +#~ msgid "Preload item visuals" +#~ msgstr "For-indlæs elementernes grafik" + +#, fuzzy +#~ msgid "Password" +#~ msgstr "Gammelt kodeord" + +#, fuzzy +#~ msgid "Favorites:" +#~ msgstr "Vis favoritter" + +#, fuzzy +#~ msgid "Games" +#~ msgstr "Spil" + +#, fuzzy +#~ msgid "Game Name" +#~ msgstr "Spil" diff --git a/po/de/minetest.po b/po/de/minetest.po new file mode 100644 index 0000000..3789b6c --- /dev/null +++ b/po/de/minetest.po @@ -0,0 +1,1207 @@ +# German translations for minetest-c55 package. +# Copyright (C) 2011 celeron +# This file is distributed under the same license as the minetest-c55 package. +# Constantin Wenger , 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: 0.0.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2013-12-29 14:29+0200\n" +"Last-Translator: Pilz Adam \n" +"Language-Team: Deutsch <>\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 1.7-dev\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "Ok" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "Welt:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "Spiel verstecken" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "MP mods verstecken" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "Mod:" + +#: builtin/mainmenu/dlg_config_world.lua:48 +msgid "Depends:" +msgstr "Abhängig von:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Speichern" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Abbrechen" + +#: builtin/mainmenu/dlg_config_world.lua:68 +msgid "Enable MP" +msgstr "MP aktivieren" + +#: builtin/mainmenu/dlg_config_world.lua:70 +msgid "Disable MP" +msgstr "MP deaktivieren" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "Aktiviert" + +#: builtin/mainmenu/dlg_config_world.lua:82 +msgid "Enable all" +msgstr "Alle an" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Weltname" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "Seed" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "Weltgenerator" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Spiel" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Erstellen" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "Keine Spiele installiert." + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "Spiele können von minetest.net heruntergeladen werden" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "Warnung: Die minimale Testversion ist für Entwickler gedacht." + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "Andere Spiele (wie minetest_game) können von minetest.net heruntergeladen werden" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "Eine Welt namens \"$1\" existiert bereits" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "Kein Weltname gegeben oder kein Spiel ausgewählt" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "\"$1\" wirklich löschen?" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Ja" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "Nein, natürlich nicht!" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "Modmgr: Fehler beim löschen von \"$1\"" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "Modmgr: Unzulässiger Modpfad \"$1\"" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "Welt \"$1\" löschen?" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Nein" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "Modpack umbenennen:" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "Annehmen" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "Mod installieren: Datei: \"$1\"" + +#: builtin/mainmenu/modmgr.lua:343 +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" +"\n" +"Mod installieren: Nicht unterstützter Dateityp \"$1\" oder fehlerhaftes Archiv" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "Fehler beim installieren von $1 zu $2" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "Mod installieren: Kann keinen Ordnernamen für Modpack $1 finden" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "Mod installieren: Kann echten Namen für $1 nicht finden" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "Unsortiert" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "Suchen" + +#: builtin/mainmenu/store.lua:125 +msgid "Downloading" +msgstr "Lade herunter" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "Bitte warten..." + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "Erfolgreich installiert:" + +#: builtin/mainmenu/store.lua:163 +msgid "Shortname:" +msgstr "Kurzname:" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "ok" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "Bewertung" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "erneut installieren" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "Installieren" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "Schließen" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "Seite $1 von $2" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Credits" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "Kernentwickler" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "Aktive Mitwirkende" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "Frühere Mitwirkende" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "Installierte Mods:" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "Online-Mod-Speicher" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "Keine Modbeschreibung verfügbar" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "Mod Information:" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "Umbenennen" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "Ausgewähltes Modpack deinstallieren" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "Ausgewählte Mod deinstallieren" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "Mod Datei auswählen:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "Mods" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "Adresse / Port" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Name/Passwort" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "Öffentliche Serverliste" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Entfernen" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Verbinden" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "Client" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Neu" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Konfigurieren" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "Spiel starten" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Welt wählen:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Kreativitätsmodus" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Schaden einschalten" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Öffentlich" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "Port" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "Serverport" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "Server" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "Sind Sie sicher, dass Sie die Einzelspielerwelt löschen wollen?" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "Nein!!!" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "Besseres Licht" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "Aktiviere Partikel" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "3D Wolken" + +#: builtin/mainmenu/tab_settings.lua:140 +msgid "Fancy Trees" +msgstr "Schöne Bäume" + +#: builtin/mainmenu/tab_settings.lua:142 +msgid "Opaque Water" +msgstr "Undurchs. Wasser" + +#: builtin/mainmenu/tab_settings.lua:144 +msgid "Connected Glass" +msgstr "Verbundenes Glas" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "Neustart nach Ändern des Treibers erforderlich" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "Mip-Mapping" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "Anisotroper Filter" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "Bi-Linearer Filter" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "Tri-Linearer Filter" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "Shader" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "Tasten ändern" + +#: builtin/mainmenu/tab_settings.lua:167 +msgid "Reset singleplayer world" +msgstr "Einzelspielerwelt zurücksetzen" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "GUI-Skalierfaktor" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "Skalierfaktor:" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +msgid "Bumpmapping" +msgstr "Bumpmapping" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "Generiere Normalmaps" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "Parallax Occlusion" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "Wasserwellen" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "Wehende Blätter" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "Wogende Pflanzen" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "Um Shader zu benutzen muss der OpenGL Treiber benutzt werden." + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "Einstellungen" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "Flugmodus" + +#: builtin/mainmenu/tab_simple_main.lua:71 +msgid "Start Singleplayer" +msgstr "Starte Einzelspieler" + +#: builtin/mainmenu/tab_simple_main.lua:72 +msgid "Config mods" +msgstr "Mods konfigurieren" + +#: builtin/mainmenu/tab_simple_main.lua:191 +msgid "Main" +msgstr "Hauptmenü" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Spielen" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Einzelspieler" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "Texturpaket auswählen:" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "Keine Informationen vorhanden" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +msgid "Texturepacks" +msgstr "Texturpakete" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "Inventarbilder..." + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "no" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "Wiederbeleben" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "Item-Definitionen..." + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "Node-Definitionen..." + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "Medien..." + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr " KB/s" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr " MB/s" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Siehe debug.txt für Details." + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "Fortsetzen" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "Enter " + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "Steuerung" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "\"Benutzen\" = herunterklettern" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "Doppelt \"springen\" zum fliegen" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "Taste bereits in Benutzung" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "Taste drücken" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Vorwärts" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Rückwärts" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Links" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Rechts" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Benutzen" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Springen" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Schleichen" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Wegwerfen" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Inventar" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Chat" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Befehl" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Konsole" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Fliegen umsch." + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Speed umsch." + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "Noclip umsch." + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "Entfernung wählen" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "Stack ausgeben" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "Altes Passwort" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "Neues Passwort" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "Passwort wiederholen" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "Ändern" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "Passwörter passen nicht zusammen!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "Soundlautstärke: " + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Zurück" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Linke Taste" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Mittlere Taste" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Rechte Taste" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "X Knopf 1" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "Rücktaste" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "löschen" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "Enter" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tab" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "X Knopf 2" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "Feststellen" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Strg" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Kana" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Menü" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Pause" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Umsch." + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "Konvertieren" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Escape" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "Final" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Kanji" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "nicht konvertieren" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "Ende" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Pos1" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "Modus-Änderung" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Bild runter" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Bild hoch" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Leertaste" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Runter" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Ausführen" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Druck" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Selektiere" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Hoch" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Hilfe" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Einfg" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "Druck" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "Win links" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "Apps" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Ziffernblock 0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Ziffernblock 1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "Win rechts" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Schlaf" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Ziffernblock 2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Ziffernblock 3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Ziffernblock 4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Ziffernblock 5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Ziffernblock 6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Ziffernblock 7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Ziffernblock *" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Ziffernblock +" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Ziffernblock -" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Ziffernblock /" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Ziffernblock 8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Ziffernblock 9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Num" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Rollen" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Umsch. links" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Umsch. rechts" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Strg links" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Alt" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Strg rechts" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Alt Gr" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Komma" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Minus" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "Punkt" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Plus" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "Attn." + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "CrSel" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "Lösche OEF" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "ExSel" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "OEM Clear" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PA1" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "Zoom" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "Hauptmenü" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "Spielername zu lang." + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "Verbindungsfehler (Zeitüberschreitung?)" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "Keine Welt ausgewählt und keine Adresse angegeben. Nichts zu tun." + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "Angegebener Weltpfad existiert nicht: " + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "Kann Spiel nicht finden/laden \"" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "Invalide Spielspezif." + +#~ msgid "Left click: Move all items, Right click: Move single item" +#~ msgstr "Linksklick: Alle Items bewegen, Rechtsklick: Einzelnes Item bewegen" + +#~ msgid "is required by:" +#~ msgstr "wird benötigt von:" + +#~ msgid "Configuration saved. " +#~ msgstr "Konfiguration gespeichert. " + +#~ msgid "Warning: Configuration not consistent. " +#~ msgstr "Warnung: Konfiguration nicht konsistent. " + +#~ msgid "Cannot create world: Name contains invalid characters" +#~ msgstr "Kann Welt nicht erstellen: Name enthält ungültige Zeichen" + +#~ msgid "Multiplayer" +#~ msgstr "Mehrspieler" + +#~ msgid "Advanced" +#~ msgstr "Erweitert" + +#~ msgid "Show Public" +#~ msgstr "Zeige öffentliche" + +#~ msgid "Show Favorites" +#~ msgstr "Zeige Favoriten" + +#~ msgid "Leave address blank to start a local server." +#~ msgstr "Lasse die Adresse frei um einen eigenen Server zu starten." + +#~ msgid "Create world" +#~ msgstr "Welt erstellen" + +#~ msgid "Address required." +#~ msgstr "Adresse benötigt." + +#~ msgid "Cannot delete world: Nothing selected" +#~ msgstr "Kann Welt nicht löchen: Nichts ausgewählt" + +#~ msgid "Files to be deleted" +#~ msgstr "Zu löschende Dateien" + +#~ msgid "Cannot create world: No games found" +#~ msgstr "Kann Welt nicht erstellen: Keine Spiele gefunden" + +#~ msgid "Cannot configure world: Nothing selected" +#~ msgstr "Kann Welt nicht konfigurieren: Nichts ausgewählt" + +#~ msgid "Failed to delete all world files" +#~ msgstr "Es konnten nicht alle Welt Dateien gelöscht werden" + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: Walk\n" +#~ "- Mouse left: dig/hit\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- 0...9: select item\n" +#~ "- Shift: sneak\n" +#~ "- R: Toggle viewing all loaded chunks\n" +#~ "- I: Inventory menu\n" +#~ "- ESC: This menu\n" +#~ "- T: Chat\n" +#~ msgstr "" +#~ "Steuerung:\n" +#~ "- WASD: Gehen\n" +#~ "- Linksklick: Graben/Schlagen\n" +#~ "- Rechtsklick: Platzieren\n" +#~ "- Mausrad: Item auswählen\n" +#~ "- 0...9: Item auswählen\n" +#~ "- Shift: Schleichen\n" +#~ "- R: alle geladenen Blöcke anzeigen (wechseln)\n" +#~ "- I: Inventar\n" +#~ "- T: Chat\n" + +#~ msgid "Delete map" +#~ msgstr "Karte löschen" + +#~ msgid "KEYBINDINGS" +#~ msgstr "TASTEN EINST." + +#~ msgid "" +#~ "Warning: Some configured mods are missing.\n" +#~ "Their setting will be removed when you save the configuration. " +#~ msgstr "" +#~ "Warnung: Einige konfigurierte Mods fehlen.\n" +#~ "Mod Einstellungen werden gelöscht wenn die Konfiguration gespeichert " +#~ "wird. " + +#~ msgid "" +#~ "Warning: Some mods are not configured yet.\n" +#~ "They will be enabled by default when you save the configuration. " +#~ msgstr "" +#~ "Warnung: Einige Mods sind noch nicht konfiguriert.\n" +#~ "Sie werden aktiviert wenn die Konfiguration gespeichert wird. " + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: move\n" +#~ "- Space: jump/climb\n" +#~ "- Shift: sneak/go down\n" +#~ "- Q: drop item\n" +#~ "- I: inventory\n" +#~ "- Mouse: turn/look\n" +#~ "- Mouse left: dig/punch\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- T: chat\n" +#~ msgstr "" +#~ "Standard Tastaturebelegung:\n" +#~ "- WASD: Bewegen\n" +#~ "- Leertaste: Springen/Klettern\n" +#~ "- Umschalt: Kriechen/herunterklettern\n" +#~ "- Q: Item droppen\n" +#~ "- I: Inventar\n" +#~ "- Maus: drehen/umschauen\n" +#~ "- Maus links: Abbauen/Schlagen\n" +#~ "- Maus rechts: Platzieren/Benutzen\n" +#~ "- Mausrad: Item auswählen\n" +#~ "- T: Chat\n" + +#~ msgid "Exit to OS" +#~ msgstr "Programm beenden" + +#~ msgid "Exit to Menu" +#~ msgstr "Hauptmenü" + +#~ msgid "Sound Volume" +#~ msgstr "Sound Lautstärke" + +#~ msgid "Change Password" +#~ msgstr "Passwort ändern" + +#~ msgid "Continue" +#~ msgstr "Weiter" + +#~ msgid "You died." +#~ msgstr "Sie sind gestorben." + +#~ msgid "Shutting down stuff..." +#~ msgstr "Herunterfahren..." + +#~ msgid "Connecting to server..." +#~ msgstr "Verbinde zum Server..." + +#~ msgid "Resolving address..." +#~ msgstr "Löse Adresse auf..." + +#~ msgid "Creating client..." +#~ msgstr "Erstelle Client..." + +#~ msgid "Creating server...." +#~ msgstr "Erstelle Server..." + +#~ msgid "Loading..." +#~ msgstr "Lädt..." + +#~ msgid "Local install" +#~ msgstr "Lokale Install." + +#~ msgid "Add mod:" +#~ msgstr "Modifikation hinzufügen:" + +#~ msgid "MODS" +#~ msgstr "MODS" + +#~ msgid "TEXTURE PACKS" +#~ msgstr "TEXTUREN PAKETE" + +#~ msgid "SINGLE PLAYER" +#~ msgstr "EINZELSPIELER" + +#~ msgid "Finite Liquid" +#~ msgstr "Endliches Wasser" + +#~ msgid "Preload item visuals" +#~ msgstr "Lade Inventarbilder vor" + +#~ msgid "SETTINGS" +#~ msgstr "EINSTELLUNGEN" + +#~ msgid "Password" +#~ msgstr "Passwort" + +#~ msgid "Name" +#~ msgstr "Name" + +#~ msgid "START SERVER" +#~ msgstr "SERVER STARTEN" + +#~ msgid "Favorites:" +#~ msgstr "Favoriten:" + +#~ msgid "CLIENT" +#~ msgstr "CLIENT" + +#~ msgid "<<-- Add mod" +#~ msgstr "<<-- Mod hinzufügen" + +#~ msgid "Remove selected mod" +#~ msgstr "Ausgewählte Mod löschen" + +#~ msgid "EDIT GAME" +#~ msgstr "SPIEL ÄNDERN" + +#~ msgid "new game" +#~ msgstr "neues Spiel" + +#~ msgid "edit game" +#~ msgstr "Spiel ändern" + +#~ msgid "Mods:" +#~ msgstr "Mods:" + +#~ msgid "Games" +#~ msgstr "Spiele" + +#~ msgid "GAMES" +#~ msgstr "SPIELE" + +#~ msgid "Gamemgr: Unable to copy mod \"$1\" to game \"$2\"" +#~ msgstr "Gamemgr: Kann mod \"$1\" nicht in Spiel \"$2\" kopieren" + +#~ msgid "Game Name" +#~ msgstr "Spielname" diff --git a/po/es/minetest.po b/po/es/minetest.po new file mode 100644 index 0000000..5f60213 --- /dev/null +++ b/po/es/minetest.po @@ -0,0 +1,1150 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-02-14 13:50+0100\n" +"PO-Revision-Date: 2015-02-14 13:35+0100\n" +"Last-Translator: Diego de las Heras \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Poedit 1.7.4\n" + +#: builtin/fstk/ui.lua:67 builtin/mainmenu/store.lua:165 +msgid "Ok" +msgstr "Aceptar" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "Mundo:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "Ocultar juego" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "Ocultar contenido mp" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "Mod:" + +#: builtin/mainmenu/dlg_config_world.lua:48 builtin/mainmenu/tab_mods.lua:99 +msgid "Depends:" +msgstr "Dependencias:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Guardar" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Cancelar" + +#: builtin/mainmenu/dlg_config_world.lua:68 +msgid "Enable MP" +msgstr "Activar paquete" + +#: builtin/mainmenu/dlg_config_world.lua:70 +msgid "Disable MP" +msgstr "Desactivar paquete" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "Activado" + +#: builtin/mainmenu/dlg_config_world.lua:82 +msgid "Enable all" +msgstr "Activar todos" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Nombre del mundo" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "Semilla" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "Generador de mapas" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Juego" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Crear" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "No tienes sub-juegos instalados." + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "Descarga algunos desde minetest.net" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" +"Advertencia: El juego \"Minimal development test\" está diseñado para " +"desarrolladores." + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "Descarga un sub-juego, como minetest_game, desde minetest.net" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "Ya existe un mundo llamado \"$1\"" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "No se ha dado un nombre al mundo o no se ha seleccionado uno" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "¿Realmente desea borrar \"$1\"?" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:79 +msgid "Yes" +msgstr "Sí" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "¡No, por su puesto que no!" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "Modmgr: Error al borrar \"$1\"" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "Modmgr: Ruta del mod \"$1\" inválida" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "¿Eliminar el mundo \"$1\"?" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "No" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "Renombrar paquete de mod:" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "Aceptar" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "Instalar mod: Archivo: \"$1\"" + +#: builtin/mainmenu/modmgr.lua:343 +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" +"\n" +"Instalar mod: Formato de archivo \"$1\" no soportado o archivo corrupto" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "Fallo al instalar $1 en $2" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" +"Instalar mod: Imposible encontrar un nombre de archivo adecuado para el " +"paquete de mod $1" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "Instalar mod: Imposible encontrar el nombre real del mod para: $1" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "Sin ordenar" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:580 +msgid "Search" +msgstr "Buscar" + +#: builtin/mainmenu/store.lua:126 +msgid "Downloading $1, please wait..." +msgstr "Descargando $1, por favor espere..." + +#: builtin/mainmenu/store.lua:160 +msgid "Successfully installed:" +msgstr "Instalado con éxito:" + +#: builtin/mainmenu/store.lua:162 +msgid "Shortname:" +msgstr "Nombre corto:" + +#: builtin/mainmenu/store.lua:472 +msgid "Rating" +msgstr "Clasificación" + +#: builtin/mainmenu/store.lua:497 +msgid "re-Install" +msgstr "Reinstalar" + +#: builtin/mainmenu/store.lua:499 +msgid "Install" +msgstr "Instalar" + +# En el menú principal de mods pone repositorio no tienda. +#: builtin/mainmenu/store.lua:518 +msgid "Close store" +msgstr "Cerrar repositorio" + +#: builtin/mainmenu/store.lua:526 +msgid "Page $1 of $2" +msgstr "Página $1 de $2" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Créditos" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "Desarrolladores principales" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "Colaboradores activos" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "Antiguos colaboradores" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "Mods instalados:" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "Repositorio de mods en línea" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "La descripción del mod no está disponible" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "Información del mod:" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "Renombrar" + +# El nombre completo no cabe. +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "Desinstalar el paquete selecc." + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "Desinstalar el mod seleccionado" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "Selecciona el fichero del mod:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "Mods" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address / Port :" +msgstr "Dirección / puerto:" + +#: builtin/mainmenu/tab_multiplayer.lua:24 +msgid "Name / Password :" +msgstr "Nombre / contraseña:" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "Lista de servidores públicos" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Borrar" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Conectar" + +#: builtin/mainmenu/tab_multiplayer.lua:62 +#: builtin/mainmenu/tab_simple_main.lua:45 +msgid "Creative mode" +msgstr "Modo creativo" + +#: builtin/mainmenu/tab_multiplayer.lua:63 +#: builtin/mainmenu/tab_simple_main.lua:46 +msgid "Damage enabled" +msgstr "Daño activado" + +#: builtin/mainmenu/tab_multiplayer.lua:64 +#: builtin/mainmenu/tab_simple_main.lua:47 +msgid "PvP enabled" +msgstr "PvP activado" + +#: builtin/mainmenu/tab_multiplayer.lua:247 +msgid "Client" +msgstr "Cliente" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Nuevo" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Configurar" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "Iniciar juego" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Selecciona un mundo:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:75 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Modo creativo" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:77 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Permitir daños" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Público" + +# Los dos puntos son intencionados. +#: builtin/mainmenu/tab_server.lua:37 builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Nombre / contraseña:" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "Asociar dirección" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "Puerto" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "Puerto del servidor:" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "Servidor" + +#: builtin/mainmenu/tab_settings.lua:21 +msgid "No Filter" +msgstr "Sin filtro" + +#: builtin/mainmenu/tab_settings.lua:22 +msgid "Bilinear Filter" +msgstr "Filtro bi-lineal" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Trilinear Filter" +msgstr "Filtro tri-lineal" + +#: builtin/mainmenu/tab_settings.lua:32 +msgid "No Mipmap" +msgstr "Sin Mipmap" + +#: builtin/mainmenu/tab_settings.lua:33 +msgid "Mipmap" +msgstr "Mipmap" + +#: builtin/mainmenu/tab_settings.lua:34 +msgid "Mipmap + Aniso. Filter" +msgstr "Mipmap + Filtro aniso." + +#: builtin/mainmenu/tab_settings.lua:77 +msgid "Are you sure to reset your singleplayer world?" +msgstr "¿Estás seguro de querer reiniciar el mundo de un jugador?" + +#: builtin/mainmenu/tab_settings.lua:81 +msgid "No!!!" +msgstr "¡¡¡No!!!" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Smooth Lighting" +msgstr "Iluminación suave" + +#: builtin/mainmenu/tab_settings.lua:183 +msgid "Enable Particles" +msgstr "Habilitar partículas" + +#: builtin/mainmenu/tab_settings.lua:185 +msgid "3D Clouds" +msgstr "Nubes 3D" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Fancy Trees" +msgstr "Árboles detallados" + +#: builtin/mainmenu/tab_settings.lua:189 +msgid "Opaque Water" +msgstr "Agua opaca" + +#: builtin/mainmenu/tab_settings.lua:191 +msgid "Connected Glass" +msgstr "Vidrios conectados" + +#: builtin/mainmenu/tab_settings.lua:193 +msgid "Node Highlighting" +msgstr "Resaltar nodos" + +#: builtin/mainmenu/tab_settings.lua:196 +msgid "Texturing:" +msgstr "Texturizado:" + +#: builtin/mainmenu/tab_settings.lua:201 +msgid "Rendering:" +msgstr "Renderizado:" + +#: builtin/mainmenu/tab_settings.lua:205 +msgid "Restart minetest for driver change to take effect" +msgstr "Reinicia minetest para que los cambios en el controlador tengan efecto" + +#: builtin/mainmenu/tab_settings.lua:207 +msgid "Shaders" +msgstr "Sombreadores" + +#: builtin/mainmenu/tab_settings.lua:212 +msgid "Change keys" +msgstr "Configurar teclas" + +#: builtin/mainmenu/tab_settings.lua:215 +msgid "Reset singleplayer world" +msgstr "Reiniciar mundo de un jugador" + +#: builtin/mainmenu/tab_settings.lua:219 +msgid "GUI scale factor" +msgstr "Factor de escala (GUI)" + +#: builtin/mainmenu/tab_settings.lua:223 +msgid "Scaling factor applied to menu elements: " +msgstr "Factor de escala aplicado a los elementos del menú: " + +#: builtin/mainmenu/tab_settings.lua:229 +msgid "Touch free target" +msgstr "Tocar para interactuar" + +#: builtin/mainmenu/tab_settings.lua:235 +msgid "Touchthreshold (px)" +msgstr "Umbral táctil (px)" + +#: builtin/mainmenu/tab_settings.lua:242 builtin/mainmenu/tab_settings.lua:256 +msgid "Bumpmapping" +msgstr "Mapeado de relieve" + +#: builtin/mainmenu/tab_settings.lua:244 builtin/mainmenu/tab_settings.lua:257 +msgid "Generate Normalmaps" +msgstr "Generar mapas normales" + +#: builtin/mainmenu/tab_settings.lua:246 builtin/mainmenu/tab_settings.lua:258 +msgid "Parallax Occlusion" +msgstr "Oclusión de paralaje" + +#: builtin/mainmenu/tab_settings.lua:248 builtin/mainmenu/tab_settings.lua:259 +msgid "Waving Water" +msgstr "Oleaje en el agua" + +#: builtin/mainmenu/tab_settings.lua:250 builtin/mainmenu/tab_settings.lua:260 +msgid "Waving Leaves" +msgstr "Movimiento de hojas" + +#: builtin/mainmenu/tab_settings.lua:252 builtin/mainmenu/tab_settings.lua:261 +msgid "Waving Plants" +msgstr "Movimiento de plantas" + +#: builtin/mainmenu/tab_settings.lua:287 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "Para habilitar los sombreadores debe utilizar el controlador OpenGL." + +#: builtin/mainmenu/tab_settings.lua:398 +msgid "Settings" +msgstr "Configuración" + +#: builtin/mainmenu/tab_simple_main.lua:79 +msgid "Fly mode" +msgstr "Modo vuelo" + +#: builtin/mainmenu/tab_simple_main.lua:83 +msgid "Start Singleplayer" +msgstr "Comenzar un jugador" + +#: builtin/mainmenu/tab_simple_main.lua:84 +msgid "Config mods" +msgstr "Configurar mods" + +#: builtin/mainmenu/tab_simple_main.lua:203 +msgid "Main" +msgstr "Principal" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Jugar" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Un jugador" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "Seleccione un paquete de texturas:" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "Sin información disponible" + +# No cabe "Paquetes de texturas". +#: builtin/mainmenu/tab_texturepacks.lua:114 +msgid "Texturepacks" +msgstr "Texturas" + +#: src/client.cpp:2788 +msgid "Loading textures..." +msgstr "Cargando texturas..." + +#: src/client.cpp:2798 +msgid "Rebuilding shaders..." +msgstr "Reconstruyendo sombreadores..." + +#: src/client.cpp:2805 +msgid "Initializing nodes..." +msgstr "Inicializando nodos..." + +#: src/client.cpp:2820 +msgid "Item textures..." +msgstr "Texturas de objetos..." + +#: src/client.cpp:2845 +msgid "Done!" +msgstr "¡Completado!" + +#: src/client/clientlauncher.cpp:171 +msgid "Main Menu" +msgstr "Menú principal" + +#: src/client/clientlauncher.cpp:209 +msgid "Player name too long." +msgstr "Nombre de jugador demasiado largo." + +#: src/client/clientlauncher.cpp:247 +msgid "Connection error (timed out?)" +msgstr "Error de conexión (¿tiempo agotado?)" + +#: src/client/clientlauncher.cpp:412 +msgid "No world selected and no address provided. Nothing to do." +msgstr "" +"No se seleccionó el mundo y no se ha especificado una dirección. Nada que " +"hacer." + +#: src/client/clientlauncher.cpp:419 +msgid "Provided world path doesn't exist: " +msgstr "La ruta del mundo especificada no existe: " + +#: src/client/clientlauncher.cpp:428 +msgid "Could not find or load game \"" +msgstr "No se puede encontrar o cargar el juego \"" + +#: src/client/clientlauncher.cpp:446 +msgid "Invalid gamespec." +msgstr "Juego especificado no válido." + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "needs_fallback_font" + +#: src/game.cpp:1057 src/guiFormSpecMenu.cpp:2006 +msgid "Proceed" +msgstr "Continuar" + +#: src/game.cpp:1077 +msgid "You died." +msgstr "Has muerto." + +#: src/game.cpp:1078 +msgid "Respawn" +msgstr "Revivir" + +#: src/game.cpp:1097 +msgid "" +"Default Controls:\n" +"No menu visible:\n" +"- single tap: button activate\n" +"- double tap: place/use\n" +"- slide finger: look around\n" +"Menu/Inventory visible:\n" +"- double tap (outside):\n" +" -->close\n" +"- touch stack, touch slot:\n" +" --> move stack\n" +"- touch&drag, tap 2nd finger\n" +" --> place single item to slot\n" +msgstr "" +"Controles predeterminados:\n" +"Con el menú oculto:\n" +"- toque simple: botón activar\n" +"- toque doble: colocar/usar\n" +"- deslizar dedo: mirar alrededor\n" +"Con el menú/inventario visible:\n" +"- toque doble (fuera):\n" +" -->cerrar\n" +"- toque en la pila de objetos:\n" +" -->mover la pila\n" +"- toque y arrastrar, toque con 2 dedos:\n" +" -->colocar solamente un objeto\n" + +#: src/game.cpp:1111 +msgid "" +"Default Controls:\n" +"- WASD: move\n" +"- Space: jump/climb\n" +"- Shift: sneak/go down\n" +"- Q: drop item\n" +"- I: inventory\n" +"- Mouse: turn/look\n" +"- Mouse left: dig/punch\n" +"- Mouse right: place/use\n" +"- Mouse wheel: select item\n" +"- T: chat\n" +msgstr "" +"Controles predeterminados:\n" +"- WASD: moverse\n" +"- Espacio: saltar/subir\n" +"- Mayús.: puntillas/bajar\n" +"- Q: soltar objeto\n" +"- I: inventario\n" +"- Ratón: girar/mirar\n" +"- Ratón izq.: cavar/golpear\n" +"- Ratón der.: colocar/usar\n" +"- Ratón rueda: elegir objeto\n" +"- T: chat\n" + +#: src/game.cpp:1130 +msgid "Continue" +msgstr "Continuar" + +#: src/game.cpp:1134 +msgid "Change Password" +msgstr "Cambiar contraseña" + +#: src/game.cpp:1139 +msgid "Sound Volume" +msgstr "Volumen del sonido" + +#: src/game.cpp:1141 +msgid "Change Keys" +msgstr "Configurar teclas" + +#: src/game.cpp:1144 +msgid "Exit to Menu" +msgstr "Salir al menú" + +#: src/game.cpp:1146 +msgid "Exit to OS" +msgstr "Salir al S.O." + +#: src/game.cpp:1809 +msgid "Shutting down..." +msgstr "Cerrando..." + +#: src/game.cpp:1858 +msgid "Loading..." +msgstr "Cargando..." + +#: src/game.cpp:1915 +msgid "Creating server..." +msgstr "Creando servidor..." + +#: src/game.cpp:1952 +msgid "Creating client..." +msgstr "Creando cliente..." + +#: src/game.cpp:2125 +msgid "Resolving address..." +msgstr "Resolviendo dirección..." + +#: src/game.cpp:2216 +msgid "Connecting to server..." +msgstr "Conectando al servidor..." + +#: src/game.cpp:2274 +msgid "Item definitions..." +msgstr "Definiciones de objetos..." + +#: src/game.cpp:2279 +msgid "Node definitions..." +msgstr "Definiciones de nodos..." + +#: src/game.cpp:2286 +msgid "Media..." +msgstr "Media..." + +#: src/game.cpp:2291 +msgid " KB/s" +msgstr " KB/s" + +#: src/game.cpp:2295 +msgid " MB/s" +msgstr " MB/s" + +#: src/game.cpp:4210 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Consulta debug.txt para obtener más detalles." + +#: src/guiFormSpecMenu.cpp:2797 +msgid "Enter " +msgstr "Ingresar " + +#: src/guiFormSpecMenu.cpp:2817 +msgid "ok" +msgstr "aceptar" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" +"Combinaciones de teclas. (Si este menú da error, elimina líneas en minetest." +"conf)" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "\"Usar\" = Descender" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "Pulsar dos veces \"saltar\" para volar" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "La tecla ya se está utilizando" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "pulsa una tecla" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Adelante" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Atrás" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Izquierda" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Derecha" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Usar" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Saltar" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Caminar" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Tirar" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Inventario" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Chat" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Comando" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Consola" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Activar volar" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Activar rápido" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "Activar noclip" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "Seleccionar distancia" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "Imprimir pilas" + +#: src/guiPasswordChange.cpp:108 +msgid "Old Password" +msgstr "Contraseña anterior" + +#: src/guiPasswordChange.cpp:124 +msgid "New Password" +msgstr "Contraseña nueva" + +#: src/guiPasswordChange.cpp:139 +msgid "Confirm Password" +msgstr "Confirmar contraseña" + +#: src/guiPasswordChange.cpp:155 +msgid "Change" +msgstr "Cambiar" + +#: src/guiPasswordChange.cpp:164 +msgid "Passwords do not match!" +msgstr "¡Las contraseñas no coinciden!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "Volúmen del sonido: " + +# Es en el menú de sonido. Salir suena muy fuerte. +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Cerrar" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Botón izquierdo" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Botón central" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Botón derecho" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "X Button 1" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "Atrás" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "Limpiar" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "Retorno" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tabulador" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "X Button 2" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "Bloq Mayús" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Control" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Kana" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Menú" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Pausa" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Shift" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "Convertir" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Escape" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "Final" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Kanji" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "No convertir" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "Fin" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Inicio" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "Cambio de modo" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Siguiente" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Anterior" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Espacio" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Abajo" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Ejecutar" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Captura" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Seleccionar" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Arriba" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Ayuda" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Introducir" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "Captura de pantalla" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "Win izq." + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "Aplicaciones" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Numpad 0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Numpad 1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "Win der." + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Suspender" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Numpad 2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Numpad 3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Numpad 4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Numpad 5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Numpad 6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Numpad 7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Numpad *" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Numpad +" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Numpad -" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Numpad /" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Numpad 8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Numpad 9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Bloq Núm" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Bloq Despl" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Shift izq." + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Shift der." + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Control izq." + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Menú izq." + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Control der." + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Menú der." + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Coma" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Menos" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "Punto" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Más" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "Attn" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "CrSel" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "Borrar OEF" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "ExSel" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "Limpiar OEM" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PA1" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "Zoom" diff --git a/po/et/minetest.po b/po/et/minetest.po new file mode 100644 index 0000000..00a9195 --- /dev/null +++ b/po/et/minetest.po @@ -0,0 +1,1161 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2013-12-18 21:28+0200\n" +"Last-Translator: Jabo Babo \n" +"Language-Team: LANGUAGE \n" +"Language: et\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 1.7-dev\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "kinnitama" + +#: builtin/mainmenu/dlg_config_world.lua:26 +#, fuzzy +msgid "World:" +msgstr "Vali maailm:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +#, fuzzy +msgid "Hide Game" +msgstr "Mäng" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:48 +#, fuzzy +msgid "Depends:" +msgstr "Vajab:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Salvesta" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Tühista" + +#: builtin/mainmenu/dlg_config_world.lua:68 +#, fuzzy +msgid "Enable MP" +msgstr "Lülita kõik sisse" + +#: builtin/mainmenu/dlg_config_world.lua:70 +#, fuzzy +msgid "Disable MP" +msgstr "Lülita kõik välja" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "Sisse lülitatud" + +#: builtin/mainmenu/dlg_config_world.lua:82 +#, fuzzy +msgid "Enable all" +msgstr "Lülita kõik sisse" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Maailma nimi" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:56 +#, fuzzy +msgid "Mapgen" +msgstr "Põlvkonna kaardid" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Mäng" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Loo" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +#, fuzzy +msgid "A world named \"$1\" already exists" +msgstr "Maailma loomine ebaõnnestus: Samanimeline maailm on juba olemas" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "No nimi või no mäng valitud" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Jah" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +#, fuzzy +msgid "Delete World \"$1\"?" +msgstr "Kustuta maailm: \"$1\"?" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Ei" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "Nõustu" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:343 +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:363 +#, fuzzy +msgid "Failed to install $1 to $2" +msgstr "Maailma initsialiseerimine ebaõnnestus" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +#, fuzzy +msgid "Downloading" +msgstr "Alla" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +#, fuzzy +msgid "Shortname:" +msgstr "Maailma nimi" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Tänuavaldused" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "Põhiline arendaja" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "Co-arendaja" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "Early arendajad" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:121 +#, fuzzy +msgid "Select Mod File:" +msgstr "Vali maailm:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "IP/Port" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Nimi/Parool" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +#, fuzzy +msgid "Public Serverlist" +msgstr "Avatud serverite nimekiri:" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Kustuta" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Liitu" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Uus" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Konfigureeri" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "Alusta mängu" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Vali maailm:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Kujunduslik mängumood" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Lülita valu sisse" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Avalik" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "Ilus valgustus" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "Lülita osakesed sisse" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "3D pilved" + +#: builtin/mainmenu/tab_settings.lua:140 +#, fuzzy +msgid "Fancy Trees" +msgstr "Uhked puud" + +#: builtin/mainmenu/tab_settings.lua:142 +#, fuzzy +msgid "Opaque Water" +msgstr "Läbipaistmatu vesi" + +#: builtin/mainmenu/tab_settings.lua:144 +#, fuzzy +msgid "Connected Glass" +msgstr "Liitu" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "Väga hea kvaliteet" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "Anisotroopne Filtreerimine" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "Bi-lineaarsed Filtreerimine" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "Tri-Linear Filtreerimine" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "Varjutajad" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "Vaheta nuppe" + +#: builtin/mainmenu/tab_settings.lua:167 +#, fuzzy +msgid "Reset singleplayer world" +msgstr "Üksikmäng" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +#, fuzzy +msgid "Bumpmapping" +msgstr "Väga hea kvaliteet" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "Aktiveerimiseks varjud, nad vajavad OpenGL draiver." + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "Sätted" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +#, fuzzy +msgid "Start Singleplayer" +msgstr "Üksikmäng" + +#: builtin/mainmenu/tab_simple_main.lua:72 +#, fuzzy +msgid "Config mods" +msgstr "Konfigureeri" + +#: builtin/mainmenu/tab_simple_main.lua:191 +#, fuzzy +msgid "Main" +msgstr "Menüü" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Mängi" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Üksikmäng" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "Vali graafika:" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "Informatsioon ei ole kättesaadav" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +#, fuzzy +msgid "Texturepacks" +msgstr "Vali graafika:" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "" + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "Ärka ellu" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "" + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "" + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "" + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Vaata debug.txt info jaoks." + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "Jätka" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" +"Nupusätted. (Kui see menüü sassi läheb, siis kustuta asju failist minetest." +"conf)" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "\"Tegevus\" = Roni alla" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "Topeltklõpsa \"Hüppamist\" et sisse lülitada lendamine" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "Nupp juba kasutuses" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "Vajuta nuppu" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Edasi" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Tagasi" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Vasakule" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Paremale" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Tegevus" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Hüppamine" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Hiilimine" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Viska maha" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Seljakott" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Jututuba" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Käsklus" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Konsool" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Lülita lendamine sisse" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Lülita kiirus sisse" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "Lülita läbi seinte minek sisse" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "Kauguse valik" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "Prindi kogused" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "Vana parool" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "Uus parool" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "Kinnita parooli" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "Muuda" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "Paroolid ei ole samad!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "Hääle Volüüm: " + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Välju" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Vasak nupp" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Keskmine nupp" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Parem nupp" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "X Nuppp 1" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "Tagasi" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "Tühjenda" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "Enter" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Reavahetus" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "X Nupp 2" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "Caps Lock" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "CTRL" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Kana" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Menüü" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Paus" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Shift," + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "Konverteeri" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Põgene" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "Viimane" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Kanji" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "Konverteerimatta" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "Lõpeta" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Kodu" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "Moodi vahetamine" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Järgmine" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Eelnev" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Tühik" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Alla" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Soorita" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Prindi" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Vali" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Üles" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Abi" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Sisesta" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "Mängupilt" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "Vasak Windowsi nupp" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "Aplikatsioonid" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Numbrilaual 0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Numbrilaual 1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "Parem Windowsi nupp" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Maga" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Numbrilaual 2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Numbrilaual 3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Numbrilaual 4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Numbrilaual 5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Numbrilaual 6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Numbrilaual 7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Numbrilaual *" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Numbrilaual +" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Numbrilaual -" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Numbrilaual /" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Numbrilaual 8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Numbrilaual 9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Numbrilaual Num Lock" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Scroll lukk" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Vasak Shift" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Parem Shift" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Vasak CTRL" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Vasak Menüü" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Parem CTRL" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Parem Menüü" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Koma" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Miinus" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "Punkt" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Pluss" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "Attn" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "CrSel" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "Kustuta OEF" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "ExSel" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "OEM Tühi" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PA1" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "Suumi" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "Menüü" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "Ühenduse viga (Aeg otsas?)" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "Pole valitud ei maailma ega IP aadressi. Pole midagi teha." + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "Ei leia ega suuda jätkata mängu \"" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "Vale mängu ID." + +#~ msgid "Left click: Move all items, Right click: Move single item" +#~ msgstr "" +#~ "Vasak hiireklõps: Liiguta kõiki asju, Parem hiireklõps: Liiguta üksikut " +#~ "asja" + +#~ msgid "is required by:" +#~ msgstr "Seda vajavad:" + +#~ msgid "Configuration saved. " +#~ msgstr "Konfiguratsioon salvestatud. " + +#~ msgid "Warning: Configuration not consistent. " +#~ msgstr "Hoiatus: Konfiguratsioon pole kindel." + +#~ msgid "Cannot create world: Name contains invalid characters" +#~ msgstr "Maailma loomine ebaõnnestus: Nimes esineb keelatud tähti" + +#~ msgid "Multiplayer" +#~ msgstr "Mitmikmäng" + +#~ msgid "Advanced" +#~ msgstr "Arenenud sätted" + +#~ msgid "Show Public" +#~ msgstr "Näita avalikke" + +#~ msgid "Show Favorites" +#~ msgstr "Näita lemmikuid" + +#~ msgid "Leave address blank to start a local server." +#~ msgstr "Jäta IP lahter tühjaks et alustada LAN serverit." + +#~ msgid "Create world" +#~ msgstr "Loo maailm" + +#~ msgid "Address required." +#~ msgstr "IP on vajalkik." + +#~ msgid "Cannot delete world: Nothing selected" +#~ msgstr "Maailma kustutamine ebaõnnestus: Maailma pole valitud" + +#~ msgid "Files to be deleted" +#~ msgstr "Failid mida kustutada" + +#~ msgid "Cannot create world: No games found" +#~ msgstr "Maailma loomine ebaõnnestus: Mängu ei leitud" + +#~ msgid "Cannot configure world: Nothing selected" +#~ msgstr "Maailma konfigureerimine ebaõnnestus: Pole midagi valitud" + +#~ msgid "Failed to delete all world files" +#~ msgstr "Kõigi maailma failide kustutamine ebaõnnestus" + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: Walk\n" +#~ "- Mouse left: dig/hit\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- 0...9: select item\n" +#~ "- Shift: sneak\n" +#~ "- R: Toggle viewing all loaded chunks\n" +#~ "- I: Inventory menu\n" +#~ "- ESC: This menu\n" +#~ "- T: Chat\n" +#~ msgstr "" +#~ "Algsed nupusätted:\n" +#~ "- WASD: Kõnni\n" +#~ "- Vasak hiireklõps: Kaeva/löö\n" +#~ "- Parem hiireklõps: Aseta/kasuta\n" +#~ "- Hiireratas: Vali asi\n" +#~ "- 0...9: Vali asi\n" +#~ "- Shift: Hiili\n" +#~ "- R: Vaheta nägemiskaugust\n" +#~ "- I: Seljakott\n" +#~ "- ESC: Menüü\n" +#~ "- T: Jututupa\n" + +#~ msgid "" +#~ "Warning: Some configured mods are missing.\n" +#~ "Their setting will be removed when you save the configuration. " +#~ msgstr "" +#~ "Hoiatus: Mõned konfigureeritud modifikatsioonid on kaotsi läinud.\n" +#~ "Nende sätted kustutatakse kui salvestada konfiguratsioon." + +#~ msgid "" +#~ "Warning: Some mods are not configured yet.\n" +#~ "They will be enabled by default when you save the configuration. " +#~ msgstr "" +#~ "Hoiatus: Mõned modifikatsioonid pole sätitud veel.\n" +#~ "Need lülitatakse sisse kohe pärast sätete salvestamist." + +#~ msgid "Exit to OS" +#~ msgstr "Välju mängust" + +#~ msgid "Exit to Menu" +#~ msgstr "Välju menüüsse" + +#~ msgid "Sound Volume" +#~ msgstr "Hääle volüüm" + +#~ msgid "Change Password" +#~ msgstr "Vaheta parooli" + +#~ msgid "Continue" +#~ msgstr "Jätka" + +#~ msgid "You died." +#~ msgstr "Sa surid." + +#, fuzzy +#~ msgid "Finite Liquid" +#~ msgstr "Löppev vedelik" + +#~ msgid "Preload item visuals" +#~ msgstr "Lae asjade visuaale" + +#~ msgid "SETTINGS" +#~ msgstr "Seaded" + +#~ msgid "Password" +#~ msgstr "Parool" + +#~ msgid "Name" +#~ msgstr "Nimi" + +#~ msgid "Favorites:" +#~ msgstr "Lemmikud:" + +#, fuzzy +#~ msgid "<<-- Add mod" +#~ msgstr "<<-- Lisama muutus" + +#, fuzzy +#~ msgid "Remove selected mod" +#~ msgstr "Eemalda valitud muutus" + +#~ msgid "EDIT GAME" +#~ msgstr "MUUDA MÄNGU" + +#~ msgid "new game" +#~ msgstr "uus mängu" + +#, fuzzy +#~ msgid "edit game" +#~ msgstr "Muuda mängu" + +#~ msgid "Games" +#~ msgstr "Mängud" + +#~ msgid "GAMES" +#~ msgstr "MÄNGUD" + +#, fuzzy +#~ msgid "Game Name" +#~ msgstr "Mäng" diff --git a/po/fr/minetest.po b/po/fr/minetest.po new file mode 100644 index 0000000..8b65af0 --- /dev/null +++ b/po/fr/minetest.po @@ -0,0 +1,1212 @@ +# French translations for minetest-c55 package. +# Copyright (C) 2011 celeron +# This file is distributed under the same license as the minetest-c55 package. +# Cyriaque 'Cisoun' Skrapits , 2011 +# +msgid "" +msgstr "" +"Project-Id-Version: 0.0.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2014-12-14 17:34+0100\n" +"Last-Translator: Calinou \n" +"Language-Team: Français <>\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 1.7-dev\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "OK" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "Sélectionner un monde :" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "Cacher le jeu" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "Cacher le contenu de packs de mods" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "Mod :" + +#: builtin/mainmenu/dlg_config_world.lua:48 +msgid "Depends:" +msgstr "Dépend de :" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Enregistrer" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Annuler" + +#: builtin/mainmenu/dlg_config_world.lua:68 +msgid "Enable MP" +msgstr "Activer le pack de mods" + +#: builtin/mainmenu/dlg_config_world.lua:70 +msgid "Disable MP" +msgstr "Désactiver le pack de mods" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "activé" + +#: builtin/mainmenu/dlg_config_world.lua:82 +msgid "Enable all" +msgstr "Tout activer" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Nom du monde" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "Graine" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "Générateur de carte" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Jeu" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Créer" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "Vous n'avez pas de sous-jeux installés." + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "Avertissement : le jeu minimal est fait pour les développeurs." + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "Téléchargez un sosu-jeu, comme minetest_game, depuis minetest.net" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "Le monde \"$1\" existe déjà" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "Nom du monde manquant ou aucun jeu sélectionné" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "Êtes-vous sûr de supprimer \"$1\" ?" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Oui" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "Non, bien sûr que non !" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "Modmgr : n'a pas pu supprimer \"$1\"" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "Modmgr : chemin de mod invalide \"$1\"" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "Supprimer le monde \"$1\" ?" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Non" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "Renommer le pack de mods :" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "Accepter" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "Installer un mod : fichier : \"$1\"" + +#: builtin/mainmenu/modmgr.lua:343 +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" +"\n" +"Installer un mod : type de fichier non supporté \"$1\" ou archive cassée" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "N'a pas pu installer $1 à $2" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" +"Installer un mod : impossible de trouver un nom de dossier valide pour le " +"pack de mods $1" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "Installer un mod : impossible de trouver le vrai nom du mod pour : $1" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "Non trié" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "Rechercher" + +#: builtin/mainmenu/store.lua:125 +msgid "Downloading" +msgstr "Télécharement" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "veuillez patienter..." + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "Installé avec succès :" + +#: builtin/mainmenu/store.lua:163 +msgid "Shortname:" +msgstr "Nom court :" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "ok" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "Note" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "Réinstaller" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "Installer" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "Fermer le store" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "Page $1 sur $2" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Crédits" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "Développeurs principaux" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "Contributeurs actifs" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "Anciens contributeurs" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "Mods installés :" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "Dépôt de mods en ligne" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "Pas de description disponible" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "Information du mod :" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "Renommer" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "Désinstaller le pack de mods sélectionné" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "Désinstaller le mod sélectionné" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "Sélectionner un fichier de mod :" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "Mods" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "Adresse / Port" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Nom / Mot de passe" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "Liste de serveurs publics" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Supprimer" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Rejoindre" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "Client" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Nouveau" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Configurer" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "Démarrer" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Sélectionner un monde :" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Mode créatif" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Activer les dégâts" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Public" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "Adresse à assigner" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "Port" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "Port du serveur" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "Serveur" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "Êtes-vous sûr de remettre à zéro votre monde solo ?" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "Non !" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "Lumière douce" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "Activer les particules" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "Nuages 3D" + +#: builtin/mainmenu/tab_settings.lua:140 + +msgid "Fancy Trees" +msgstr "Arbres détaillés" + +#: builtin/mainmenu/tab_settings.lua:142 +msgid "Opaque Water" +msgstr "Eau opaque" + +#: builtin/mainmenu/tab_settings.lua:144 + +msgid "Connected Glass" +msgstr "Verre connecté" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "Redémarrez Minetest pour que le changement de pilote prenne effet" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "Mip-mapping" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "Filtrage anisotrope" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "Filtrage bilinéaire" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "Filtrage trilinéaire" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "Shaders" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "Changer les touches" + +#: builtin/mainmenu/tab_settings.lua:167 +#, fuzzy +msgid "Reset singleplayer world" +msgstr "Remettre le monde solo à zéro" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "Échelle des menus" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "Échelle appliquée aux menus :" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +msgid "Bumpmapping" +msgstr "Bump mapping" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "Générer des normal maps" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "Occlusion parallaxe" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "Liquides animés" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "Feuilles animées" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "Plantes animées" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "Pour activer les shaders, le pilote OpenGL doit être utilisé." + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "Réglages" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "Voler" + +#: builtin/mainmenu/tab_simple_main.lua:71 +msgid "Start Singleplayer" +msgstr "Démarrer la partie solo" + +#: builtin/mainmenu/tab_simple_main.lua:72 + +msgid "Config mods" +msgstr "Configurer les mods" + +#: builtin/mainmenu/tab_simple_main.lua:191 +msgid "Main" +msgstr "Menu principal" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Jouer" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Solo" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "Sélectionner un pack de textures :" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "Pas d'information disponible" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +msgid "Texturepacks" +msgstr "Packs de textures" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "Textures d'objets..." + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "needs_fallback_font" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "Réapparaître" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "Définitions d'objets..." + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "Définitions des blocs..." + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "Média..." + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr " Ko/s" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr " Mo/s" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Voir debug.txt pour plus d'information." + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "Procéder" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "Entrer " + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "Raccourcis" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "\"Use\" = descendre (escalade)" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "Double appui sur \"saut\" pour voler" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "Touche déjà utilisée" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "appuyez sur une touche" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Avancer" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Reculer" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Gauche" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Droite" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Utiliser" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Sauter" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Marcher" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Lâcher" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Inventaire" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Messagerie" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Commande" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Console" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Voler" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Mode rapide" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "Mode noclip" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "Distance de rendu" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "Imprimer stacks" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "Ancien mot de passe" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "Nouveau mot de passe" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "Confirmer mot de passe" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "Changer" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "Les mots de passe ne correspondent pas !" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "Volume du son :" + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Quitter" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Bouton gauche" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Bouton du milieu" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Bouton droit" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "Bouton X 1" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "Retour en arrière" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "Vider" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "Retour" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tabulation" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "Bouton X 2" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "Verr. maj." + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Contrôle" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Kana" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Menu" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Pause" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Majuscule" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "Convertir" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Échap" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "Final" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Kanji" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "Nonconvert" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "Fin" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Origine" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "Changer de mode" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Suivant" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Précédent" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Espace" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Bas" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Exécuter" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Imprimer" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Sélectionner" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Haut" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Aide" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Insérer" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "Capture d'écran" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "Windows gauche" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "Applications" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Pavé num. 0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Pavé num. 1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "Windows droite" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Mise en veille" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Pavé num. 2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Pavé num. 3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Pavé num. 4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Pavé num. 5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Pavé num. 6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Pavé num. 7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Pavé num. *" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Pavé num. +" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Pavé num. -" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Pavé num. /" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Pavé num. 8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Pavé num. 9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Verr. pavé num." + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Verr. défilement" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Majuscule gauche" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Majuscule droite" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Contrôle gauche" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Menu gauche" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Contrôle droite" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Menu droite" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Virgule" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Moins" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "Point" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Plus" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "Attente" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "Vider sélection" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "Écraser l'OEF" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "ExSel" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "OEM Clear" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PA1" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "Zoomer" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "Menu principal" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "Nom du joueur trop long." + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "Erreur de connexion (perte de connexion ?)" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "Pas de monde sélectionné et pas d'adresse fournie. Rien à faire." + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "Le chemin du monde spécifié n'existe pas :" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "Le jeu \" n'a pas pu être trouvé" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "gamespec invalide." + +#~ msgid "Left click: Move all items, Right click: Move single item" +#~ msgstr "" +#~ "Clic gauche : déplacer tous les objets -- Clic droit : déplacer un objet" + +#~ msgid "is required by:" +#~ msgstr "est requis par :" + +#~ msgid "Configuration saved. " +#~ msgstr "Configuration enregistrée. " + +#~ msgid "Warning: Configuration not consistent. " +#~ msgstr "Attention : configuration incorrecte. " + +#~ msgid "Cannot create world: Name contains invalid characters" +#~ msgstr "" +#~ "Impossible de créer le monde : le nom contient des caractères invalides" + +#~ msgid "Multiplayer" +#~ msgstr "Multijoueur" + +#~ msgid "Advanced" +#~ msgstr "Avancé" + +#~ msgid "Show Public" +#~ msgstr "Voir les serveurs publics" + +#~ msgid "Show Favorites" +#~ msgstr "Voir les serveurs favoris" + +#~ msgid "Leave address blank to start a local server." +#~ msgstr "Laisser l'adresse vide pour lancer un serveur local." + +#~ msgid "Create world" +#~ msgstr "Créer un monde" + +#~ msgid "Address required." +#~ msgstr "Adresse requise." + +#~ msgid "Cannot delete world: Nothing selected" +#~ msgstr "Impossible de supprimer le monde : rien n'est sélectionné" + +#~ msgid "Files to be deleted" +#~ msgstr "Fichiers à supprimer" + +#~ msgid "Cannot create world: No games found" +#~ msgstr "Impossible de créer le monde : aucun jeu n'est présent" + +#~ msgid "Cannot configure world: Nothing selected" +#~ msgstr "Impossible de configurer ce monde : aucune sélection active" + +#~ msgid "Failed to delete all world files" +#~ msgstr "Tous les fichiers du monde n'ont pu être supprimés" + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: Walk\n" +#~ "- Mouse left: dig/hit\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- 0...9: select item\n" +#~ "- Shift: sneak\n" +#~ "- R: Toggle viewing all loaded chunks\n" +#~ "- I: Inventory menu\n" +#~ "- ESC: This menu\n" +#~ "- T: Chat\n" +#~ msgstr "" +#~ "Touches par défaut :\n" +#~ "- ZQSD : se déplacer\n" +#~ "- Clic gauche : creuser bloc\n" +#~ "- Clic droite : insérer bloc\n" +#~ "- Roulette : sélectionner objet\n" +#~ "- 0...9 : sélectionner objet\n" +#~ "- Shift : déplacement prudent\n" +#~ "- R : active la vue de tous les blocs\n" +#~ "- I : inventaire\n" +#~ "- Échap : ce menu\n" +#~ "- T : discuter\n" + +#~ msgid "Delete map" +#~ msgstr "Supprimer la carte" + +#~ msgid "" +#~ "Warning: Some configured mods are missing.\n" +#~ "Their setting will be removed when you save the configuration. " +#~ msgstr "" +#~ "Attention : certains mods configurés sont introuvables.\n" +#~ "Leurs réglages seront effacés quand vous enregistrerez la configuration. " + +#~ msgid "" +#~ "Warning: Some mods are not configured yet.\n" +#~ "They will be enabled by default when you save the configuration. " +#~ msgstr "" +#~ "Attention : certains mods ne sont pas encore configurés.\n" +#~ "Ils seront activés par défaut quand vous enregistrerez la configuration. " + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: move\n" +#~ "- Space: jump/climb\n" +#~ "- Shift: sneak/go down\n" +#~ "- Q: drop item\n" +#~ "- I: inventory\n" +#~ "- Mouse: turn/look\n" +#~ "- Mouse left: dig/punch\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- T: chat\n" +#~ msgstr "" +#~ "Contrôles:\n" +#~ "- ZQSD : se déplacer\n" +#~ "- Espace : sauter/grimper\n" +#~ "- Maj. : marcher prudemment/descendre\n" +#~ "- A : lâcher l'objet en main\n" +#~ "- I : inventaire\n" +#~ "- Souris : tourner/regarder\n" +#~ "- Souris gauche : creuser/attaquer\n" +#~ "- Souris droite : placer/utiliser\n" +#~ "- Molette souris : sélectionner objet\n" +#~ "- T : discuter\n" + +#~ msgid "Exit to OS" +#~ msgstr "Quitter le jeu" + +#~ msgid "Exit to Menu" +#~ msgstr "Quitter vers le menu" + +#~ msgid "Sound Volume" +#~ msgstr "Volume du son" + +#~ msgid "Change Password" +#~ msgstr "Changer mot de passe" + +#~ msgid "Continue" +#~ msgstr "Continuer" + +#~ msgid "You died." +#~ msgstr "Vous êtes mort." + +#~ msgid "Shutting down stuff..." +#~ msgstr "Quitter le jeu..." + +#~ msgid "Connecting to server..." +#~ msgstr "Connexion au serveur..." + +#~ msgid "Resolving address..." +#~ msgstr "Résolution de l'adresse..." + +#~ msgid "Creating client..." +#~ msgstr "Création du client..." + +#~ msgid "Creating server...." +#~ msgstr "Création du serveur..." + +#~ msgid "Loading..." +#~ msgstr "Chargement..." + +#~ msgid "Local install" +#~ msgstr "Installation locale" + +#~ msgid "Add mod:" +#~ msgstr "Ajouter un mod :" + +#~ msgid "MODS" +#~ msgstr "MODS" + +#~ msgid "TEXTURE PACKS" +#~ msgstr "PACKS DE TEXTURES" + +#~ msgid "SINGLE PLAYER" +#~ msgstr "PARTIE SOLO" + +#~ msgid "Finite Liquid" +#~ msgstr "Liquides limités" + +#~ msgid "Preload item visuals" +#~ msgstr "Précharger les objets" + +#~ msgid "SETTINGS" +#~ msgstr "PARAMÈTRES" + +#~ msgid "Password" +#~ msgstr "Mot de passe" + +#~ msgid "Name" +#~ msgstr "Nom" + +#~ msgid "START SERVER" +#~ msgstr "DÉMARRER LE SERVEUR" + +#~ msgid "Favorites:" +#~ msgstr "Favoris :" + +#~ msgid "CLIENT" +#~ msgstr "CLIENT" + +#~ msgid "<<-- Add mod" +#~ msgstr "<<-- Ajouter un mod" + +#~ msgid "Remove selected mod" +#~ msgstr "Supprimer le mod sélectionné" + +#~ msgid "EDIT GAME" +#~ msgstr "MODIFIER LE JEU" + +#~ msgid "new game" +#~ msgstr "nouveau jeu" + +#~ msgid "edit game" +#~ msgstr "éditer le jeu" + +#~ msgid "Mods:" +#~ msgstr "Mods :" + +#~ msgid "Games" +#~ msgstr "Jeux" + +#~ msgid "GAMES" +#~ msgstr "JEUX" + +#~ msgid "Gamemgr: Unable to copy mod \"$1\" to game \"$2\"" +#~ msgstr "Gamemgr : Impossible de copier le mod \"$1\" dans le jeu \"$2\"" + +#~ msgid "Game Name" +#~ msgstr "Nom du jeu" diff --git a/po/hu/minetest.po b/po/hu/minetest.po new file mode 100644 index 0000000..d49bc78 --- /dev/null +++ b/po/hu/minetest.po @@ -0,0 +1,1135 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2013-08-11 03:05+0200\n" +"Last-Translator: Sasikaa Lacikaa \n" +"Language-Team: LANGUAGE \n" +"Language: hu\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Weblate 1.4-dev\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:26 +#, fuzzy +msgid "World:" +msgstr "Világ kiválasztása:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +#, fuzzy +msgid "Hide Game" +msgstr "Játék" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:48 +#, fuzzy +msgid "Depends:" +msgstr "Attól függ:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Mentés" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Mégse" + +#: builtin/mainmenu/dlg_config_world.lua:68 +#, fuzzy +msgid "Enable MP" +msgstr "Összes engedélyezve" + +#: builtin/mainmenu/dlg_config_world.lua:70 +#, fuzzy +msgid "Disable MP" +msgstr "Összes tiltva" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "Engedélyezve" + +#: builtin/mainmenu/dlg_config_world.lua:82 +#, fuzzy +msgid "Enable all" +msgstr "Összes engedélyezve" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Világ neve" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Játék" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Létrehozás" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +#, fuzzy +msgid "A world named \"$1\" already exists" +msgstr "Nem sikerült a viág létrehozása: A világ neve már használva van" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Igen" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +#, fuzzy +msgid "Delete World \"$1\"?" +msgstr "Világ törlése" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Nem" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "Elfogadva" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:343 +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:363 +#, fuzzy +msgid "Failed to install $1 to $2" +msgstr "A világ betöltése közben hiba" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +#, fuzzy +msgid "Downloading" +msgstr "Le" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +#, fuzzy +msgid "Shortname:" +msgstr "Világ neve" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Stáblista" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:121 +#, fuzzy +msgid "Select Mod File:" +msgstr "Világ kiválasztása:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "Cím/Port" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Név/jelszó" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +#, fuzzy +msgid "Public Serverlist" +msgstr "Publikus Szerver Lista:" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Törlés" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Csatlakozás" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Új" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Beállítás" + +#: builtin/mainmenu/tab_server.lua:29 +#, fuzzy +msgid "Start Game" +msgstr "Játék indítása / Csatlakozás" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Világ kiválasztása:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Kreatív mód" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Sérülés engedélyezése" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Publikus" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "Simított megvilágítás" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "Részecskék engedélyezése" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "3D Felhők" + +#: builtin/mainmenu/tab_settings.lua:140 +#, fuzzy +msgid "Fancy Trees" +msgstr "Szép fák" + +#: builtin/mainmenu/tab_settings.lua:142 +#, fuzzy +msgid "Opaque Water" +msgstr "Átlátszó víz" + +#: builtin/mainmenu/tab_settings.lua:144 +#, fuzzy +msgid "Connected Glass" +msgstr "Csatlakozás" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "Mip-mapping" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "Anzisztrópikus szűrés" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "Bi-Linear Szűrés" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "Tri-Linear Szűrés" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "Shaderek" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "Gomb választása" + +#: builtin/mainmenu/tab_settings.lua:167 +#, fuzzy +msgid "Reset singleplayer world" +msgstr "Egyjátékos mód" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +#, fuzzy +msgid "Bumpmapping" +msgstr "Mip-mapping" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "Beállítások" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +#, fuzzy +msgid "Start Singleplayer" +msgstr "Egyjátékos mód" + +#: builtin/mainmenu/tab_simple_main.lua:72 +#, fuzzy +msgid "Config mods" +msgstr "Beállítás" + +#: builtin/mainmenu/tab_simple_main.lua:191 +#, fuzzy +msgid "Main" +msgstr "Fő menü" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Játék" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Egyjátékos mód" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +msgid "Texturepacks" +msgstr "" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "Tárgy textúrák..." + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "Újra éledés" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "" + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "" + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "" + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "Elfogadva" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" +"Gombkiosztás. (Amíg ez a menü fejlesztve van, el van távolítva a minetest." +"conf-ból)" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "\"Használat\" = Lemászás" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "Duplán nyomd meg az \"ugrás\" gombot ahhoz hogy repülj" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "A gomb már használatban van" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "Nyomj meg egy gombot" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Vissza" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Előre" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Bal" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Jobb" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Használni" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Ugrás" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Lopakodás" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Dobás" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Invertory" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Beszélgetés" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Parancs" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Konzol" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Repülés bekapcsolása" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Gyorsaság bekapcsolása" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "váltás noclip-re" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "Távolság választása" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "Stacks nyomtatása" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "Régi jelszó" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "Új jelszó" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "Jelszó visszaigazolás" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "Változtat" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "Nem eggyeznek a jelszavak!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "Hangerő: " + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Kilépés" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Bal gomb" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Középső gomb" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Jobb gomb" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "X gomb 1" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "Vissza" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "Törlés" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "Enter" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tabulátor" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "X Gomb 2" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "Kapital" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Controll" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Kanaa" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Menü" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Pillanat-álj" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "váltás" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "Konvertálás" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Kilépés" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "Befejezés" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junjaa" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Kanjii" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "Nem konvertált" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "Vége" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Otthon" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "Mód váltás" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Következő" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Priorr" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Hely" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Le" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Tömörít" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Nyomtat" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Kiválaszt" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Fel" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Segítség" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Beilleszt" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "Verzió" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "Bal Windows" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "Prog" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Numerikus billentyű 0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Numerikus billentyű 1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "Jobb Windows" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Alvás" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Numerikus billentyű 2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Numerikus billentyű 3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Numerikus billentyű 4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Numerikus billentyű 5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Numerikus billentyű 6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Numerikus billentyű 7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Numerikus billentyű *" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Numerikus billentyű +" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Numerikus billentyű -" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Numerikus billentyű /" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Numerikus billentyű 8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Numerikus billentyű 9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Numerikus lock" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Skroll Lock" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Bal Shift" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Jobb Shift" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Bal Controll" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Bal menü" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Jobb Controll" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Jobb menü" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Vessző" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Minusz" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "tizedes" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Plusz" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "Attnn" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "CrSell" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "Erase OEFF" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "ExSell" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "OEM Clearr" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PA11" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "Nagyítás" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "Fő menü" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "Csatlakozási hiba (Idő lejárt?)" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "Nincs kiválasztott világ, nincs cím. Nincs mit tenni." + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "Nem található, vagy nem betöltött játék \"" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "Nem valós játék spec." + +#~ msgid "Left click: Move all items, Right click: Move single item" +#~ msgstr "Ball gomb: Tárgyak mozgatása, Jobb gomb: egy tárgyat mozgat" + +#~ msgid "is required by:" +#~ msgstr "kell neki:" + +#~ msgid "Configuration saved. " +#~ msgstr "Beállítások mentve. " + +#~ msgid "Warning: Configuration not consistent. " +#~ msgstr "Figyelem: A beállítások nem egyformák. " + +#~ msgid "Cannot create world: Name contains invalid characters" +#~ msgstr "Nem sikerült a világ létrehozása: A névben nem jó karakterek vannak" + +#~ msgid "Multiplayer" +#~ msgstr "Többjátékos mód" + +#~ msgid "Advanced" +#~ msgstr "Haladó" + +#~ msgid "Show Public" +#~ msgstr "Publikus mutatása" + +#~ msgid "Show Favorites" +#~ msgstr "Kedvencek mutatása" + +#~ msgid "Leave address blank to start a local server." +#~ msgstr "Hagyd el a nevét, hogy helyi szervert indíts." + +#~ msgid "Create world" +#~ msgstr "Világ létrehozása" + +#~ msgid "Address required." +#~ msgstr "Cím szükséges." + +#~ msgid "Cannot delete world: Nothing selected" +#~ msgstr "Nem törölhető a világ: Nincs kiválasztva" + +#~ msgid "Files to be deleted" +#~ msgstr "A fájl törölve lett" + +#~ msgid "Cannot create world: No games found" +#~ msgstr "Nem sikerült a világot létrehozni: Nem található a játék" + +#~ msgid "Cannot configure world: Nothing selected" +#~ msgstr "Nem sikerült a világ beállítása: Nincs kiválasztva" + +#~ msgid "Failed to delete all world files" +#~ msgstr "Hiba az összes világ törlése közben" + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: move\n" +#~ "- Space: jump/climb\n" +#~ "- Shift: sneak/go down\n" +#~ "- Q: drop item\n" +#~ "- I: inventory\n" +#~ "- Mouse: turn/look\n" +#~ "- Mouse left: dig/punch\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- T: chat\n" +#~ msgstr "" +#~ "Alap beállítások:\n" +#~ "- WASD: Mozgás\n" +#~ "- Space: Ugrás/Mászás\n" +#~ "- Shift: Lopakodás/Lefele menés\n" +#~ "- Q: Tárgyak eldobása\n" +#~ "- I: Invertory\n" +#~ "- Egér: Forgás/Nézelődés\n" +#~ "- Egér Bal-gomb: Ásás/ütés\n" +#~ "- Egér jobb-gomb: elhejezés/használat\n" +#~ "- Egér görgő: Tárgyak kiválasztása\n" +#~ "- T: Beszélgetés\n" + +#~ msgid "Exit to OS" +#~ msgstr "Kilépés az OP-rendszerbe" + +#~ msgid "Exit to Menu" +#~ msgstr "Kilépés a menübe" + +#~ msgid "Sound Volume" +#~ msgstr "Hangerő" + +#~ msgid "Change Password" +#~ msgstr "Jelszó változtatás" + +#~ msgid "Continue" +#~ msgstr "Tovább" + +#~ msgid "You died." +#~ msgstr "Meghaltál." + +#~ msgid "Shutting down stuff..." +#~ msgstr "Dolog leállítása..." + +#~ msgid "Connecting to server..." +#~ msgstr "Csatlakozás a szerverhez..." + +#~ msgid "Resolving address..." +#~ msgstr "Cím létrehozása..." + +#~ msgid "Creating client..." +#~ msgstr "Kliens létrehozása..." + +#~ msgid "Creating server...." +#~ msgstr "Szerver létrehozása..." + +#~ msgid "Loading..." +#~ msgstr "Betöltés..." + +#, fuzzy +#~ msgid "Finite Liquid" +#~ msgstr "Végtelen folyadék" + +#~ msgid "Preload item visuals" +#~ msgstr "Előretöltött tárgy láthatóság" + +#, fuzzy +#~ msgid "Password" +#~ msgstr "Régi jelszó" + +#~ msgid "Favorites:" +#~ msgstr "Kedvencek:" + +#, fuzzy +#~ msgid "Games" +#~ msgstr "Játék" + +#, fuzzy +#~ msgid "Game Name" +#~ msgstr "Játék" diff --git a/po/id/minetest.po b/po/id/minetest.po new file mode 100644 index 0000000..2136197 --- /dev/null +++ b/po/id/minetest.po @@ -0,0 +1,999 @@ +# Indonesian translation for minetest-c55 package. +# Copyright (C) 2014 srifqi +# This file is distributed under the same license as the PACKAGE package. +# Muhammad Rifqi Priyo Susanto , 2014. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2015-03-02 00:21+0700\n" +"Last-Translator: Muhammad Rifqi Priyo Susanto " +"\n" +"Language-Team: Bahasa Indonesia <>\n" +"Language: id\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "Oke" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "Dunia:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "" +"Sembunyikan\n" +"permainan" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "" +"Sembunyikan\n" +"konten pm" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "Mod:" + +#: builtin/mainmenu/dlg_config_world.lua:48 +msgid "Depends:" +msgstr "Bergantung pada:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Simpan" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Batal" + +#: builtin/mainmenu/dlg_config_world.lua:68 +msgid "Enable MP" +msgstr "Aktifkan PM" + +#: builtin/mainmenu/dlg_config_world.lua:70 +msgid "Disable MP" +msgstr "Nonaktifkan PM" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "diaktifkan" + +#: builtin/mainmenu/dlg_config_world.lua:82 +msgid "Enable all" +msgstr "Aktifkan semua" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Nama dunia" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "Seed" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "Generator peta" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Permainan" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Buat" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "Kamu tidak punya sub-permainan terpasang" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "Unduh satu dari minetest.net" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "Peringatan: minimal development test ditujukan untuk pengembang." + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "Unduh sebuah sub-permainan, seperti minetest_game, dari minetest.net" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "Dunia bernama \"$1\" telah ada" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "Tidak ada nama atau permainan yang dipilih" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "Kamu yakin ingin menghapus \"$1\"?" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Ya" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "Tentu tidak!" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "Pengelola mod: gagal untuk menghapus \"$1\"" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "Pengelola mod: jalur mod tidak sah" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "Hapus Dunia \"$1\"?" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Tidak" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "Ganti Nama Paket Mod:" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "Setuju" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "Pemasangan Mod: berkas: \"$1\"" + +#: builtin/mainmenu/modmgr.lua:343 +#, fuzzy +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" +"\n" +"Pemasangan Mod: tipe berkas tidak didukung \"$1\" atau kerusakan arsip" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "Gagal untuk memasang $1 ke $2" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" +"Pemasangan Mod: tidak dapat mencari nama folder yang sesuai untuk paket mod " +"$1" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "Pemasangan Mod: tidak dapat mencari nama yang sebenarnya dari: $1" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "Tidak diurutkan" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "Cari" + +#: builtin/mainmenu/store.lua:125 +msgid "Downloading" +msgstr "Mengunduh" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "mohon tunggu..." + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "Berhasil dipasang:" + +#: builtin/mainmenu/store.lua:163 +#, fuzzy +msgid "Shortname:" +msgstr "Nama pendek:" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "oke" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "Peringkat" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "Pasang ulang" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "Pasang" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "Tutup toko" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "Halaman $1 dari $2" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Penghargaan" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "Pengembang Inti" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "Kontributor Aktif" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "Kontributor Sebelumnya" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "Mod Terpasang:" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "Gudang mod daring" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "Tidak ada deskripsi mod tersedia" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "Informasi mod:" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "Ganti Nama" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "Copot pemasangan paket mod terpilih" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "Copot pemasangan mod terpilih" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "Pilih Berkas Mod:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "Mod" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "Alamat/Port" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Nama/Kata sandi" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "Daftar Server Publik" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Hapus" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Sambung" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "Klien" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Baru" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Konfigurasi" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "Mulai Permainan" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Pilih Dunia:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Mode Kreatif" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Aktifkan Kerusakan" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Publik" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "Port" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "Port Server" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "Server" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "Yakin ingin mengaturulang dunia anda?" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "Tidak!!!" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "Pencahayaan Halus" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "Aktifkan Partikel" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "Awan 3D" + +#: builtin/mainmenu/tab_settings.lua:140 +#, fuzzy +msgid "Fancy Trees" +msgstr "Pohon Indah" + +#: builtin/mainmenu/tab_settings.lua:142 +msgid "Opaque Water" +msgstr "Air Buram" + +#: builtin/mainmenu/tab_settings.lua:144 +#, fuzzy +msgid "Connected Glass" +msgstr "Kaca Tersambung" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "Mulai ulang minetest untuk beralih ke driver yang dipilih" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "Mip-Mapping" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "Anisotropic Filtering" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "Bi-Linear Filtering" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "Tri-Linear Filtering" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "Shaders" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "Ubah tombol" + +#: builtin/mainmenu/tab_settings.lua:167 +#, fuzzy +msgid "Reset singleplayer world" +msgstr "Atur ulang dunia pemain tunggal" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "Skala antarmuka" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "Faktor skala yang diatur untuk elemen menu: " + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "Bebas sentuhan" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "Batas sentuhan (px)" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +#, fuzzy +msgid "Bumpmapping" +msgstr "Bumpmapping" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "Gunakan Normalmaps" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "Parallax Occlusion" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "Air Berombak" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "Daun Melambai" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "Tanaman Berayun" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "Untuk mengaktifkan shaders OpenGL driver harus digunakan." + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "Pengaturan" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "Mode terbang" + +#: builtin/mainmenu/tab_simple_main.lua:71 +#, fuzzy +msgid "Start Singleplayer" +msgstr "Mulai Pemain Tunggal" + +#: builtin/mainmenu/tab_simple_main.lua:72 +#, fuzzy +msgid "Config mods" +msgstr "Konfigurasi mod" + +#: builtin/mainmenu/tab_simple_main.lua:191 +#, fuzzy +msgid "Main" +msgstr "Beranda" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Mainkan" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Pemain Tunggal" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "Pilih paket tekstur:" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "Tidak ada informasi tersedia" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +#, fuzzy +msgid "Texturepacks" +msgstr "Paket Tekstur" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "Tekstur barang..." + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "needs_fallback_font" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "Bangkit" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "Definisi barang..." + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "Definisi node..." + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "Media..." + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr " KB/detik" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr " MB/detik" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Cek debug.txt untuk detail." + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "Lanjut" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "Masuk " + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" +"Pengaturan tombol. (Jika menu ini kacau, hapus pengaturan kontrol dari minetest.conf)" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "\"Pakai\" = turun" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "Tekan ganda \"lompat\" untuk\n" +"beralih terbang" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "Tombol telah terpakai" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "tekan tombol" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Maju" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Mundur" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Kiri" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Kanan" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Pakai" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Lompat" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Menyelinap" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Jatuhkan" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Inventaris" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Obrolan" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Perintah" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Konsol" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Terbang" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Gerak cepat" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "Tembus blok" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "Jarak pandang" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "Cetak tumpukan" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "Kata Sandi Lama" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "Kata Sandi Baru" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "Konfirmasi Kata Sandi" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "Ubah" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "Kata sandi tidak cocok!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "Volume Suara: " + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Keluar" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Klik Kiri" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Klik Tengah" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Klik Kanan" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "Tombol X 1" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "Backspace" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "Bersihkan" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "Enter" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tab" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "Tombol X 2" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "Caps Lock" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Ctrl" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Kana" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Alt" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Pause" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Shift" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "Convert" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Esc" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "Final" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Kanji" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "Nonconvert" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "End" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Home" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "Mode Change" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Page Up" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Page Down" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Spasi" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Turun" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Execute" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Print" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Select" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Atas" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Help" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Insert" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "Snapshot" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "Start Kiri" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "Tombol Menu" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Numpad 0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Numpad 1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "Start Kanan" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Sleep" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Numpad 2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Numpad 3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Numpad 4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Numpad 5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Numpad 6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Numpad 7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Numpad *" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Numpad +" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Numpad -" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Numpad /" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Numpad 8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Numpad 9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Num Lock" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Scroll Lock" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Shift Kiri" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Shift Kanan" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Ctrl Kiri" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Left Menu" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Ctrl Kanan" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Right Menu" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Koma" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Kurang" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "Titik" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Tambah" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "Attn" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "CrSel" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "Erase OEF" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "ExSel" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "OEM Clear" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PA1" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "Zoom" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "Menu Utama" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "Nama pemain terlalu panjang." + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "Koneksi rusak (terlalu lama?)" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "Tidak ada dunia yang dipilih dan tidak ada alamat yang diberikan." + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "Jalur dunia yang diberikan tidak ada: " + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "Tidak dapat mencari atau memuat permainan \"" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "Spesifikasi permainan tidak sah." \ No newline at end of file diff --git a/po/it/minetest.po b/po/it/minetest.po new file mode 100644 index 0000000..ef8ebc5 --- /dev/null +++ b/po/it/minetest.po @@ -0,0 +1,1129 @@ +# Italian translation for Minetest. +# Copyright (C) 2011 Perttu Ahola "celeron55" +# This file is distributed under the same license as the Minetest package. +# Giuseppe Bilotta , 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: Minetest 0.4.9\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2014-06-01 19:05+0100\n" +"Last-Translator: Enki \n" +"Language-Team: \n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.5.4\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "Ok" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "Mondo:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "Nasc. gioco" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "Nasc. cont. pacchetti" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "Mod.:" + +#: builtin/mainmenu/dlg_config_world.lua:48 +msgid "Depends:" +msgstr "Dipendenze:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Salvare" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Annullare" + +#: builtin/mainmenu/dlg_config_world.lua:68 +msgid "Enable MP" +msgstr "Att. pacch." + +#: builtin/mainmenu/dlg_config_world.lua:70 +msgid "Disable MP" +msgstr "Disatt. pacch." + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "attivata" + +#: builtin/mainmenu/dlg_config_world.lua:82 +msgid "Enable all" +msgstr "Attivarli tutti" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Nome del mondo" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "Seme casuale" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "Generat. mappe" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Gioco" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Creare" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "Esiste già un mondo chiamato \"$1\"" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "" +"Non è stato fornito nessun nome del mondo oppure non è stato selezionato " +"nessun gioco" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "Siete certi di volere cancellare \"$1\"?" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Sì" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "No, certo che no!" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "Gestore dei moduli: la cancellazione di \"$1\" è fallita" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "Gestore dei moduli: percorso del modulo \"$1\" non valido" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "Cancellare il mondo \"$1\"?" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "No" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "Rinominare il pacchetto moduli:" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "Accettare" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "Installare un modulo: file: \"$1\"" + +#: builtin/mainmenu/modmgr.lua:343 +#, fuzzy +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" +"\n" +"Installare un modulo: tipo di file non supportato \"$1\"" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "L'installazione di $1 in $2 è fallita" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" +"Installare un modulo: impossibile trovare un nome di cartella appropriato " +"per il pacchetto moduli $1" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "" +"Installare un modulo: impossibile trovare il vero nome del modulo per: $1" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +msgid "Downloading" +msgstr "" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +#, fuzzy +msgid "Shortname:" +msgstr "Nome del mondo" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "Valutazione" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "Reinstallare" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "Installare" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "Pagina $1 di $2" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Riconoscimenti" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "Sviluppatori principali" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "Contributori attivi" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "Contributori precedenti" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "Moduli installati:" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "Archivio in linea" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "Nessuna descrizione disponibile per il modulo" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "Informazioni sul modulo:" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "Rinominare" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "Disinstallare il pacchetto moduli selezionato" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "Disinstallare il modulo selezionato" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "Selezionare il file modulo:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "Moduli" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "Indirizzo/Porta" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Nome/Password" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "Elenco dei server pubblici" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Cancellare" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Connettere" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "Client" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Nuovo" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Configurare" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "Avviare il gioco" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Selezionare il mondo:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Modalità creativa" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Abilitare il danno" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Pubblico" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "Porta del server" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "Server" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "Illuminazione armoniosa" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "Abilitare le particelle" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "Nuvole 3D" + +#: builtin/mainmenu/tab_settings.lua:140 +#, fuzzy +msgid "Fancy Trees" +msgstr "Alberi migliori" + +#: builtin/mainmenu/tab_settings.lua:142 +msgid "Opaque Water" +msgstr "Acqua opaca" + +#: builtin/mainmenu/tab_settings.lua:144 +#, fuzzy +msgid "Connected Glass" +msgstr "Connettere" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "Mip-Mapping" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "Filtro anisotropico" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "Filtro Bi-Lineare" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "Filtro Tri-Lineare" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "Shader" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "Cambiare i tasti" + +#: builtin/mainmenu/tab_settings.lua:167 +#, fuzzy +msgid "Reset singleplayer world" +msgstr "Giocatore singolo" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +#, fuzzy +msgid "Bumpmapping" +msgstr "Mip-Mapping" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "E' necessario usare i driver OpenGL per abilitare gli shader." + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "Impostazioni" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +#, fuzzy +msgid "Start Singleplayer" +msgstr "Giocatore singolo" + +#: builtin/mainmenu/tab_simple_main.lua:72 +#, fuzzy +msgid "Config mods" +msgstr "Configurare" + +#: builtin/mainmenu/tab_simple_main.lua:191 +#, fuzzy +msgid "Main" +msgstr "Menù principale" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Giocare" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Giocatore singolo" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "Selezionare un pacchetto di immagini:" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "Nessuna informazione disponibile" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +#, fuzzy +msgid "Texturepacks" +msgstr "Pacch. immagini" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "Immagini degli oggetti..." + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "needs_fallback_font" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "Riapparire" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "Definizioni degli oggetti..." + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "Definizioni dei cubi..." + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "Media..." + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Controllare debug.txt per i dettagli." + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "Procedere" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" +"Collegamenti dei tasti. (Se questo menù si incasina, rimuovete la roba da " +"minetest.conf)" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "\"Usare\" = scendere" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "Volare On/Off = due volte \"saltare\"" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "Tasto già in uso" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "premere il tasto" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Avanti" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Indietro" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Sinistra" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Destra" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Usare" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Saltare" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Strisciare" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Scartare" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Inventario" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Chat" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Comando" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Console" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Volare On/Off" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Correre On/Off" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "Fantasma On/Off" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "Selez. ad area" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "Stampa stack" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "Vecchia password" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "Nuova password" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "Confermare la password" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "Cambiare" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "Le password non coincidono!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "Volume suono:" + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Uscire" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Tasto sinistro" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Tasto centrale" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Tasto destro" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "Pulsante X 1" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "Backspace" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "Canc" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "Invio" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tab" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "Pulsante X 2" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "Bloc Maiusc" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Control" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Kana" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Menù" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Pausa" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Maiusc" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "Convert" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Esc" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "Fine" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Kanji" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "Nonconvert" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "Fine" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Inizio" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "Cambio di modalità" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Successivo" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Precedente" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Spazio" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Pag. giù" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Eseguire" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Stamp" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Selezionare" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Pag. su" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Aiuto" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Ins" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "Istantanea" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "Finestre a sinistra" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "Applicazioni" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Tast. num. 0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Tast. num. 1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "Finestre a destra" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Sospensione" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Tast. num. 2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Tast. num. 3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Tast. num. 4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Tast. num. 5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Tast. num. 6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Tast. num. 7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Tast. num. *" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Tast. num. +" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Tast. num. -" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Tast. num. /" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Tast. num. 8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Tast. num. 9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Bloc Num" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Bloc Scorr" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Maiusc sx" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Maiusc dx" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Ctrl sx" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Menù a sinistra" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Ctrl dx" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Menù a destra" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Virgola" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Meno" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "Punto" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Più" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "Attn" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "CrSel" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "Erase OEF" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "ExSel" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "OEM Clear" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PA1" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "Zoom" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "Menù principale" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "Errore di connessione (scaduta?)" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "" +"Non è stato selezionato nessun mondo e non è stato fornito nessun indirizzo. " +"Niente da fare." + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "Impossibile trovare o caricare il gioco \"" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "Specifica del gioco non valida." + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: move\n" +#~ "- Space: jump/climb\n" +#~ "- Shift: sneak/go down\n" +#~ "- Q: drop item\n" +#~ "- I: inventory\n" +#~ "- Mouse: turn/look\n" +#~ "- Mouse left: dig/punch\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- T: chat\n" +#~ msgstr "" +#~ "Controlli predefiniti:\n" +#~ "- WASD: muoversi\n" +#~ "- Space: saltare/arrampicarsi\n" +#~ "- Shift: strisciare/scendere\n" +#~ "- Q: scartare l'oggetto\n" +#~ "- I: inventario\n" +#~ "- Mouse: girarsi/guardare\n" +#~ "- Tasto sinistro del mouse: scavare/colpire\n" +#~ "- Tasto destro del mouse: posizionare/usare\n" +#~ "- Rotella del mouse: scegliere l'oggetto\n" +#~ "- T: chat\n" + +#~ msgid "Exit to OS" +#~ msgstr "Tornare al S.O." + +#~ msgid "Exit to Menu" +#~ msgstr "Tornare al menù" + +#~ msgid "Sound Volume" +#~ msgstr "Volume del suono" + +#~ msgid "Change Password" +#~ msgstr "Cambiare la password" + +#~ msgid "Continue" +#~ msgstr "Continuare" + +#~ msgid "You died." +#~ msgstr "Siete morti." + +#~ msgid "Shutting down stuff..." +#~ msgstr "Spegnimento della roba..." + +#~ msgid "Connecting to server..." +#~ msgstr "Connessione al server..." + +#~ msgid "Resolving address..." +#~ msgstr "Risoluzione dell'indirizzo..." + +#~ msgid "Creating client..." +#~ msgstr "Creazione del client..." + +#~ msgid "Creating server...." +#~ msgstr "Creazione del server..." + +#~ msgid "Loading..." +#~ msgstr "Caricamento..." + +#~ msgid "Local install" +#~ msgstr "Installazione locale" + +#~ msgid "Add mod:" +#~ msgstr "Aggiungere un modulo:" + +#~ msgid "MODS" +#~ msgstr "MODULI" + +#~ msgid "TEXTURE PACKS" +#~ msgstr "PACCH. DI IMM." + +#~ msgid "SINGLE PLAYER" +#~ msgstr "GIOC. SING." + +#~ msgid "Finite Liquid" +#~ msgstr "Liquido limitato" + +#~ msgid "Preload item visuals" +#~ msgstr "Precaricare le immagini" + +#~ msgid "SETTINGS" +#~ msgstr "IMPOSTAZIONI" + +#~ msgid "Password" +#~ msgstr "Password" + +#~ msgid "Name" +#~ msgstr "Nome" + +#~ msgid "START SERVER" +#~ msgstr "AVVIO SERVER" + +#~ msgid "Favorites:" +#~ msgstr "Preferiti:" + +#~ msgid "CLIENT" +#~ msgstr "CLIENT" + +#~ msgid "<<-- Add mod" +#~ msgstr "<<-- Aggiungere il modulo" + +#~ msgid "Remove selected mod" +#~ msgstr "Rimuovere il modulo selezionato" + +#~ msgid "EDIT GAME" +#~ msgstr "MODIFICARE IL GIOCO" + +#~ msgid "new game" +#~ msgstr "nuovo gioco" + +#~ msgid "edit game" +#~ msgstr "modificare il gioco" + +#~ msgid "Mods:" +#~ msgstr "Moduli:" + +#~ msgid "Games" +#~ msgstr "Giochi" + +#~ msgid "GAMES" +#~ msgstr "GIOCHI" + +#~ msgid "Gamemgr: Unable to copy mod \"$1\" to game \"$2\"" +#~ msgstr "Gestore del gioco: impossibile il modulo \"$1\" nel gioco \"$2\"" + +#~ msgid "Game Name" +#~ msgstr "Nome del gioco" diff --git a/po/ja/minetest.po b/po/ja/minetest.po new file mode 100644 index 0000000..8dba0dc --- /dev/null +++ b/po/ja/minetest.po @@ -0,0 +1,856 @@ +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-02-14 23:12+0900\n" +"PO-Revision-Date: 2015-02-22 10:57+0900\n" +"Last-Translator: Rui Takeda \n" +"Language-Team: Japanese \n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Poedit 1.6.5\n" + +msgid "Ok" +msgstr "決定" + +msgid "World:" +msgstr "ワールド:" + +msgid "Hide Game" +msgstr "ゲームを隠す" + +msgid "Hide mp content" +msgstr "Modパックの簡略化" + +msgid "Mod:" +msgstr "Mod名:" + +msgid "Depends:" +msgstr "依存Mod:" + +msgid "Save" +msgstr "保存" + +msgid "Cancel" +msgstr "キャンセル" + +msgid "Enable MP" +msgstr "有効化" + +msgid "Disable MP" +msgstr "無効化" + +msgid "enabled" +msgstr "有効化" + +msgid "Enable all" +msgstr "すべて有効化" + +msgid "World name" +msgstr "ワールド名" + +msgid "Seed" +msgstr "Seed値" + +msgid "Mapgen" +msgstr "ワールドタイプ" + +msgid "Game" +msgstr "ゲーム" + +msgid "Create" +msgstr "作成" + +msgid "You have no subgames installed." +msgstr "ゲームがインストールされていません。" + +msgid "Download one from minetest.net" +msgstr "minetest.netから再ダウンロードしてください。" + +msgid "Warning: The minimal development test is meant for developers." +msgstr "警告:Minimal development testは開発者のためのゲームです。" + +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "minetest.netからminetest_gameのゲームをダウンロードしてください。" + +msgid "A world named \"$1\" already exists" +msgstr "ワールド名\"$1\"はすでに使用されています。" + +msgid "No worldname given or no game selected" +msgstr "ワールド名が入力されていないか、ゲームが選択されていません。" + +msgid "Are you sure you want to delete \"$1\"?" +msgstr "本当に\"$1\"を削除してよろしいですか?" + +msgid "Yes" +msgstr "はい" + +msgid "No of course not!" +msgstr "いいえ" + +msgid "Modmgr: failed to delete \"$1\"" +msgstr "Modマネージャー:\"$1\"の削除に失敗しました。" + +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "Modマネージャー:Mod\"$1\"の場所が不明です。" + +msgid "Delete World \"$1\"?" +msgstr "ワールド\"$1\"を削除してよろしいですか?" + +msgid "No" +msgstr "いいえ" + +msgid "Rename Modpack:" +msgstr "名前を変更" + +msgid "Accept" +msgstr "決定" + +msgid "Install Mod: file: \"$1\"" +msgstr "Modインストール:ファイル\"$1\"からModをインストールします。" + +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" +"\n" +"Modインストール:ファイル\"$1\"は非対応の形式か、壊れています。" + +msgid "Failed to install $1 to $2" +msgstr "$2へ$1をインストールできませんでした。" + +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" +"Modインストール:Modパック$1に適したフォルダ名を見つけることができませんでし" +"た。" + +msgid "Install Mod: unable to find real modname for: $1" +msgstr "Modインストール:$1の本来のMod名が不明です。" + +msgid "Unsorted" +msgstr "未分類" + +msgid "Search" +msgstr "検索" + +msgid "Downloading $1, please wait..." +msgstr "$1をダウンロードしています。しばらくお待ちください..." + +msgid "Successfully installed:" +msgstr "インストールが完了しました。:" + +msgid "Shortname:" +msgstr "省略名" + +msgid "Rating" +msgstr "評価" + +msgid "re-Install" +msgstr "再インストール" + +msgid "Install" +msgstr "インストール" + +msgid "Close store" +msgstr "閉じる" + +msgid "Page $1 of $2" +msgstr "ページ $1/$2" + +msgid "Credits" +msgstr "クレジット" + +msgid "Core Developers" +msgstr "開発者" + +msgid "Active Contributors" +msgstr "開発協力者" + +msgid "Previous Contributors" +msgstr "以前の開発協力者" + +msgid "Installed Mods:" +msgstr "インストール済みのMod:" + +msgid "Online mod repository" +msgstr "オンラインでModを検索" + +msgid "No mod description available" +msgstr "Modの説明がありません。" + +msgid "Mod information:" +msgstr "Modの情報:" + +msgid "Rename" +msgstr "名前を変更" + +msgid "Uninstall selected modpack" +msgstr "選択したModパックを削除" + +msgid "Uninstall selected mod" +msgstr "選択したModを削除" + +msgid "Select Mod File:" +msgstr "Modファイルを選択" + +msgid "Mods" +msgstr "Mod" + +msgid "Address / Port :" +msgstr "アドレスとポート:" + +msgid "Name / Password :" +msgstr "名前とパスワード:" + +msgid "Public Serverlist" +msgstr "公開済みのサーバーの一覧" + +msgid "Delete" +msgstr "削除" + +msgid "Connect" +msgstr "接続" + +msgid "Creative mode" +msgstr "クリエイティブモード" + +msgid "Damage enabled" +msgstr "HPあり" + +msgid "PvP enabled" +msgstr "PvPあり" + +msgid "Client" +msgstr "クライアント" + +msgid "New" +msgstr "作成" + +msgid "Configure" +msgstr "設定" + +msgid "Start Game" +msgstr "ゲームスタート" + +msgid "Select World:" +msgstr "ワールドを選択:" + +msgid "Creative Mode" +msgstr "クリエイティブモード" + +msgid "Enable Damage" +msgstr "ダメージあり" + +msgid "Public" +msgstr "サーバーを公開する" + +msgid "Name/Password" +msgstr "名前とパスワード" + +msgid "Bind Address" +msgstr "バインドアドレス" + +msgid "Port" +msgstr "ポート" + +msgid "Server Port" +msgstr "サーバーのポート" + +msgid "Server" +msgstr "サーバー" + +msgid "No Filter" +msgstr "フィルタ無し" + +msgid "Bilinear Filter" +msgstr "バイリニアフィルタ" + +msgid "Trilinear Filter" +msgstr "トリリニアフィルタ" + +msgid "No Mipmap" +msgstr "ミップマップ無し" + +msgid "Mipmap" +msgstr "ミップマップ" + +msgid "Mipmap + Aniso. Filter" +msgstr "異方性フィルタ" + +msgid "Are you sure to reset your singleplayer world?" +msgstr "シングルプレイヤーのワールドをリセットしてよろしいですか?" + +msgid "No!!!" +msgstr "いいえ" + +msgid "Smooth Lighting" +msgstr "滑らかな光" + +msgid "Enable Particles" +msgstr "パーティクル有効化" + +msgid "3D Clouds" +msgstr "立体の雲" + +msgid "Fancy Trees" +msgstr "綺麗な木" + +msgid "Opaque Water" +msgstr "不透明な水" + +msgid "Connected Glass" +msgstr "ガラスをつなげる" + +msgid "Node Highlighting" +msgstr "ノードの強調" + +msgid "Texturing:" +msgstr "テクスチャリング:" + +msgid "Rendering:" +msgstr "レンダリング:" + +msgid "Restart minetest for driver change to take effect" +msgstr "ドライバーを変更するためMinetesを再起動します" + +msgid "Shaders" +msgstr "シェーダー" + +msgid "Change keys" +msgstr "操作変更" + +msgid "Reset singleplayer world" +msgstr "シングルプレイヤーのワールドをリセット" + +msgid "GUI scale factor" +msgstr "メニューの大きさ" + +msgid "Scaling factor applied to menu elements: " +msgstr "メニューの大きさとして設定する数値:" + +msgid "Touch free target" +msgstr "タッチ位置を自由にする" + +msgid "Touchthreshold (px)" +msgstr "タッチのしきい値(ピクセル単位)" + +msgid "Bumpmapping" +msgstr "バンプマッピング" + +msgid "Generate Normalmaps" +msgstr "ノーマルマップの生成" + +msgid "Parallax Occlusion" +msgstr "視差遮蔽マッピング" + +msgid "Waving Water" +msgstr "揺れる水" + +msgid "Waving Leaves" +msgstr "揺れる葉" + +msgid "Waving Plants" +msgstr "揺れる草花" + +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "シェーダーを有効にするにはOpenGLを使用する必要があります。" + +msgid "Settings" +msgstr "設定" + +msgid "Fly mode" +msgstr "飛行モード" + +msgid "Start Singleplayer" +msgstr "ゲームスタート" + +msgid "Config mods" +msgstr "Mod設定" + +msgid "Main" +msgstr "メイン" + +msgid "Play" +msgstr "ゲームスタート" + +msgid "Singleplayer" +msgstr "シングルプレイヤー" + +msgid "Select texture pack:" +msgstr "テクスチャパックを選択:" + +msgid "No information available" +msgstr "情報がありません。" + +msgid "Texturepacks" +msgstr "テクスチャパック" + +msgid "Loading textures..." +msgstr "テクスチャ読み込み中..." + +msgid "Rebuilding shaders..." +msgstr "シェーダー構築中..." + +msgid "Initializing nodes..." +msgstr "ノードの設定中..." + +msgid "Item textures..." +msgstr "アイテムのテクスチャを設定中..." + +msgid "Done!" +msgstr "完了!" + +msgid "Main Menu" +msgstr "メインメニュー" + +msgid "Player name too long." +msgstr "名前が長過ぎます。" + +msgid "Connection error (timed out?)" +msgstr "接続失敗(またはタイムアウト)" + +msgid "No world selected and no address provided. Nothing to do." +msgstr "ワールドが選択されていないアドレスです。続行できません。" + +msgid "Provided world path doesn't exist: " +msgstr "ワールドが存在しません:" + +msgid "Could not find or load game \"" +msgstr "ゲーム\"の読み込みができません。" + +msgid "Invalid gamespec." +msgstr "無効なgamespecです。" + +msgid "needs_fallback_font" +msgstr "yes" + +msgid "Proceed" +msgstr "決定" + +msgid "You died." +msgstr "You died." + +msgid "Respawn" +msgstr "Respawn" + +msgid "" +"Default Controls:\n" +"No menu visible:\n" +"- single tap: button activate\n" +"- double tap: place/use\n" +"- slide finger: look around\n" +"Menu/Inventory visible:\n" +"- double tap (outside):\n" +" -->close\n" +"- touch stack, touch slot:\n" +" --> move stack\n" +"- touch&drag, tap 2nd finger\n" +" --> place single item to slot\n" +msgstr "" +"基本操作:\n" +"タッチによる操作\n" +"- シングルタップ:ブロックの破壊\n" +"- ダブルタップ:設置やアイテムの使用\n" +"- 指でスライド:見回す\n" +"メニュー(インベントリ)の操作\n" +"- ダブルタップ:\n" +"-- 閉じる\n" +"- アイテムスロットをタッチ:\n" +"-- アイテムの移動\n" +"- タッチしてドラッグ:\n" +"-- アイテムを置く\n" + +msgid "" +"Default Controls:\n" +"- WASD: move\n" +"- Space: jump/climb\n" +"- Shift: sneak/go down\n" +"- Q: drop item\n" +"- I: inventory\n" +"- Mouse: turn/look\n" +"- Mouse left: dig/punch\n" +"- Mouse right: place/use\n" +"- Mouse wheel: select item\n" +"- T: chat\n" +msgstr "" +"基本操作:\n" +"- WASD:移動\n" +"- スペース:ジャンプ、登る\n" +"- Shift:スニーク、降りる\n" +"- Q:アイテムを落とす\n" +"- I:インベントリ\n" +"- マウス移動:見回す\n" +"- 左クリック:ブロック破壊\n" +"- 右クリック:設置や使用\n" +"- ホイール:アイテム選択\n" +"- T:チャット画面\n" + +msgid "Continue" +msgstr "再開" + +msgid "Change Password" +msgstr "パスワード変更" + +msgid "Sound Volume" +msgstr "音量" + +msgid "Change Keys" +msgstr "操作変更" + +msgid "Exit to Menu" +msgstr "タイトル" + +msgid "Exit to OS" +msgstr "終了" + +msgid "Shutting down..." +msgstr "終了中..." + +msgid "Loading..." +msgstr "読み込み中..." + +msgid "Creating server..." +msgstr "サーバー作成中..." + +msgid "Creating client..." +msgstr "クライアント作成中..." + +msgid "Resolving address..." +msgstr "アドレス解決中..." + +msgid "Connecting to server..." +msgstr "サーバー接続中..." + +msgid "Item definitions..." +msgstr "アイテム定義中..." + +msgid "Node definitions..." +msgstr "ノード定義中..." + +msgid "Media..." +msgstr "..." + +msgid " KB/s" +msgstr " KB/秒" + +msgid " MB/s" +msgstr " MB/秒" + +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"詳細はdebug.txtをご覧ください。" + +msgid "Enter " +msgstr "Enter" + +msgid "ok" +msgstr "決定" + +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "操作の設定を変更します。" + +msgid "\"Use\" = climb down" +msgstr "「使用」で降りる" + +msgid "Double tap \"jump\" to toggle fly" +msgstr "「ジャンプ」二回押しで飛行モード" + +msgid "Key already in use" +msgstr "すでに使われているキーです。" + +msgid "press key" +msgstr "キー入力待ち" + +msgid "Forward" +msgstr "前進" + +msgid "Backward" +msgstr "後退" + +msgid "Left" +msgstr "左に進む" + +msgid "Right" +msgstr "右に進む" + +msgid "Use" +msgstr "使用" + +msgid "Jump" +msgstr "ジャンプ" + +msgid "Sneak" +msgstr "スニーク" + +msgid "Drop" +msgstr "落とす" + +msgid "Inventory" +msgstr "インベントリ" + +msgid "Chat" +msgstr "チャット" + +msgid "Command" +msgstr "コマンド" + +msgid "Console" +msgstr "コンソール" + +msgid "Toggle fly" +msgstr "飛行モード" + +msgid "Toggle fast" +msgstr "高速移動モード" + +msgid "Toggle noclip" +msgstr "すり抜けモード" + +msgid "Range select" +msgstr "視野範囲変更" + +msgid "Print stacks" +msgstr "スタックの表示" + +msgid "Old Password" +msgstr "古いパスワード" + +msgid "New Password" +msgstr "新しいパスワード" + +msgid "Confirm Password" +msgstr "パスワードの確認" + +msgid "Change" +msgstr "変更" + +msgid "Passwords do not match!" +msgstr "パスワードが一致しません!" + +msgid "Sound Volume: " +msgstr "音量:" + +msgid "Exit" +msgstr "閉じる" + +msgid "Left Button" +msgstr "左ボタン" + +msgid "Middle Button" +msgstr "中ボタン" + +msgid "Right Button" +msgstr "右ボタン" + +msgid "X Button 1" +msgstr "Xボタン1" + +msgid "Back" +msgstr "Back" + +msgid "Clear" +msgstr "消す" + +msgid "Return" +msgstr "エンター" + +msgid "Tab" +msgstr "タブ" + +msgid "X Button 2" +msgstr "Xボタン2" + +msgid "Capital" +msgstr "Caps Lock" + +msgid "Control" +msgstr "コントロール" + +msgid "Kana" +msgstr "かな" + +msgid "Menu" +msgstr "メニュー" + +msgid "Pause" +msgstr "ポーズ" + +msgid "Shift" +msgstr "Shift" + +msgid "Convert" +msgstr "変換" + +msgid "Escape" +msgstr "Esc" + +msgid "Final" +msgstr "Finalキー" + +msgid "Junja" +msgstr "Junjaキー" + +msgid "Kanji" +msgstr "半角/全角" + +msgid "Nonconvert" +msgstr "無変換" + +msgid "End" +msgstr "終了" + +msgid "Home" +msgstr "Home" + +msgid "Mode Change" +msgstr "モード変更" + +msgid "Next" +msgstr "Page Down" + +msgid "Prior" +msgstr "Page Up" + +msgid "Space" +msgstr "スペース" + +msgid "Down" +msgstr "下" + +msgid "Execute" +msgstr "実行キー" + +msgid "Print" +msgstr "印刷キー" + +msgid "Select" +msgstr "選択キー" + +msgid "Up" +msgstr "上" + +msgid "Help" +msgstr "ヘルプ" + +msgid "Insert" +msgstr "Insert" + +msgid "Snapshot" +msgstr "Snapshot" + +msgid "Left Windows" +msgstr "左Windows" + +msgid "Apps" +msgstr "Apps" + +msgid "Numpad 0" +msgstr "Numpad 0" + +msgid "Numpad 1" +msgstr "Numpad 1" + +msgid "Right Windows" +msgstr "右Windows" + +msgid "Sleep" +msgstr "スリープ" + +msgid "Numpad 2" +msgstr "Numpad 2" + +msgid "Numpad 3" +msgstr "Numpad 3" + +msgid "Numpad 4" +msgstr "Numpad 4" + +msgid "Numpad 5" +msgstr "Numpad 5" + +msgid "Numpad 6" +msgstr "Numpad 6" + +msgid "Numpad 7" +msgstr "Numpad 7" + +msgid "Numpad *" +msgstr "Numpad *" + +msgid "Numpad +" +msgstr "Numpad +" + +msgid "Numpad -" +msgstr "Numpad -" + +msgid "Numpad /" +msgstr "Numpad /" + +msgid "Numpad 8" +msgstr "Numpad 8" + +msgid "Numpad 9" +msgstr "Numpad 9" + +msgid "Num Lock" +msgstr "Num Lock" + +msgid "Scroll Lock" +msgstr "Scroll Lock" + +msgid "Left Shift" +msgstr "左Shift" + +msgid "Right Shift" +msgstr "右Shift" + +msgid "Left Control" +msgstr "左Ctrl" + +msgid "Left Menu" +msgstr "左メニュー" + +msgid "Right Control" +msgstr "右Ctrl" + +msgid "Right Menu" +msgstr "右メニュー" + +msgid "Comma" +msgstr "読点" + +msgid "Minus" +msgstr "ー" + +msgid "Period" +msgstr "." + +msgid "Plus" +msgstr "プラス" + +msgid "Attn" +msgstr ":" + +msgid "CrSel" +msgstr "CrSel" + +msgid "Erase OEF" +msgstr "Erase OEF" + +msgid "ExSel" +msgstr "ExSel" + +msgid "OEM Clear" +msgstr "OEM Clear" + +msgid "PA1" +msgstr "PA1" + +msgid "Zoom" +msgstr "ズーム" diff --git a/po/ko/minetest.po b/po/ko/minetest.po new file mode 100644 index 0000000..2611758 --- /dev/null +++ b/po/ko/minetest.po @@ -0,0 +1,976 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:48 +msgid "Depends:" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:68 +msgid "Enable MP" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:70 +msgid "Disable MP" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:82 +msgid "Enable all" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:343 +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +msgid "Downloading" +msgstr "" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +msgid "Shortname:" +msgstr "" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:140 +msgid "Fancy Trees" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:142 +msgid "Opaque Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:144 +msgid "Connected Glass" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:167 +msgid "Reset singleplayer world" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +msgid "Bumpmapping" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +msgid "Start Singleplayer" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:72 +msgid "Config mods" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:191 +msgid "Main" +msgstr "" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +msgid "Texturepacks" +msgstr "" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "" + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "yes" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "" + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "" + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "" + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "" + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "" + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "" diff --git a/po/ky/minetest.po b/po/ky/minetest.po new file mode 100644 index 0000000..47da737 --- /dev/null +++ b/po/ky/minetest.po @@ -0,0 +1,1154 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2013-06-01 18:09+0200\n" +"Last-Translator: Chynggyz Jumaliev \n" +"Language-Team: LANGUAGE \n" +"Language: ky\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 1.4-dev\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:26 +#, fuzzy +msgid "World:" +msgstr "Дүйнөнү тандаңыз:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +#, fuzzy +msgid "Hide Game" +msgstr "Оюн" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:48 +#, fuzzy +msgid "Depends:" +msgstr "көз карандылыктары:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Сактоо" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Жокко чыгаруу" + +#: builtin/mainmenu/dlg_config_world.lua:68 +#, fuzzy +msgid "Enable MP" +msgstr "Баарын күйгүзүү" + +#: builtin/mainmenu/dlg_config_world.lua:70 +#, fuzzy +msgid "Disable MP" +msgstr "Баарын өчүрүү" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "күйгүзүлгөн" + +#: builtin/mainmenu/dlg_config_world.lua:82 +#, fuzzy +msgid "Enable all" +msgstr "Баарын күйгүзүү" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Дүйнө аты" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Оюн" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Жаратуу" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Ооба" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +#, fuzzy +msgid "Delete World \"$1\"?" +msgstr "Дүйнөнү өчүрүү" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Жок" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "Кабыл алуу" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:343 +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:363 +#, fuzzy +msgid "Failed to install $1 to $2" +msgstr "Дүйнөнү инициалдаштыруу катасы" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +#, fuzzy +msgid "Downloading" +msgstr "Ылдый" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +#, fuzzy +msgid "Shortname:" +msgstr "Дүйнө аты" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Алкыштар" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:121 +#, fuzzy +msgid "Select Mod File:" +msgstr "Дүйнөнү тандаңыз:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "Дареги/порту" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Аты/сырсөзү" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +#, fuzzy +msgid "Public Serverlist" +msgstr "Жалпылык серверлердин тизмеси:" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Өчүрүү" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Туташуу" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Жаңы" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Ырастоо" + +#: builtin/mainmenu/tab_server.lua:29 +#, fuzzy +msgid "Start Game" +msgstr "Оюнду баштоо/туташуу" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Дүйнөнү тандаңыз:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Жаратуу режими" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Убалды күйгүзүү" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Жалпылык" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "Тегиз жарык" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "Бөлүкчөлөрдү күйгүзүү" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "3D-булуттар" + +#: builtin/mainmenu/tab_settings.lua:140 +#, fuzzy +msgid "Fancy Trees" +msgstr "Кооз бактар" + +#: builtin/mainmenu/tab_settings.lua:142 +#, fuzzy +msgid "Opaque Water" +msgstr "Күңүрт суу" + +#: builtin/mainmenu/tab_settings.lua:144 +#, fuzzy +msgid "Connected Glass" +msgstr "Туташуу" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "Mip-текстуралоо" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "Анизатропия чыпкалоосу" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "Экисызык чыпкалоосу" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "Үчсызык чыпкалоосу" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "Көлөкөлөгүчтөр" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "Баскычтарды өзгөртүү" + +#: builtin/mainmenu/tab_settings.lua:167 +#, fuzzy +msgid "Reset singleplayer world" +msgstr "Бир кишилик" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +#, fuzzy +msgid "Bumpmapping" +msgstr "Mip-текстуралоо" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "Ырастоолор" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +#, fuzzy +msgid "Start Singleplayer" +msgstr "Бир кишилик" + +#: builtin/mainmenu/tab_simple_main.lua:72 +#, fuzzy +msgid "Config mods" +msgstr "Ырастоо" + +#: builtin/mainmenu/tab_simple_main.lua:191 +#, fuzzy +msgid "Main" +msgstr "Башкы меню" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Ойноо" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Бир кишилик" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +msgid "Texturepacks" +msgstr "" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "Буюм текстуралары..." + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "Кайтадан жаралуу" + +#: src/game.cpp:2250 +#, fuzzy +msgid "Item definitions..." +msgstr "Буюм текстуралары..." + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "" + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "" + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Толугураак маалымат үчүн, debug.txt'ти текшериңиз." + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "Улантуу" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "баскычты басыңыз" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Алга" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Артка" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Солго" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Оңго" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Колдонуу" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Секирүү" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Уурданып басуу" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Ыргытуу" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Мүлк-шайман" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Маек" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Команда" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Консоль" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Учууга которуу" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Тез басууга которуу" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "Эски сырсөз" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "Жаңы сырсөз" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "Сырсөздү аныктоо" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "Өзгөртүү" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "Сырсөздөр дал келген жок!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "Үн көлөмү: " + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Чыгуу" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Сол баскыч" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Ортоңку баскыч" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Оң баскыч" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "Артка" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "Тазалоо" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tab" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "Caps Lock" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Ctrl" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Кана" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Меню" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Пауза" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Shift" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Esc" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Кандзи" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "End" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Home" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "Режимди өзгөртүү" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Кийинки" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Боштук" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Ылдый" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Аткаруу" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Басма" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Тандоо" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Өйдө" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Жардам" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Insert" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "Тез сүрөт" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "Сол Windows" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "Тиркемелер" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Кош. клав. 0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Кош. клав. 1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "Оң Windows" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Уйку" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Кош. клав. 2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Кош. клав. 3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Кош. клав. 4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Кош. клав. 5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Кош. клав. 6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Кош. клав. 7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Кош. клав. *" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Кош. клав. +" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Кош. клав. -" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Кош. клав. /" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Кош. клав. 8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Кош. клав. 9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Num Lock" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Scroll Lock" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Сол Shift" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Оң Shift" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Сол Ctrl" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Сол меню" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Оң Ctrl" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Оң меню" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Үтүр" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Кемитүү белгиси" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "Айланма сан" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Кошуу белгиси" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "Масштаб" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "Башкы меню" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "Туташтыруу катасы (убактыңыз өтүп кеттиби?)" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "Дүйнө тандалган жок жана дареги киргизилген жок. Кылууга эч нерсе жок." + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "Оюнду табуу же жүктөө мүмкүн эмес \"" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "" + +#~ msgid "Left click: Move all items, Right click: Move single item" +#~ msgstr "Сол баскычы: Бардык буюмдарды ташуу, Оң баскычы: Бир буюмду ташуу" + +#~ msgid "is required by:" +#~ msgstr "талап кылынганы:" + +#~ msgid "Configuration saved. " +#~ msgstr "Конфигурация сакталды. " + +#~ msgid "Warning: Configuration not consistent. " +#~ msgstr "Эскертүү: Туура эмес конфигурация. " + +#~ msgid "Multiplayer" +#~ msgstr "Көп кишилик" + +#~ msgid "Advanced" +#~ msgstr "Кошумча" + +#~ msgid "Show Public" +#~ msgstr "Жалпылыкты көрсөтүү" + +#~ msgid "Show Favorites" +#~ msgstr "Тандалмаларды көрсөтүү" + +#~ msgid "Leave address blank to start a local server." +#~ msgstr "Жергиликтүү серверди жүргүзүү үчүн даректи бош калтырыңыз." + +#~ msgid "Create world" +#~ msgstr "Дүйнөнү жаратуу" + +#~ msgid "Address required." +#~ msgstr "Дареги талап кылынат." + +#~ msgid "Cannot delete world: Nothing selected" +#~ msgstr "Дүнөнү жаратуу мүмкүн эмес: Эч нерсе тандалган жок" + +#~ msgid "Files to be deleted" +#~ msgstr "Өчүрүлө турган файлдар" + +#~ msgid "Cannot create world: No games found" +#~ msgstr "Дүйнөнү жаратуу мүмкүн эмес: Оюндар табылган жок" + +#~ msgid "Cannot configure world: Nothing selected" +#~ msgstr "Дүйнөнү ырастоо мүмкүн эмес: Эч нерсе тандалган жок" + +#~ msgid "Failed to delete all world files" +#~ msgstr "Бардык дүйнө файлдарын өчүрүү оңунан чыккан жок" + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: Walk\n" +#~ "- Mouse left: dig/hit\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- 0...9: select item\n" +#~ "- Shift: sneak\n" +#~ "- R: Toggle viewing all loaded chunks\n" +#~ "- I: Inventory menu\n" +#~ "- ESC: This menu\n" +#~ "- T: Chat\n" +#~ msgstr "" +#~ "Жарыяланбас башкаруу:\n" +#~ "- WASD: Басуу\n" +#~ "- Сол кнопкасы: казуу/согуу\n" +#~ "- Оң кнопкасы: коюу/колдонуу\n" +#~ "- Чычкан дөңгөлөгү: буюмду тандоо\n" +#~ "- 0...9: буюмду тандоо\n" +#~ "- Shift: уурданып басуу\n" +#~ "- R: алыс кароо\n" +#~ "- I: мүлк-шайман\n" +#~ "- ESC: бул меню\n" +#~ "- T: маек\n" + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: move\n" +#~ "- Space: jump/climb\n" +#~ "- Shift: sneak/go down\n" +#~ "- Q: drop item\n" +#~ "- I: inventory\n" +#~ "- Mouse: turn/look\n" +#~ "- Mouse left: dig/punch\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- T: chat\n" +#~ msgstr "" +#~ "Жарыяланбас башкаруу:\n" +#~ "- WASD: басуу\n" +#~ "- Боштугу: секирүү/өйдө чыгуу\n" +#~ "- Shift: уурданып басуу/ылдый түшүү\n" +#~ "- Q: буюмду таштоо\n" +#~ "- I: мүлк-шайман\n" +#~ "- Чычканы: бурулуу/кароо\n" +#~ "- Сол чычкан баскычы: казуу/согуу\n" +#~ "- Оң чычкан баскычы: коюу/колдонуу\n" +#~ "- Чычкан дөңгөлөгү: буюмду тандоо\n" +#~ "- T: маек\n" + +#~ msgid "Exit to OS" +#~ msgstr "Оюндан чыгуу" + +#~ msgid "Exit to Menu" +#~ msgstr "Менюга чыгуу" + +#~ msgid "Sound Volume" +#~ msgstr "Үн көлөмү" + +#~ msgid "Change Password" +#~ msgstr "Сырсөздү өзгөртүү" + +#~ msgid "Continue" +#~ msgstr "Улантуу" + +#~ msgid "You died." +#~ msgstr "Сиз өлдүңүз." + +#~ msgid "Shutting down stuff..." +#~ msgstr "Оюн өчүрүлүүдө..." + +#~ msgid "Connecting to server..." +#~ msgstr "Серверге туташтырылууда..." + +#~ msgid "Resolving address..." +#~ msgstr "Дареги чечилүүдө..." + +#~ msgid "Creating client..." +#~ msgstr "Клиент жаратылууда..." + +#~ msgid "Creating server...." +#~ msgstr "Сервер жаратылууда...." + +#~ msgid "Loading..." +#~ msgstr "Жүктөлүүдө..." + +#, fuzzy +#~ msgid "Finite Liquid" +#~ msgstr "Чектүү суюктук" + +#, fuzzy +#~ msgid "Password" +#~ msgstr "Эски сырсөз" + +#~ msgid "Favorites:" +#~ msgstr "Тандалмалар:" + +#, fuzzy +#~ msgid "Games" +#~ msgstr "Оюн" + +#, fuzzy +#~ msgid "Game Name" +#~ msgstr "Оюн" diff --git a/po/lt/minetest.po b/po/lt/minetest.po new file mode 100644 index 0000000..28054b7 --- /dev/null +++ b/po/lt/minetest.po @@ -0,0 +1,1077 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2013-12-11 19:23+0200\n" +"Last-Translator: Jonas Kriaučiūnas \n" +"Language-Team: LANGUAGE \n" +"Language: lt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n" +"%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 1.7-dev\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "Gerai" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "Pasaulis:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "Slėpti vidinius" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "Slėpti papild. pakų turinį" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "Papildinys:" + +#: builtin/mainmenu/dlg_config_world.lua:48 +msgid "Depends:" +msgstr "Priklauso:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Įrašyti" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Atsisakyti" + +#: builtin/mainmenu/dlg_config_world.lua:68 +msgid "Enable MP" +msgstr "Įjungti papildinį" + +#: builtin/mainmenu/dlg_config_world.lua:70 +msgid "Disable MP" +msgstr "Išjungti papildinį" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "įjungtas" + +#: builtin/mainmenu/dlg_config_world.lua:82 +msgid "Enable all" +msgstr "Įjungti visus" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Pasaulio pavadinimas" + +#: builtin/mainmenu/dlg_create_world.lua:53 +#, fuzzy +msgid "Seed" +msgstr "Sėkla" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Žaidimas" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Sukurti" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "Pasaulis, pavadintas „$1“ jau yra" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Taip" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "Ištrinti pasaulį „$1“?" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Ne" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "Pervadinti papildinių paką:" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +#, fuzzy +msgid "Accept" +msgstr "Priimti" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:343 +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "Nepavyko įdiegti $1 į $2" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +msgid "Downloading" +msgstr "" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +#, fuzzy +msgid "Shortname:" +msgstr "Pasaulio pavadinimas" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "Įdiegti iš naujo" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "Įdiegti" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Padėkos" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "Pagrindiniai kūrėjai" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "Aktyvūs pagalbininkai" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "Įdiegti papildiniai:" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "Papildiniai internete" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "Papildinio aprašymas nepateiktas" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "Papildinio informacija:" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "Pervadinti" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "Pašalinti pasirinktą papildinį" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "Pašalinti pasirinktą papildinį" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "Pasirinkite papildinio failą:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "Papildiniai" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "Adresas/Prievadas" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Vardas/slaptažodis" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "Viešų serverių sąrašas" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Ištrinti" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Jungtis" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "Žaisti tinkle(klientas)" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Naujas" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Konfigūruoti" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "Pradėti žaidimą" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Pasirinkite pasaulį:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +#, fuzzy +msgid "Creative Mode" +msgstr "Kūrybinė veiksena" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Leisti sužeidimus" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Viešas" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "Serverio prievadas" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "Serveris" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +#, fuzzy +msgid "Smooth Lighting" +msgstr "Apšvietimo efektai" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "Trimačiai debesys" + +#: builtin/mainmenu/tab_settings.lua:140 +msgid "Fancy Trees" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:142 +msgid "Opaque Water" +msgstr "Nepermatomas vanduo" + +#: builtin/mainmenu/tab_settings.lua:144 +#, fuzzy +msgid "Connected Glass" +msgstr "Jungtis" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:160 +#, fuzzy +msgid "Shaders" +msgstr "Šešėliai" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "Nustatyti klavišus" + +#: builtin/mainmenu/tab_settings.lua:167 +#, fuzzy +msgid "Reset singleplayer world" +msgstr "Žaisti vienam" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +msgid "Bumpmapping" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "Nustatymai" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +#, fuzzy +msgid "Start Singleplayer" +msgstr "Žaisti vienam" + +#: builtin/mainmenu/tab_simple_main.lua:72 +#, fuzzy +msgid "Config mods" +msgstr "Konfigūruoti" + +#: builtin/mainmenu/tab_simple_main.lua:191 +#, fuzzy +msgid "Main" +msgstr "Pagrindinis meniu" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Žaisti" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Žaisti vienam" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +msgid "Texturepacks" +msgstr "" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "" + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "Prisikelti" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "" + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "" + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "" + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" + +#: src/guiFormSpecMenu.cpp:2055 +#, fuzzy +msgid "Proceed" +msgstr "Tęsti" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "Klavišas jau naudojamas" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "paspauskite klavišą" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Pirmyn" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Atgal" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Kairėn" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Dešinėn" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Naudoti" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Pašokti" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Mesti" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Inventorius" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Susirašinėti" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Komanda" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "Senas slaptažodis" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "Naujas slaptažodis" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "Patvirtinti slaptažodį" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "Pakeisti" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "Slaptažodžiai nesutampa!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "" + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Išeiti" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Kairysis mygtukas" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Vidurinis mygtukas" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Dešinysis mygtukas" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Meniu" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Shift (Lyg2)" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Tarpas" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Žemyn" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Vykdyti" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Aukštyn" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Pagalba" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Įterpti" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Kairysis Shift" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Dešinysis Shift" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Kairysis Control" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Dešinysis Control" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Kablelis" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Plius" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "" + +#: src/keycode.cpp:249 +#, fuzzy +msgid "Zoom" +msgstr "Pritraukti" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "Pagrindinis meniu" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "" + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "" + +#~ msgid "Exit to OS" +#~ msgstr "Išeiti iš žaidimo" + +#~ msgid "Exit to Menu" +#~ msgstr "Grįžti į meniu" + +#~ msgid "Change Password" +#~ msgstr "Keisti slaptažodį" + +#~ msgid "Continue" +#~ msgstr "Tęsti" + +#~ msgid "You died." +#~ msgstr "Jūs numirėte." + +#~ msgid "Connecting to server..." +#~ msgstr "Jungiamasi prie serverio..." + +#~ msgid "Resolving address..." +#~ msgstr "Ieškoma adreso..." + +#, fuzzy +#~ msgid "Creating client..." +#~ msgstr "Kuriamas klientas..." + +#~ msgid "Creating server...." +#~ msgstr "Kuriamas serveris...." + +#~ msgid "Loading..." +#~ msgstr "Įkeliama..." + +#~ msgid "Add mod:" +#~ msgstr "Pridėti papildinį:" + +#~ msgid "MODS" +#~ msgstr "PAPILDINIAI" + +#~ msgid "SINGLE PLAYER" +#~ msgstr "VIENAS ŽAIDĖJAS" + +#~ msgid "SETTINGS" +#~ msgstr "NUSTATYMAI" + +#~ msgid "Password" +#~ msgstr "Slaptažodis" + +#~ msgid "Name" +#~ msgstr "Vardas" + +#~ msgid "START SERVER" +#~ msgstr "PALEISTI SERVERĮ" + +#~ msgid "Favorites:" +#~ msgstr "Mėgiami:" + +#~ msgid "CLIENT" +#~ msgstr "ŽAISTI TINKLE" + +#~ msgid "<<-- Add mod" +#~ msgstr "<<-- Pridėti papildinį" + +#~ msgid "Remove selected mod" +#~ msgstr "Pašalinti pasirinktą papildinį" + +#, fuzzy +#~ msgid "EDIT GAME" +#~ msgstr "KEISTI ŽAIDIMĄ" + +#~ msgid "new game" +#~ msgstr "naujas žaidimas" + +#~ msgid "edit game" +#~ msgstr "keisti žaidimą" + +#~ msgid "Mods:" +#~ msgstr "Papildiniai:" + +#~ msgid "Games" +#~ msgstr "Žaidimai" + +#~ msgid "GAMES" +#~ msgstr "ŽAIDIMAI" + +#~ msgid "Game Name" +#~ msgstr "Žaidimo pavadinimas" diff --git a/po/minetest.pot b/po/minetest.pot new file mode 100644 index 0000000..9a62378 --- /dev/null +++ b/po/minetest.pot @@ -0,0 +1,976 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:48 +msgid "Depends:" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:68 +msgid "Enable MP" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:70 +msgid "Disable MP" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:82 +msgid "Enable all" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:343 +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +msgid "Downloading" +msgstr "" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +msgid "Shortname:" +msgstr "" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:140 +msgid "Fancy Trees" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:142 +msgid "Opaque Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:144 +msgid "Connected Glass" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:167 +msgid "Reset singleplayer world" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +msgid "Bumpmapping" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +msgid "Start Singleplayer" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:72 +msgid "Config mods" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:191 +msgid "Main" +msgstr "" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +msgid "Texturepacks" +msgstr "" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "" + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "" + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "" + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "" + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "" + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "" + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "" diff --git a/po/nb/minetest.po b/po/nb/minetest.po new file mode 100644 index 0000000..438e7e4 --- /dev/null +++ b/po/nb/minetest.po @@ -0,0 +1,1015 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2013-06-21 15:48+0200\n" +"Last-Translator: sfan5 \n" +"Language-Team: LANGUAGE \n" +"Language: nb\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Weblate 1.4-dev\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:26 +#, fuzzy +msgid "World:" +msgstr "Navnet på verdenen" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +#, fuzzy +msgid "Hide Game" +msgstr "Spill" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:48 +#, fuzzy +msgid "Depends:" +msgstr "Avhenger av:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Lagre" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Avbryt" + +#: builtin/mainmenu/dlg_config_world.lua:68 +#, fuzzy +msgid "Enable MP" +msgstr "Aktiver Alle" + +#: builtin/mainmenu/dlg_config_world.lua:70 +#, fuzzy +msgid "Disable MP" +msgstr "Deaktiver Alle" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "aktivert" + +#: builtin/mainmenu/dlg_config_world.lua:82 +#, fuzzy +msgid "Enable all" +msgstr "Aktiver Alle" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Navnet på verdenen" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Spill" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Opprett" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Ja" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Nei" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:343 +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +msgid "Downloading" +msgstr "" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +#, fuzzy +msgid "Shortname:" +msgstr "Navnet på verdenen" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:140 +msgid "Fancy Trees" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:142 +msgid "Opaque Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:144 +msgid "Connected Glass" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:167 +msgid "Reset singleplayer world" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +msgid "Bumpmapping" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +msgid "Start Singleplayer" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:72 +msgid "Config mods" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:191 +msgid "Main" +msgstr "" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +msgid "Texturepacks" +msgstr "" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "" + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "" + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "" + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "" + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "" + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "" + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "" + +#~ msgid "is required by:" +#~ msgstr "trengs av:" + +#~ msgid "Configuration saved. " +#~ msgstr "Konfigurasjon lagret. " + +#~ msgid "" +#~ "Warning: Some configured mods are missing.\n" +#~ "Their setting will be removed when you save the configuration. " +#~ msgstr "" +#~ "Advarsel: Noen konfigurerte modifikasjoner mangler. \n" +#~ "Instillingene deres vil bli fjernet når du lagrer konfigurasjonen." + +#~ msgid "" +#~ "Warning: Some mods are not configured yet.\n" +#~ "They will be enabled by default when you save the configuration. " +#~ msgstr "" +#~ "Advarsel: Noen modifikasjoner er ikke konfigurert enda. \n" +#~ "De vil bli aktivert som standard når du lagrer konfigurasjonen." + +#~ msgid "You died." +#~ msgstr "Du døde." + +#, fuzzy +#~ msgid "Games" +#~ msgstr "Spill" + +#, fuzzy +#~ msgid "Game Name" +#~ msgstr "Spill" diff --git a/po/nl/minetest.po b/po/nl/minetest.po new file mode 100644 index 0000000..c81353d --- /dev/null +++ b/po/nl/minetest.po @@ -0,0 +1,1216 @@ +# SOME DESCRIPTIVE TITLE. DUTCH TRANSLATION +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2014-01-05 11:31+0200\n" +"Last-Translator: b p \n" +"Language-Team: \n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 1.7-dev\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "Ok" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "Wereld:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "Geen std" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "Verberg mp mods" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "Mod:" + +#: builtin/mainmenu/dlg_config_world.lua:48 +msgid "Depends:" +msgstr "Afhankelijkheden:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Bewaar" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Annuleer" + +#: builtin/mainmenu/dlg_config_world.lua:68 +msgid "Enable MP" +msgstr "MP inschakelen" + +#: builtin/mainmenu/dlg_config_world.lua:70 +msgid "Disable MP" +msgstr "MP uitschakelen" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "ingeschakeld" + +#: builtin/mainmenu/dlg_config_world.lua:82 +msgid "Enable all" +msgstr "Alles aan" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Naam wereld" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "kiemgetal" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "Kaartgenerator" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Spel" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Maak aan" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "Wereld \"$1\" bestaat al" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "Geen wereldnaam opgegeven of geen spel geselecteerd" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "Weet je zeker dat je \"$1\" wilt verwijderen?" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Ja" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "Natuurlijk niet!" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "Modmgr: kan \"$1\" niet verwijderen" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "Modmgr: onjuist pad \"$1\"" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "Verwijder wereld \"$1\"?" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Nee" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "Modverzameling hernoemen:" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "Accepteren" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "Mod installeren: bestand: \"$1\"" + +#: builtin/mainmenu/modmgr.lua:343 +#, fuzzy +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" +"\n" +"Mod installeren: niet ondersteund bestandstype \"$1\"" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "Installeren van $1 in $2 is mislukt" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "Mod installeren: kan geen geschikte map vinden voor modverzameling $1" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "Mod installeren: kan geen echte modnaam vinden voor: $1" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +#, fuzzy +msgid "Downloading" +msgstr "Downloaden" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +#, fuzzy +msgid "Shortname:" +msgstr "Naam wereld" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "Rang" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "opnieuw installeren" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "Installeren" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "Pagina $1 van $2" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Credits" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "Hoofdontwikkelaars" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "Actieve bijdragers" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "Vroegere bijdragers" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "Geïnstalleerde Mods:" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "Online mod opslagplaats" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "Geen mod beschrijving aanwezig" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "Mod beschrijving:" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "Hernoemen" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "Geselecteerde modpack deinstalleren" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "Geselecteerde mod deinstalleren" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "Selecteer Modbestand:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "Mods" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "IP-Adres/Poort" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Naam/Wachtwoord" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "Publieke Serverlijst" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Verwijderen" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Verbinden" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "Client" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Nieuw" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Instellingen" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "Start Server" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Selecteer Wereld:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Creatieve Modus" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Schade inschakelen" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Publiek" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "Serverpoort" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "Server" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "Mooie verlichting" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "Deeltjes aanzetten" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "3D wolken" + +#: builtin/mainmenu/tab_settings.lua:140 +#, fuzzy +msgid "Fancy Trees" +msgstr "Mooie bomen" + +#: builtin/mainmenu/tab_settings.lua:142 +msgid "Opaque Water" +msgstr "Ondoorzichtig water" + +#: builtin/mainmenu/tab_settings.lua:144 +#, fuzzy +msgid "Connected Glass" +msgstr "Verbinden" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "Mip-Mapping" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "Anisotrope Filtering" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "Bi-Lineaire Filtering" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "Tri-Lineare Filtering" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "Shaders" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "Toetsen" + +#: builtin/mainmenu/tab_settings.lua:167 +#, fuzzy +msgid "Reset singleplayer world" +msgstr "Singleplayer" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +#, fuzzy +msgid "Bumpmapping" +msgstr "Mip-Mapping" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "Om schaduwen mogelijk te maken moet OpenGL worden gebruikt." + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "Instellingen" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +#, fuzzy +msgid "Start Singleplayer" +msgstr "Singleplayer" + +#: builtin/mainmenu/tab_simple_main.lua:72 +#, fuzzy +msgid "Config mods" +msgstr "Instellingen" + +#: builtin/mainmenu/tab_simple_main.lua:191 +#, fuzzy +msgid "Main" +msgstr "Hoofdmenu" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Speel" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Singleplayer" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "Selecteer textuurverzameling:" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "Geen informatie aanwezig" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +#, fuzzy +msgid "Texturepacks" +msgstr "Texturen" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "Voorwerp texturen..." + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "needs_fallback_font" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "Herspawnen" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "Voorwerpdefinities..." + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "Node definities..." + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "Media..." + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Lees debug.txt voor details." + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "Doorgaan" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" +"Sneltoetsen. (Als dit menu stuk gaat, verwijder dan instellingen uit " +"minetest.conf)." + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "\"Gebruiken\" = Omlaag" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "2x \"springen\" om te vliegen" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "Toets is al in gebruik" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "druk op" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Vooruit" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Achteruit" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Links" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Rechts" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Gebruiken" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Springen" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Kruipen" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Weggooien" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Rugzak" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Chatten" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Opdracht" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Console" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Vliegen aan/uit" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Snel bewegen aan/uit" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "Noclip aan/uit" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "Range instellen" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "Print stacks" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "Huidig wachtwoord" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "Nieuw wachtwoord" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "Herhaal wachtwoord" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "Veranderen" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "Wachtwoorden zijn niet gelijk!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "Volume: " + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Terug" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Linkermuisknop" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Muiswielknop" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Rechtmuisknop" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "X knop 1" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "Terug" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "Wissen" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "Terug" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tab" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "X knop 2" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "Kapitaal" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Control" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Kana" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Menu" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Pauze" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Shift" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "Converteren" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Escape" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "Final" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Kanji" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "Nonconvert" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "End" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Home" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "Modus veranderen" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Volgende" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Eerste" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Spatie" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Omlaag" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Uitvoeren" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Print" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Selecteren" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Omhoog" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Help" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Insert" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "Screenshot" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "Linker Windowstoets" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "Menu" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Numpad 0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Numpad 1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "Rechter Windowstoets" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Slaapknop" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Numpad 2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Numpad 3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Numpad 4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Numpad 5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Numpad 6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Numpad 7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Numpad *" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Numpad +" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Numpad -" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Numpad /" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Numpad 8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Numpad 9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Num Lock" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Scroll Lock" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Linker Shift" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Rechter Shift" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Linker Ctrl" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Linker Menu" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Rechter Ctrl" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Rechter Menu" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Komma" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Min" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "Punt" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Plus" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "SAK" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "CrSel" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "Erase EOF" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "ExSel" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "OEM Clear" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PA1" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "Zoom" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "Hoofdmenu" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "Fout bij verbinden (time out?)" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "Geen wereld en adres geselecteerd. Niks te doen." + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "Kan niet de game laden of vinden \"" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "Onjuiste gamespec." + +#~ msgid "Left click: Move all items, Right click: Move single item" +#~ msgstr "" +#~ "Linkermuisknop: Verplaats alle items. Rechtermuisknop: Verplaats één item" + +#~ msgid "is required by:" +#~ msgstr "is benodigd voor:" + +#~ msgid "Configuration saved. " +#~ msgstr "Instellingen bewaard. " + +#~ msgid "Warning: Configuration not consistent. " +#~ msgstr "Waarschuwing: Instellingen niet consistent. " + +#~ msgid "Cannot create world: Name contains invalid characters" +#~ msgstr "Kan geen nieuwe wereld aanmaken: de naam bevat onjuiste tekens" + +#~ msgid "Multiplayer" +#~ msgstr "Multiplayer" + +#~ msgid "Advanced" +#~ msgstr "Geavanceerd" + +#~ msgid "Show Public" +#~ msgstr "Publieke server" + +#~ msgid "Show Favorites" +#~ msgstr "Favourieten" + +#~ msgid "Leave address blank to start a local server." +#~ msgstr "Laat het adres leeg om een lokale server te starten." + +#~ msgid "Create world" +#~ msgstr "Maak wereld aan" + +#~ msgid "Address required." +#~ msgstr "IP-adres nodig." + +#~ msgid "Cannot delete world: Nothing selected" +#~ msgstr "Kan niets verwijderen: Geen wereld geselecteerd" + +#~ msgid "Files to be deleted" +#~ msgstr "Deze bestanden worden verwijderd" + +#~ msgid "Cannot create world: No games found" +#~ msgstr "Kan geen wereld aanmaken: Geen games gevonden" + +#~ msgid "Cannot configure world: Nothing selected" +#~ msgstr "Kan instellingen niet aanpassen: Niets geselecteerd" + +#~ msgid "Failed to delete all world files" +#~ msgstr "Niet alle bestanden zijn verwijderd" + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: Walk\n" +#~ "- Mouse left: dig/hit\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- 0...9: select item\n" +#~ "- Shift: sneak\n" +#~ "- R: Toggle viewing all loaded chunks\n" +#~ "- I: Inventory menu\n" +#~ "- ESC: This menu\n" +#~ "- T: Chat\n" +#~ msgstr "" +#~ "Default Besturing:\n" +#~ "- WASD: Lopen\n" +#~ "- Linkermuisknop: Graaf/Sla\n" +#~ "- Rechtmuisknop: Plaats/Gebruik\n" +#~ "- Muiswiel: selecteer item\n" +#~ "- 0...9: selecteer item\n" +#~ "- Shift: kruipen\n" +#~ "- R: Toggle viewing all loaded chunks\n" +#~ "- I: Rugzak\n" +#~ "- ESC: Menu\n" +#~ "- T: Chat\n" + +#~ msgid "" +#~ "Warning: Some configured mods are missing.\n" +#~ "Their setting will be removed when you save the configuration. " +#~ msgstr "" +#~ "LEt op: Sommige ingestelde mods zijn vermist.\n" +#~ "Hun instellingen worden verwijderd als je de configuratie opslaat. " + +#~ msgid "" +#~ "Warning: Some mods are not configured yet.\n" +#~ "They will be enabled by default when you save the configuration. " +#~ msgstr "" +#~ "Let op: Nog niet alle mods zijn geconfigueerd. \n" +#~ "De mods zullen automatisch worden ingeschakeld als je de configuratie " +#~ "bewaard. " + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: move\n" +#~ "- Space: jump/climb\n" +#~ "- Shift: sneak/go down\n" +#~ "- Q: drop item\n" +#~ "- I: inventory\n" +#~ "- Mouse: turn/look\n" +#~ "- Mouse left: dig/punch\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- T: chat\n" +#~ msgstr "" +#~ "Standaard toetsen:\n" +#~ "- W,A,S,D: bewegen\n" +#~ "- Spatie: spring/klim\n" +#~ "- Shift: kruip/duik\n" +#~ "- Q: weggooien\n" +#~ "- I: rugzak\n" +#~ "- Muis: draaien/kijken\n" +#~ "- L.muisknop: graaf/sla\n" +#~ "- R.muisknop: plaats/gebruik\n" +#~ "- Muiswiel: selecteer\n" +#~ "- T: chat\n" + +#~ msgid "Exit to OS" +#~ msgstr "Afsluiten" + +#~ msgid "Exit to Menu" +#~ msgstr "Terug naar menu" + +#~ msgid "Sound Volume" +#~ msgstr "Volume" + +#~ msgid "Change Password" +#~ msgstr "Verander wachtwoord" + +#~ msgid "Continue" +#~ msgstr "Verder spelen" + +#~ msgid "You died." +#~ msgstr "Je bent gestorven." + +#~ msgid "Shutting down stuff..." +#~ msgstr "Stopzetten..." + +#~ msgid "Connecting to server..." +#~ msgstr "Verbinding met de server wordt gemaakt..." + +#~ msgid "Resolving address..." +#~ msgstr "IP-adres opzoeken..." + +#~ msgid "Creating client..." +#~ msgstr "Bezig client te maken..." + +#~ msgid "Creating server...." +#~ msgstr "Bezig server te maken..." + +#~ msgid "Loading..." +#~ msgstr "Bezig met laden..." + +#~ msgid "Local install" +#~ msgstr "Plaatselijk installeren" + +#~ msgid "Add mod:" +#~ msgstr "Mod toevoegen:" + +#~ msgid "MODS" +#~ msgstr "MODS" + +#~ msgid "TEXTURE PACKS" +#~ msgstr "TEXTUREN" + +#~ msgid "SINGLE PLAYER" +#~ msgstr "SINGLEPLAYER" + +#~ msgid "Finite Liquid" +#~ msgstr "Eindige vloeistoffen" + +#~ msgid "Preload item visuals" +#~ msgstr "Voorwerpen vooraf laden" + +#~ msgid "SETTINGS" +#~ msgstr "INSTELLINGEN" + +#~ msgid "Password" +#~ msgstr "Wachtwoord" + +#~ msgid "Name" +#~ msgstr "Naam" + +#~ msgid "START SERVER" +#~ msgstr "START SERVER" + +#~ msgid "Favorites:" +#~ msgstr "Favorieten:" + +#~ msgid "CLIENT" +#~ msgstr "CLIENT" + +#~ msgid "<<-- Add mod" +#~ msgstr "<<-- Mod toevoegen" + +#~ msgid "Remove selected mod" +#~ msgstr "Geselecteerde mod verwijderen" + +#~ msgid "EDIT GAME" +#~ msgstr "SPEL AANPASSEN" + +#~ msgid "new game" +#~ msgstr "nieuw spel" + +#~ msgid "edit game" +#~ msgstr "spel aanpassen" + +#~ msgid "Mods:" +#~ msgstr "Mods:" + +#~ msgid "Games" +#~ msgstr "Spellen" + +#~ msgid "GAMES" +#~ msgstr "SPELLEN" + +#~ msgid "Gamemgr: Unable to copy mod \"$1\" to game \"$2\"" +#~ msgstr "Gamemgr: Kan mod \"$1\" niet naar spel \"$2\" kopiëren" + +#~ msgid "Game Name" +#~ msgstr "Spel" diff --git a/po/pl/minetest.po b/po/pl/minetest.po new file mode 100644 index 0000000..c49c340 --- /dev/null +++ b/po/pl/minetest.po @@ -0,0 +1,1225 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2013-10-08 21:22+0200\n" +"Last-Translator: Maciej Kasatkin \n" +"Language-Team: Polish <>\n" +"Language: pl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " +"|| n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 1.7-dev\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "OK" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "Świat:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "Ukryj Grę" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "Mod:" + +#: builtin/mainmenu/dlg_config_world.lua:48 +msgid "Depends:" +msgstr "Zależy od:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Zapisz" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Anuluj" + +#: builtin/mainmenu/dlg_config_world.lua:68 +#, fuzzy +msgid "Enable MP" +msgstr "Włącz wszystkie" + +#: builtin/mainmenu/dlg_config_world.lua:70 +#, fuzzy +msgid "Disable MP" +msgstr "Wyłącz wszystkie" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "włączone" + +#: builtin/mainmenu/dlg_config_world.lua:82 +msgid "Enable all" +msgstr "Włącz wszystkie" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Nazwa świata" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "Generator mapy" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Gra" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Utwórz" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "Istnieje już świat o nazwie \"$1\"" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "Nie podano nazwy świata lub nie wybrano gry" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "Na pewno usunąć \"$1\"?" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Tak" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "Oczywiście, że nie!" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "Modmgr: nie można usunąć \"$1\"" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "Modmgr: nieprawidłowy katalog \"$1\"" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "Usunąć świat \"$1\"?" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Nie" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "Zmień nazwe Paczki Modów:" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "Accept" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "Zainstaluj mod: plik: \"$1\"" + +#: builtin/mainmenu/modmgr.lua:343 +#, fuzzy +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" +"\n" +"Instalacja moda: nieznany typ pliku \"$1\"" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "Instalacja $1 do $2 nie powiodła się" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" +"Instalacja moda: nie można znaleźć odpowiedniego folderu dla paczki modów $1" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "Instalacja moda: nie można znaleźć nazwy moda $1" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +#, fuzzy +msgid "Downloading" +msgstr "Ściągnij" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +#, fuzzy +msgid "Shortname:" +msgstr "Nazwa świata" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "Ocena" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "Ponowna instalacja" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "Instaluj" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "Strona $1 z $2" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Autorzy" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "Twórcy" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "Aktywni współautorzy" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "Byli współautorzy" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "Zainstalowane Mody:" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:78 +#, fuzzy +msgid "No mod description available" +msgstr "Brak informacjii" + +#: builtin/mainmenu/tab_mods.lua:82 +#, fuzzy +msgid "Mod information:" +msgstr "Brak informacjii" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "Zmień nazwę" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:106 +#, fuzzy +msgid "Uninstall selected mod" +msgstr "Usuń zaznaczony mod" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "Wybierz plik moda:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "Mody" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "Adres/Port" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Nazwa gracza/Hasło" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "Lista publicznych serwerów" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Usuń" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Połącz" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "Klient" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Nowy" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Ustaw" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "Rozpocznij grę/Połącz" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Wybierz świat:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Tryb kreatywny" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Włącz obrażenia" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Publiczne" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "Port Serwera" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "Serwer" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "Płynne oświetlenie" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "Włącz cząstki" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "Chmury 3D" + +#: builtin/mainmenu/tab_settings.lua:140 +#, fuzzy +msgid "Fancy Trees" +msgstr "Ozdobne drzewa" + +#: builtin/mainmenu/tab_settings.lua:142 +msgid "Opaque Water" +msgstr "Nieprzeźroczysta woda" + +#: builtin/mainmenu/tab_settings.lua:144 +#, fuzzy +msgid "Connected Glass" +msgstr "Połącz" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "Mip-Mappowanie" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "Filtrowanie anizotropowe" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "Filtrowanie dwuliniowe" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "Filtrowanie trójliniowe" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "Shadery" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "Zmień klawisze" + +#: builtin/mainmenu/tab_settings.lua:167 +#, fuzzy +msgid "Reset singleplayer world" +msgstr "Pojedynczy gracz" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +#, fuzzy +msgid "Bumpmapping" +msgstr "Mip-Mappowanie" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "Ustawienia" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +#, fuzzy +msgid "Start Singleplayer" +msgstr "Pojedynczy gracz" + +#: builtin/mainmenu/tab_simple_main.lua:72 +#, fuzzy +msgid "Config mods" +msgstr "Ustaw" + +#: builtin/mainmenu/tab_simple_main.lua:191 +#, fuzzy +msgid "Main" +msgstr "Menu główne" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Graj" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Pojedynczy gracz" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "Wybierz paczkę tekstur:" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "Brak informacjii" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +#, fuzzy +msgid "Texturepacks" +msgstr "Paczki tekstur" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "Tekstury przedmiotów..." + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "Wróć do gry" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "Definicje przedmiotów..." + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "Definicje nod..." + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "Media..." + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Sprawdź plik debug.txt by uzyskać więcej informacji." + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "Kontynuuj" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" +"Zdefiniowane klawisze. (Jeżeli to menu nie działa, usuń skonfigurowane " +"klawisze z pliku minetest.conf)" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "\"Użyj\" = wspinaj się" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "Wciśnij dwukrotnie \"Skok\" by włączyć tryb latania" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "Klawisz już zdefiniowany" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "naciśnij klawisz" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Przód" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Tył" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Lewo" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Prawo" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Użyj" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Skok" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Skradanie" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Upuść" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Ekwipunek" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Czat" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Komenda" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Konsola" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Przełącz tryb latania" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Przełącz tryb szybki" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "Przełącz tryb noclip" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "Zasięg widzenia" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "Drukuj stosy" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "Stare hasło" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "Nowe hasło" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "Potwierdź hasło" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "Zmień" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "Hasła nie są jednakowe!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "Głośność: " + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Wyjście" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Lewy przycisk myszy" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Środkowy przycisk myszy" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Prawy przycisk myszy" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "X Button 1" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "Backspace" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "Delete" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "Enter" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tab" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "X Button 2" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "Caps Lock" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Control" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Kana" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Menu" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Pause" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Shift" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "Convert" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Escape" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "Final" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Kanji" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "Nonconvert" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "End" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Home" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "Mode Change" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Next" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Prior" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Spacja" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Dół" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Execute" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Print" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Select" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Góra" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Pomoc" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Insert" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "Snapshot" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "Lewy Windows" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "Apps" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Numpad 0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Numpad 1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "Prawy Windows" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Sleep" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Numpad 2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Numpad 3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Numpad 4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Numpad 5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Numpad 6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Numpad 7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Numpad *" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Numpad +" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Numpad -" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Numpad /" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Numpad 8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Numpad 9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Num Lock" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Scroll Lock" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Lewy Shift" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Prawy Shift" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Lewy Control" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Lewy Menu" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Prawy Control" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Prawy Menu" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Przecinek" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Minus" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "Kropka" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Plus" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "Attn" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "CrSel" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "Erase OEF" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "ExSel" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "OEM Clear" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PA1" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "Zoom" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "Menu główne" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "Błąd połączenia (brak odpowiedzi?)" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "Nie wybrano świata ani adresu." + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "Nie można znaleźć lub wczytać trybu gry \"" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "Nieprawidłowa specyfikacja trybu gry." + +#~ msgid "Left click: Move all items, Right click: Move single item" +#~ msgstr "" +#~ "Lewy przycisk myszy: przenieś wszystkie przedmioty, Prawy przycisk myszy: " +#~ "przenieś pojedynczy przedmiot" + +#~ msgid "is required by:" +#~ msgstr "wymagane przez:" + +#~ msgid "Configuration saved. " +#~ msgstr "Konfiguracja zapisana. " + +#~ msgid "Warning: Configuration not consistent. " +#~ msgstr "Ostrzeżenie: Plik konfiguracyjny niespójny. " + +#~ msgid "Cannot create world: Name contains invalid characters" +#~ msgstr "Nie można stworzyć świata: Nazwa zawiera niedozwolone znaki" + +#~ msgid "Multiplayer" +#~ msgstr "Gra wieloosobowa" + +#~ msgid "Advanced" +#~ msgstr "Zaawansowane" + +#~ msgid "Show Public" +#~ msgstr "Pokaż publiczne" + +#~ msgid "Show Favorites" +#~ msgstr "Pokaż ulubione" + +#~ msgid "Leave address blank to start a local server." +#~ msgstr "Pozostaw pole adresu puste, by uruchomić serwer lokalny." + +#~ msgid "Create world" +#~ msgstr "Stwórz świat" + +#~ msgid "Address required." +#~ msgstr "Wymagany adres." + +#~ msgid "Cannot delete world: Nothing selected" +#~ msgstr "Nie można skasować świata: nic nie zaznaczono" + +#~ msgid "Files to be deleted" +#~ msgstr "Pliki do skasowania" + +#~ msgid "Cannot create world: No games found" +#~ msgstr "Nie można utworzyć świata: Nie znaleziono żadnego trybu gry" + +#~ msgid "Cannot configure world: Nothing selected" +#~ msgstr "Nie można skonfigurować świata: Nic nie zaznaczono" + +#~ msgid "Failed to delete all world files" +#~ msgstr "Nie udało się skasować wszystkich plików świata" + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: Walk\n" +#~ "- Mouse left: dig/hit\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- 0...9: select item\n" +#~ "- Shift: sneak\n" +#~ "- R: Toggle viewing all loaded chunks\n" +#~ "- I: Inventory menu\n" +#~ "- ESC: This menu\n" +#~ "- T: Chat\n" +#~ msgstr "" +#~ "Domyślne ustawienia:\n" +#~ "- WASD: poruszanie\n" +#~ "- Lewy przycisk myszki: kop/uderz\n" +#~ "- Prawy przycisk myszki: połóż/użyj\n" +#~ "- Kółko myszy: wybieranie przedmiotu\n" +#~ "- 0...9: wybieranie przedmiotu\n" +#~ "- Shift: skradanie\n" +#~ "- R: przełączanie trybu widoczności\n" +#~ "- I: menu ekwipunku\n" +#~ "- ESC: to menu\n" +#~ "- T: czat\n" + +#~ msgid "" +#~ "Warning: Some configured mods are missing.\n" +#~ "Their setting will be removed when you save the configuration. " +#~ msgstr "" +#~ "Ostrzeżenie: Niektóre z modyfikacji nie zostały znalezione.\n" +#~ "Ich ustawienia zostaną usunięte gdy zapiszesz konfigurację. " + +#~ msgid "" +#~ "Warning: Some mods are not configured yet.\n" +#~ "They will be enabled by default when you save the configuration. " +#~ msgstr "" +#~ "Uwaga: Niektóre z modyfikacji nie zostały jeszcze skonfigurowane.\n" +#~ "Zostaną domyślnie włączone gdy zapiszesz konfigurację. " + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: move\n" +#~ "- Space: jump/climb\n" +#~ "- Shift: sneak/go down\n" +#~ "- Q: drop item\n" +#~ "- I: inventory\n" +#~ "- Mouse: turn/look\n" +#~ "- Mouse left: dig/punch\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- T: chat\n" +#~ msgstr "" +#~ "Domyślne sterowanie:↵\n" +#~ "- WASD: ruch↵\n" +#~ "- Spacja: skok/wspinanie się↵\n" +#~ "- Shift: skradanie się/schodzenie w dół↵\n" +#~ "- Q: upuszczenie przedmiotu↵\n" +#~ "- I: ekwipunek↵\n" +#~ "- Mysz: obracanie się/patrzenie↵\n" +#~ "- Lewy przycisk myszy: kopanie/uderzanie↵\n" +#~ "- Prawy przycisk myszy: postawienie/użycie przedmiotu↵\n" +#~ "- Rolka myszy: wybór przedmiotu↵\n" +#~ "- T: chat\n" + +#~ msgid "Exit to OS" +#~ msgstr "Wyjście z gry" + +#~ msgid "Exit to Menu" +#~ msgstr "Wyjście do menu" + +#~ msgid "Sound Volume" +#~ msgstr "Głośność" + +#~ msgid "Change Password" +#~ msgstr "Zmień hasło" + +#~ msgid "Continue" +#~ msgstr "Dalej" + +#~ msgid "You died." +#~ msgstr "Zginąłeś." + +#~ msgid "Shutting down stuff..." +#~ msgstr "Wyłączanie..." + +#~ msgid "Connecting to server..." +#~ msgstr "Łączenie z serwerem..." + +#~ msgid "Resolving address..." +#~ msgstr "Sprawdzanie adresu..." + +#~ msgid "Creating client..." +#~ msgstr "Tworzenie klienta..." + +#~ msgid "Creating server...." +#~ msgstr "Tworzenie serwera...." + +#~ msgid "Loading..." +#~ msgstr "Ładowanie..." + +#, fuzzy +#~ msgid "Local install" +#~ msgstr "Instaluj" + +#, fuzzy +#~ msgid "Add mod:" +#~ msgstr "<<--Dodaj mod" + +#~ msgid "MODS" +#~ msgstr "MODY" + +#~ msgid "TEXTURE PACKS" +#~ msgstr "PACZKI TEKSTUR" + +#~ msgid "SINGLE PLAYER" +#~ msgstr "TRYB JEDNOOSOBOWY" + +#~ msgid "Finite Liquid" +#~ msgstr "Realistyczne ciecze" + +#~ msgid "Preload item visuals" +#~ msgstr "Ładuj obrazy przedmiotów" + +#~ msgid "SETTINGS" +#~ msgstr "USTAWIENIA" + +#~ msgid "Password" +#~ msgstr "Hasło" + +#~ msgid "Name" +#~ msgstr "Nazwa" + +#~ msgid "START SERVER" +#~ msgstr "URUCHOM SERWER" + +#~ msgid "Favorites:" +#~ msgstr "Ulubione:" + +#~ msgid "CLIENT" +#~ msgstr "KLIENT" + +#~ msgid "<<-- Add mod" +#~ msgstr "<<--Dodaj mod" + +#~ msgid "Remove selected mod" +#~ msgstr "Usuń zaznaczony mod" + +#~ msgid "EDIT GAME" +#~ msgstr "EDYTUJ GRĘ" + +#~ msgid "new game" +#~ msgstr "nowa gra" + +#~ msgid "edit game" +#~ msgstr "edytuj grę" + +#~ msgid "Mods:" +#~ msgstr "Mody:" + +#~ msgid "Games" +#~ msgstr "Gry" + +#~ msgid "GAMES" +#~ msgstr "GRY" + +#~ msgid "Gamemgr: Unable to copy mod \"$1\" to game \"$2\"" +#~ msgstr "Gamemgr: Kopiowanie moda \"$1\" do gry \"$2\" nie powiodło się" + +#~ msgid "Game Name" +#~ msgstr "Nazwa Gry" diff --git a/po/pt/minetest.po b/po/pt/minetest.po new file mode 100644 index 0000000..24680e2 --- /dev/null +++ b/po/pt/minetest.po @@ -0,0 +1,1223 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2014-01-06 01:45+0200\n" +"Last-Translator: João Farias \n" +"Language-Team: LANGUAGE \n" +"Language: pt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 1.7-dev\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "Ok" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "Mundo:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "Esconder Jogo" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "Extra:" + +#: builtin/mainmenu/dlg_config_world.lua:48 +msgid "Depends:" +msgstr "Depende de:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Guardar" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Cancelar" + +#: builtin/mainmenu/dlg_config_world.lua:68 +#, fuzzy +msgid "Enable MP" +msgstr "Ativar Tudo" + +#: builtin/mainmenu/dlg_config_world.lua:70 +#, fuzzy +msgid "Disable MP" +msgstr "Desativar Tudo" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "ativo" + +#: builtin/mainmenu/dlg_config_world.lua:82 +#, fuzzy +msgid "Enable all" +msgstr "Ativar Tudo" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Nome do Mundo" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "Geração de Mapa" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Jogo" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Criar" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "O mundo com o nome \"$1\" já existe" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "Mundo sem nome ou nenhum jogo selecionado" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "Tem a certeza que pertende eliminar \"$1\"?" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Sim" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "Não, é claro que não!" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "Mensagem de Extra: falhou a eliminação de \"$1\"" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "Mensagem de extra: caminho inválido \"$1\"" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "Eliminar Mundo \"$1\"?" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Não" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "Renomear Pacote de Extras:" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "Aceitar" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "Instalar Extra: ficheiro: \"$1\"" + +#: builtin/mainmenu/modmgr.lua:343 +#, fuzzy +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" +"\n" +"Instalar Extra: tipo de ficheiro desconhecido \"$1\"" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "Falha ao instalar de $1 ao $2" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +#, fuzzy +msgid "Downloading" +msgstr "Descarregar" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +#, fuzzy +msgid "Shortname:" +msgstr "Nome do Mundo" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "Classificação" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "re-Instalar" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "Instalar" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "Página $1 de $2" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Créditos" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "Desenvolvedores Chave" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "Contribuintes Ativos" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "Antigos Contribuintes" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "Extras Instalados:" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "Repositório Online de Mods" + +#: builtin/mainmenu/tab_mods.lua:78 +#, fuzzy +msgid "No mod description available" +msgstr "Sem informação" + +#: builtin/mainmenu/tab_mods.lua:82 +#, fuzzy +msgid "Mod information:" +msgstr "Sem informação" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "Renomear" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "Desinstalar mode selecionado" + +#: builtin/mainmenu/tab_mods.lua:106 +#, fuzzy +msgid "Uninstall selected mod" +msgstr "Remover extra selecionado" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "Seleccionar ficheiro de Extra:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "Extras" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "Endereço/Porta" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Nome/Senha" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "Lista de Servidores Públicos" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Eliminar" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Ligar" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "Cliente" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Novo" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Configurar" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "Jogar" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Seleccionar Mundo:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Modo Criativo" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Ativar Dano" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Público" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "Porta" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "Servidor" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "Iluminação Suave" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "Ativar Partículas" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "Nuvens 3D" + +#: builtin/mainmenu/tab_settings.lua:140 +#, fuzzy +msgid "Fancy Trees" +msgstr "Árvores Melhoradas" + +#: builtin/mainmenu/tab_settings.lua:142 +msgid "Opaque Water" +msgstr "Água Opaca" + +#: builtin/mainmenu/tab_settings.lua:144 +#, fuzzy +msgid "Connected Glass" +msgstr "Ligar" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "Mip-Mapping" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "Filtro Anisotrópico" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "Filtro Bi-Linear" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "Filtro Tri-Linear" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "Sombras" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "Mudar teclas" + +#: builtin/mainmenu/tab_settings.lua:167 +#, fuzzy +msgid "Reset singleplayer world" +msgstr "Um Jogador" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +#, fuzzy +msgid "Bumpmapping" +msgstr "Mip-Mapping" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "Definições" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +#, fuzzy +msgid "Start Singleplayer" +msgstr "Um Jogador" + +#: builtin/mainmenu/tab_simple_main.lua:72 +#, fuzzy +msgid "Config mods" +msgstr "Configurar" + +#: builtin/mainmenu/tab_simple_main.lua:191 +#, fuzzy +msgid "Main" +msgstr "Menu Principal" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Jogar" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Um Jogador" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "Selecione um pacote de texturas:" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "Sem informação" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +#, fuzzy +msgid "Texturepacks" +msgstr "Pacotes de Texturas" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "Texturas dos items..." + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "Reaparecer" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "Definições dos Itens..." + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "" + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "Dados..." + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Consulte debug.txt para mais detalhes." + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "Continuar" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "Teclas. (Se este menu se estragar, remova as linhas do minetest.conf)" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "\"Use\" = descer" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "Carregue duas vezes em \"saltar\" para ativar o vôo" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "Tecla já em uso" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "pressione a tecla" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Avançar" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Recuar" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Esquerda" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Direita" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Usar" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Saltar" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Agachar" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Largar" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Inventário" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Conversa" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Comando" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Consola" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Ativar/Desativar vôo" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Ativar/Desativar correr" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "Ativar/Desativar noclip" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "Seleccionar Distância" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "Imprimir stacks" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "Senha antiga" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "Senha Nova" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "Confirmar Senha" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "Mudar" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "Senhas não correspondem!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "Volume do som: " + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Sair" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Botão Esquerdo" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Roda do Rato" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Botão Direito" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "Botão X 1" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "Voltar" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "Limpar" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "Enter" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tabulação" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "Botão X 2" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "Capital" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Control" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Kana" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Menu" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Pausa" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Shift" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "Converter" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "ESC" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "Final" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Kanji" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "Nãoconverter" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "End" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Home" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "Mode Change" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Próximo" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Prévio" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Espaço" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Baixo" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Executar" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Print" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Seleccionar" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Cima" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Ajuda" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Insert" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "Screenshot" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "WINDOWS Esq." + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "App" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Numpad 0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Numpad 1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "WINDOWS Dir." + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Suspender" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Numpad 2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Numpad 3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Numpad 4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Numpad 5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Numpad 6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Numpad 7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Numpad *" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Numpad +" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Numpad -" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Numpad /" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Numpad 8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Numpad 9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Num Lock" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Scroll Lock" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Shift Esquerdo" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Shift Direito" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Control Esq" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Menu Esquerdo" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Control Direito" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Menu Direito" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Virgula" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Menos" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "Período" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Mais" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "Attm" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "CrSel" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "Apagar OEF" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "ExSel" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "Limpar OEM" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PAL" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "Zoom" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "Menu Principal" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "Erro de conexão (excedeu tempo?)" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "" +"Nenhum mundo seleccionado e nenhum endereço providenciado. Nada para fazer." + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "Não foi possível encontrar ou carregar jogo \"" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "gamespec inválido." + +#~ msgid "Left click: Move all items, Right click: Move single item" +#~ msgstr "Botão esq: Mover todos os itens Botão dir: Mover um item" + +#~ msgid "is required by:" +#~ msgstr "é necessário pelo:" + +#~ msgid "Configuration saved. " +#~ msgstr "Configuração gravada. " + +#~ msgid "Warning: Configuration not consistent. " +#~ msgstr "Atenção: Configuração não compatível. " + +#~ msgid "Cannot create world: Name contains invalid characters" +#~ msgstr "Não foi possível criar mundo: Nome com caracteres inválidos" + +#~ msgid "Multiplayer" +#~ msgstr "Vários jogadores" + +#~ msgid "Advanced" +#~ msgstr "Avançado" + +#~ msgid "Show Public" +#~ msgstr "Mostrar Públicos" + +#~ msgid "Show Favorites" +#~ msgstr "Mostrar Favoritos" + +#~ msgid "Leave address blank to start a local server." +#~ msgstr "Deixe endereço em branco para iniciar servidor local." + +#~ msgid "Create world" +#~ msgstr "Criar mundo" + +#~ msgid "Address required." +#~ msgstr "Endereço necessário." + +#~ msgid "Cannot delete world: Nothing selected" +#~ msgstr "Não foi possível eliminar mundo: Nada seleccionado" + +#~ msgid "Files to be deleted" +#~ msgstr "Ficheiros para eliminar" + +#~ msgid "Cannot create world: No games found" +#~ msgstr "Não foi possível criar mundo: Não foram detectados jogos" + +#~ msgid "Cannot configure world: Nothing selected" +#~ msgstr "Não foi possível configurar mundo: Nada seleccionado" + +#~ msgid "Failed to delete all world files" +#~ msgstr "Falhou a remoção de todos os ficheiros dos mundos" + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: Walk\n" +#~ "- Mouse left: dig/hit\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- 0...9: select item\n" +#~ "- Shift: sneak\n" +#~ "- R: Toggle viewing all loaded chunks\n" +#~ "- I: Inventory menu\n" +#~ "- ESC: This menu\n" +#~ "- T: Chat\n" +#~ msgstr "" +#~ "Controlos Normais:\n" +#~ "- WASD: Andar\n" +#~ "- Botão esq.: partir/atacar\n" +#~ "- Botão dir.: colocar/usar\n" +#~ "- Roda do Rato: seleccionar item\n" +#~ "- 0...9: seleccionar item\n" +#~ "- Shift: agachar\n" +#~ "- R: Mudar visualização de todos os chunks\n" +#~ "- I: Inventário\n" +#~ "- ESC: Este menu\n" +#~ "- T: Chat\n" + +#~ msgid "" +#~ "Warning: Some configured mods are missing.\n" +#~ "Their setting will be removed when you save the configuration. " +#~ msgstr "" +#~ "Atenção: Alguns mods configurados estão em falta.\n" +#~ "As suas definições vão ser removidas quando gravar a configuração. " + +#~ msgid "" +#~ "Warning: Some mods are not configured yet.\n" +#~ "They will be enabled by default when you save the configuration. " +#~ msgstr "" +#~ "Atenção: Alguns mods ainda não estão configurados.\n" +#~ "Eles vão ser ativados por predefinição quando guardar a configuração. " + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: move\n" +#~ "- Space: jump/climb\n" +#~ "- Shift: sneak/go down\n" +#~ "- Q: drop item\n" +#~ "- I: inventory\n" +#~ "- Mouse: turn/look\n" +#~ "- Mouse left: dig/punch\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- T: chat\n" +#~ msgstr "" +#~ "Teclas por defeito:\n" +#~ "- WASD: mover\n" +#~ "- Barra de espaço: saltar/subir\n" +#~ "- Shift: andar cuidadosamente/descer\n" +#~ "- Q: Largar item\n" +#~ "- I: Inventário\n" +#~ "- Rato: virar/olhar\n" +#~ "- Clique esquerdo: cavar/bater\n" +#~ "- Clique direito: colocar/utilizar\n" +#~ "- Roda do rato: seleccionar item\n" +#~ "- T: conversação\n" + +#~ msgid "Exit to OS" +#~ msgstr "Sair para o sistema" + +#~ msgid "Exit to Menu" +#~ msgstr "Sair para Menu" + +#~ msgid "Sound Volume" +#~ msgstr "Volume do som" + +#~ msgid "Change Password" +#~ msgstr "Mudar Senha" + +#~ msgid "Continue" +#~ msgstr "Continuar" + +#~ msgid "You died." +#~ msgstr "Morreste." + +#~ msgid "Shutting down stuff..." +#~ msgstr "A desligar..." + +#~ msgid "Connecting to server..." +#~ msgstr "A conectar ao servidor..." + +#~ msgid "Resolving address..." +#~ msgstr "A resolver endereço..." + +#~ msgid "Creating client..." +#~ msgstr "A criar cliente..." + +#~ msgid "Creating server...." +#~ msgstr "A criar servidor..." + +#~ msgid "Loading..." +#~ msgstr "A carregar..." + +#, fuzzy +#~ msgid "Local install" +#~ msgstr "Instalar" + +#, fuzzy +#~ msgid "Add mod:" +#~ msgstr "<<-- Adicionar extra" + +#~ msgid "MODS" +#~ msgstr "EXTRAS" + +#~ msgid "TEXTURE PACKS" +#~ msgstr "PACOTES DE TEXTURAS" + +#~ msgid "SINGLE PLAYER" +#~ msgstr "Um Jogador" + +#~ msgid "Finite Liquid" +#~ msgstr "Líquido Finito" + +#~ msgid "Preload item visuals" +#~ msgstr "Pré-carregamento dos itens" + +#~ msgid "SETTINGS" +#~ msgstr "DEFINIÇÕES" + +#~ msgid "Password" +#~ msgstr "Senha" + +#~ msgid "Name" +#~ msgstr "Nome" + +#~ msgid "START SERVER" +#~ msgstr "INICIAR SERVIDOR" + +#~ msgid "Favorites:" +#~ msgstr "Favoritos:" + +#~ msgid "CLIENT" +#~ msgstr "CLIENTE" + +#~ msgid "<<-- Add mod" +#~ msgstr "<<-- Adicionar extra" + +#~ msgid "Remove selected mod" +#~ msgstr "Remover extra selecionado" + +#~ msgid "EDIT GAME" +#~ msgstr "EDITAR JOGO" + +#~ msgid "new game" +#~ msgstr "novo jogo" + +#~ msgid "edit game" +#~ msgstr "editar jogo" + +#~ msgid "Mods:" +#~ msgstr "Extras:" + +#~ msgid "Games" +#~ msgstr "Jogos" + +#~ msgid "GAMES" +#~ msgstr "JOGOS" + +#~ msgid "Gamemgr: Unable to copy mod \"$1\" to game \"$2\"" +#~ msgstr "" +#~ "Mensagem de Jogo: Impossível fazer cópia do extra \"$1\" para o jogo " +#~ "\"$2\"" + +#~ msgid "Game Name" +#~ msgstr "Nome do Jogo" diff --git a/po/pt_BR/minetest.po b/po/pt_BR/minetest.po new file mode 100644 index 0000000..b8b68a6 --- /dev/null +++ b/po/pt_BR/minetest.po @@ -0,0 +1,1220 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2013-11-25 02:13+0200\n" +"Last-Translator: Frederico Guimarães \n" +"Language-Team: LANGUAGE \n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 1.7-dev\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "Ok" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "Mundo:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "Ocultar jogos" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "Ocultar conteúdo PMs" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "Mod:" + +#: builtin/mainmenu/dlg_config_world.lua:48 +msgid "Depends:" +msgstr "Depende de:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Salvar" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Cancelar" + +#: builtin/mainmenu/dlg_config_world.lua:68 +msgid "Enable MP" +msgstr "Habilitar PMs" + +#: builtin/mainmenu/dlg_config_world.lua:70 +msgid "Disable MP" +msgstr "Desabilitar PMs" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "habilitado" + +#: builtin/mainmenu/dlg_config_world.lua:82 +msgid "Enable all" +msgstr "Habilitar todos" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Nome do mundo" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "Seed" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "Mapgen" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Jogo" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Criar" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "Já existe um mundo com o nome \"$1\"" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "" +"Não foi fornecido nenhum nome para o mundo ou não foi selecionado nenhum jogo" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "Tem certeza que deseja excluir \"$1\"?" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Sim" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "Claro que não!" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "Modmgr: não foi possível excluir \"$1\"" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "Modmgr: caminho inválido do módulo \"$1\"" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "Excluir o mundo \"$1\"?" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Não" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "Renomear pacote de módulos:" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "Aceitar" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "Instalação de módulo: arquivo: \"$1\"" + +#: builtin/mainmenu/modmgr.lua:343 +#, fuzzy +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" +"\n" +"Instalação de módulo: o tipo de arquivo \"$1\" não é suportado" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "Não foi possível instalar $1 em $2" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" +"Instalação de módulo: não foi possível encontrar o nome adequado da pasta " +"para o pacote de módulos $1" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "" +"Instalação de módulo: não foi possível encontrar o nome real do módulo: $1" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +#, fuzzy +msgid "Downloading" +msgstr "Baixar" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +#, fuzzy +msgid "Shortname:" +msgstr "Nome do mundo" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "Classificação" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "reinstalar" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "Instalar" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "Página $1 de $2" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Créditos" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "Desenvolvedores principais" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "Colaboradores ativos" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "Colaboradores anteriores" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "Módulos instalados:" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "Repositório de módulos online" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "Nenhuma descrição disponível do módulo" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "Informação do módulo:" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "Renomear" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "Desinstalar o pacote de módulos selecionado" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "Desinstalar o módulo selecionado" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "Selecione o arquivo do módulo:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "Módulos" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "Endereço/Porta" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Nome/Senha" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "Servidores públicos" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Excluir" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Conectar" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "Cliente" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Novo" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Configurar" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "Iniciar o jogo" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Selecione um mundo:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Modo criativo" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Habilitar dano" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Público" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "Porta do servidor" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "Servidor" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "Iluminação suave" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "Habilitar partículas" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "Nuvens 3D" + +#: builtin/mainmenu/tab_settings.lua:140 +#, fuzzy +msgid "Fancy Trees" +msgstr "Árvores melhores" + +#: builtin/mainmenu/tab_settings.lua:142 +msgid "Opaque Water" +msgstr "Água opaca" + +#: builtin/mainmenu/tab_settings.lua:144 +#, fuzzy +msgid "Connected Glass" +msgstr "Conectar" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "Mipmapping" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "Filtragem anisotrópica" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "Filtragem bi-linear" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "Filtragem tri-linear" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "Sombreadores" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "Mudar teclas" + +#: builtin/mainmenu/tab_settings.lua:167 +#, fuzzy +msgid "Reset singleplayer world" +msgstr "Um jogador" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +#, fuzzy +msgid "Bumpmapping" +msgstr "Mipmapping" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "Para habilitar os sombreadores é necessário usar o driver OpenGL." + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "Configurações" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +#, fuzzy +msgid "Start Singleplayer" +msgstr "Um jogador" + +#: builtin/mainmenu/tab_simple_main.lua:72 +#, fuzzy +msgid "Config mods" +msgstr "Configurar" + +#: builtin/mainmenu/tab_simple_main.lua:191 +#, fuzzy +msgid "Main" +msgstr "Menu principal" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Jogar" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Um jogador" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "Selecione o pacote de texturas:" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "Nenhuma informação disponível" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +#, fuzzy +msgid "Texturepacks" +msgstr "Pacotes de texturas" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "Texturas dos itens..." + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "needs_fallback_font" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "Reviver" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "Definições dos itens..." + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "Definições dos nós..." + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "Mídia..." + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Verifique o debug.txt para mais detalhes." + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "Continuar" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" +"Teclas (se este menu estiver com problema, remova itens do arquivo minetest." +"conf)" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "\"Usar\" = descer" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "\"Pular\" duas vezes ativa o voo" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "Essa tecla já está em uso" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "press. uma tecla" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Avançar" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Voltar" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Esquerda" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Direita" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Usar" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Pular" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Esgueirar" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Soltar" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Inventário" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Bate-papo" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Comando" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Console" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Alternar voo" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Alternar corrida" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "Alternar noclip" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "Sel. distância" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "Impr. pilha (log)" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "Senha antiga" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "Nova senha" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "Confirmar a senha" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "Alterar" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "As senhas não correspondem!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "Volume do som: " + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Sair" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Botão esquerdo" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Roda do mouse" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Botão direito" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "Botão X 1" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "Backspace" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "Limpar" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "Enter" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tab" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "Botão X 2" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "Caps Lock" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Ctrl" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Kana" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Menu" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Pause" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Shift" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "Convert" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Esc" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "Final" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Kanji" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "Nonconvert" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "End" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Home" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "Mode Change" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Page Down" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Page Up" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Espaço" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Abaixo" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Executar" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Print Screen" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Select" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Acima" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Ajuda" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Insert" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "Captura de tela" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "Windows esq." + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "Apps" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Tecl.num.0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Tecl.num.1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "Windows dir." + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Sleep" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Tecl.num.2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Tecl.num.3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Tecl.num.4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Tecl.num.5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Tecl.num.6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Tecl.num.7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Tecl.num.*" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Tecl.num.+" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Tecl.num.-" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Tecl.num./" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Tecl.num.8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Tecl.num.9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Num Lock" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Scroll Lock" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Shift esq." + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Shift dir." + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Ctrl esq." + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Menu esq." + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Ctrl dir." + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Menu dir." + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Vírgula" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Menos" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "Ponto" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Mais" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "Attn" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "CrSel" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "Apagar OEF" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "ExSel" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "Limpar OEM" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PA1" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "Zoom" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "Menu principal" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "Erro de conexão (tempo excedido?)" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "" +"Nenhum mundo foi selecionado e nenhum endereço fornecido. Não existe nada a " +"ser feito." + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "Não foi possível localizar ou carregar jogo \"" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "Especificação do jogo inválida." + +#~ msgid "Left click: Move all items, Right click: Move single item" +#~ msgstr "Botão esquerdo: Move todos os itens. Botão direito: Move um item" + +#~ msgid "is required by:" +#~ msgstr "é necessário para:" + +#~ msgid "Configuration saved. " +#~ msgstr "A configuração foi salva. " + +#~ msgid "Warning: Configuration not consistent. " +#~ msgstr "Atenção: A configuração não está consistente." + +#~ msgid "Cannot create world: Name contains invalid characters" +#~ msgstr "Não foi possível criar o mundo: O nome contém caracteres inválidos" + +#~ msgid "Multiplayer" +#~ msgstr "Vários jogadores" + +#~ msgid "Advanced" +#~ msgstr "Avançado" + +#~ msgid "Show Public" +#~ msgstr "Exibir públicos" + +#~ msgid "Show Favorites" +#~ msgstr "Exibir favoritos" + +#~ msgid "Leave address blank to start a local server." +#~ msgstr "Deixe o endereço em branco para iniciar um servidor local." + +#~ msgid "Create world" +#~ msgstr "Criar o mundo" + +#~ msgid "Address required." +#~ msgstr "É necessário um endereço." + +#~ msgid "Cannot delete world: Nothing selected" +#~ msgstr "Não foi possível excluir o mundo: Nenhum foi selecionado" + +#~ msgid "Files to be deleted" +#~ msgstr "Arquivos a serem excluídos" + +#~ msgid "Cannot create world: No games found" +#~ msgstr "Não foi possivel criar o mundo: Não foi encontrado nenhum jogo" + +#~ msgid "Cannot configure world: Nothing selected" +#~ msgstr "Não foi possível configurar o mundo: Nada foi selecionado" + +#~ msgid "Failed to delete all world files" +#~ msgstr "Não foi possível excluir todos os arquivos do mundo" + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: Walk\n" +#~ "- Mouse left: dig/hit\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- 0...9: select item\n" +#~ "- Shift: sneak\n" +#~ "- R: Toggle viewing all loaded chunks\n" +#~ "- I: Inventory menu\n" +#~ "- ESC: This menu\n" +#~ "- T: Chat\n" +#~ msgstr "" +#~ "Controles padrão:\n" +#~ "- WASD: andar\n" +#~ "- Botão esquerdo: cavar/golpear\n" +#~ "- Botão direito: colocar/usar\n" +#~ "- Roda do mouse: selecionar item\n" +#~ "- 0...9: selecionar item\n" +#~ "- Shift: esgueirar\n" +#~ "- R: alternar a distância de visualização\n" +#~ "- I: inventário\n" +#~ "- ESC: este menu\n" +#~ "- T: bate-papo\n" + +#~ msgid "" +#~ "Warning: Some configured mods are missing.\n" +#~ "Their setting will be removed when you save the configuration. " +#~ msgstr "" +#~ "Atenção: Alguns mods configurados não foram encontrados.\n" +#~ "Suas definições serão removidas quando você salvar a configuração." + +#~ msgid "" +#~ "Warning: Some mods are not configured yet.\n" +#~ "They will be enabled by default when you save the configuration. " +#~ msgstr "" +#~ "Atenção: Alguns mods ainda não foram configurados.\n" +#~ "E eles serão ativados por padrão, quando você salvar a configuração." + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: move\n" +#~ "- Space: jump/climb\n" +#~ "- Shift: sneak/go down\n" +#~ "- Q: drop item\n" +#~ "- I: inventory\n" +#~ "- Mouse: turn/look\n" +#~ "- Mouse left: dig/punch\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- T: chat\n" +#~ msgstr "" +#~ "Controles padrão:\n" +#~ "- WASD: mover\n" +#~ "- Espaço: pular/escalar\n" +#~ "- Shift: esgueirar/descer\n" +#~ "- Q: descartar o item\n" +#~ "- I: inventário\n" +#~ "- Mouse: virar/olhar\n" +#~ "- Botão esquerdo: cavar/atingir\n" +#~ "- Botão direito: colocar/usar\n" +#~ "- Roda: selecionar item\n" +#~ "- T: bate-papo\n" + +#~ msgid "Exit to OS" +#~ msgstr "Sair do Minetest" + +#~ msgid "Exit to Menu" +#~ msgstr "Sair para o menu" + +#~ msgid "Sound Volume" +#~ msgstr "Volume do som" + +#~ msgid "Change Password" +#~ msgstr "Alterar a senha" + +#~ msgid "Continue" +#~ msgstr "Continuar" + +#~ msgid "You died." +#~ msgstr "Você morreu." + +#~ msgid "Shutting down stuff..." +#~ msgstr "Desligando tudo..." + +#~ msgid "Connecting to server..." +#~ msgstr "Conectando ao servidor..." + +#~ msgid "Resolving address..." +#~ msgstr "Resolvendo os endereços..." + +#~ msgid "Creating client..." +#~ msgstr "Criando o cliente..." + +#~ msgid "Creating server...." +#~ msgstr "Criando o servidor..." + +#~ msgid "Loading..." +#~ msgstr "Carregando..." + +#~ msgid "Local install" +#~ msgstr "Instalação local" + +#~ msgid "Add mod:" +#~ msgstr "Adicionar módulo:" + +#~ msgid "MODS" +#~ msgstr "MÓDULOS" + +#~ msgid "TEXTURE PACKS" +#~ msgstr "TEXTURAS" + +#~ msgid "SINGLE PLAYER" +#~ msgstr "UM JOGADOR" + +#~ msgid "Finite Liquid" +#~ msgstr "Líquido finito" + +#~ msgid "Preload item visuals" +#~ msgstr "Precarga de elementos visuais" + +#~ msgid "SETTINGS" +#~ msgstr "CONFIGURAÇÕES" + +#~ msgid "Password" +#~ msgstr "Senha" + +#~ msgid "Name" +#~ msgstr "Nome" + +#~ msgid "START SERVER" +#~ msgstr "SERVIDOR" + +#~ msgid "Favorites:" +#~ msgstr "Favoritos:" + +#~ msgid "CLIENT" +#~ msgstr "CLIENTE" + +#~ msgid "<<-- Add mod" +#~ msgstr "<<-- Adicionar módulo" + +#~ msgid "Remove selected mod" +#~ msgstr "Remover o módulo selecionado" + +#~ msgid "EDIT GAME" +#~ msgstr "EDITAR JOGO" + +#~ msgid "new game" +#~ msgstr "novo jogo" + +#~ msgid "edit game" +#~ msgstr "editar o jogo" + +#~ msgid "Mods:" +#~ msgstr "Módulos:" + +#~ msgid "Games" +#~ msgstr "Jogos" + +#~ msgid "GAMES" +#~ msgstr "JOGOS" + +#~ msgid "Gamemgr: Unable to copy mod \"$1\" to game \"$2\"" +#~ msgstr "Gamemgr: Não foi possível copiar o mod \"$1\" para o jogo \"$2\"" + +#~ msgid "Game Name" +#~ msgstr "Nome do jogo" diff --git a/po/ro/minetest.po b/po/ro/minetest.po new file mode 100644 index 0000000..eefdd5f --- /dev/null +++ b/po/ro/minetest.po @@ -0,0 +1,1130 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2013-12-18 21:44+0200\n" +"Last-Translator: King Artur \n" +"Language-Team: LANGUAGE \n" +"Language: ro\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < " +"20)) ? 1 : 2;\n" +"X-Generator: Weblate 1.7-dev\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "Ok" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "Lume:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "Ascunde Joc" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "Ascunde conținutul mp" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "Mod:" + +#: builtin/mainmenu/dlg_config_world.lua:48 +msgid "Depends:" +msgstr "Dependințe:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Salvează" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Anulează" + +#: builtin/mainmenu/dlg_config_world.lua:68 +msgid "Enable MP" +msgstr "Activează MP" + +#: builtin/mainmenu/dlg_config_world.lua:70 +msgid "Disable MP" +msgstr "Dezactivează MP" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "activat" + +#: builtin/mainmenu/dlg_config_world.lua:82 +msgid "Enable all" +msgstr "Activează tot" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Numele lumii" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "Seminţe" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "Mapgen" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Joc" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Creează" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "O lume cu numele \"$1\" deja există" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "Jocul nu are nume, sau nu ai selectat un joc" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "Ești sigur că vrei să ștergi \"$1\"?" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Da" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "Nu, sigur că nu!" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "Modmgr: Eroare la ștergerea \"$1\"" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "Modmgr: Pacht de mod invalid \"$1\"" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "Ștergi lumea \"$1\"?" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Nu" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "Redenumiți Pachetul de moduri:" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "Acceptă" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "Instalare Mod: fișier: \"$1\"" + +#: builtin/mainmenu/modmgr.lua:343 +#, fuzzy +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" +"\n" +"Instalare Mod: tip de fișier neacceptat \"$1\"" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "Eșuare la instalarea $1 în $2" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" +"Instalare Mod: nu se poate găsi nume de folder potrivit pentru pachetul de " +"mod $1" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "Instalare mod: nu se poate găsi numele real pentru: $1" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +#, fuzzy +msgid "Downloading" +msgstr "Descarcă" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +#, fuzzy +msgid "Shortname:" +msgstr "Numele lumii" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "Notă" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "Reinstalează" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "Instalează" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "Pagina $1 din $2" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Credits" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "Dezvoltatori de bază" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "Contribuitori activi" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "Foști contribuitori" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "Moduri Instalate:" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "Repozitoriu Online de moduri" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "Nici o descriere de mod disponibilă" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "Informații mod:" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "Redenumiți" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "Dezinstalaţi Pachetul de moduri selectat" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "Dezinstalaţi modul selectat" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "Selectează Fișierul Modului:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "Moduri" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "Adresă/Port" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Nume/Parolă" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "Listă de servere publică" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Șterge" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Conectează" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "Client" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Nou" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Configurează" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "Începe jocul" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Selectează lumea:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Modul Creativ" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Activează Daune" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Public" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "Port server" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "Server" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "Lumină mai bună" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "Activează particulele" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "Nori 3D" + +#: builtin/mainmenu/tab_settings.lua:140 +#, fuzzy +msgid "Fancy Trees" +msgstr "Copaci fantezici" + +#: builtin/mainmenu/tab_settings.lua:142 +msgid "Opaque Water" +msgstr "Apă opacă" + +#: builtin/mainmenu/tab_settings.lua:144 +#, fuzzy +msgid "Connected Glass" +msgstr "Conectează" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "Mip Mapping" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "Filtru Anizotropic" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "Filtrare Biliniară" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "Filtrare Triliniară" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "Umbră" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "Modifică tastele" + +#: builtin/mainmenu/tab_settings.lua:167 +#, fuzzy +msgid "Reset singleplayer world" +msgstr "Singleplayer" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +#, fuzzy +msgid "Bumpmapping" +msgstr "Mip Mapping" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "Pentru a permite shadere OpenGL trebuie să fie folosite." + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "Setări" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +#, fuzzy +msgid "Start Singleplayer" +msgstr "Singleplayer" + +#: builtin/mainmenu/tab_simple_main.lua:72 +#, fuzzy +msgid "Config mods" +msgstr "Configurează" + +#: builtin/mainmenu/tab_simple_main.lua:191 +#, fuzzy +msgid "Main" +msgstr "Meniul Principal" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Joacă" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Singleplayer" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "Selectează pachetul de textură:" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "Nici o informație disponibilă" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +#, fuzzy +msgid "Texturepacks" +msgstr "Pachete de tetură" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "Texturi..." + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "lipsă_tip_font" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "Reînviere" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "Definițiile obiectelor..." + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "Definițiile Blocurilor..." + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "Media..." + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Verifică deug.txt pentru detalii." + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "Continuă" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" +"Keybindings. (Dacă acest meniu apare, șterge lucrurile din minetest.conf)" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "\"Aleargă\" = coboară" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "Apasă de 2 ori \"sari\" pentru a zbura" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "Tastă deja folosită" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "apasă o tastă" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Înainte" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Înapoi" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Stânga" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Dreapta" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Aleargă" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Sari" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Furișează" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Aruncă" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Inventar" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Chat" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Comandă" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Consloă" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Intră pe zbor" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Intră pe rapid" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "Intră pe noclip" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "Selectare distanță" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "Salvează logurile" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "Vechea parolă" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "Noua parolă" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "Confirmarea parolei" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "Schimbă" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "Parolele nu se potrivesc!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "Volum sunet: " + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Ieșire" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Stânga" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Rotiță" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Drepata" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "X Butonul 1" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "Înapoi" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "Șterge" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "Înapoi" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tab" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "X Butonul 2" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "Capital" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Control" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Kana" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Meniu" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Pauză" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Shift" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "Convert" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Escape" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "Final" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Kanji" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "Nonconvert" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "End" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Home" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "Schimbă modul" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Următorul" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Anteriorul" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Spațiu" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Jos" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Execută" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Print" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Selectează" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Sus" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Ajutor" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Insert" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "PrintScreen" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "Windows Stânga" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "Aplicații" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Numpad 0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Numpad 1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "Windows Dreapta" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Sleep" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Numpad 2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Numpad 3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Numpad 4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Numpad 5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Numpad 6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Numpad 7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Numpad *" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Numpad +" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Numpad -" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Numpad /" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Numpad 8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Numpad 9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Num Lock" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Scroll Lock" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Shift Stânga" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Shift Dreapta" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Ctrl Stânga" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Meniu Stânga" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Ctrl Dreapta" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Meniu Drepata" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Virgulă" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Minus" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "Punct" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Plus" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "Attn" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "CrSel" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "Ștergere OEF" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "ExSel" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "Curățare OEM" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PA1" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "Mărire" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "Meniul Principal" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "Eroare de conexiune (timeout?)" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "Nici un cuvânt selectat și nici o adresă scrisă. Nimic de făcut." + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "Nu se poate găsi sau încărca jocul \"" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "Specificare invalidă" + +#~ msgid "Left click: Move all items, Right click: Move single item" +#~ msgstr "" +#~ "Click stânga: Mută toate obiectele, Click dreapta: Mută un singur obiect" + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: move\n" +#~ "- Space: jump/climb\n" +#~ "- Shift: sneak/go down\n" +#~ "- Q: drop item\n" +#~ "- I: inventory\n" +#~ "- Mouse: turn/look\n" +#~ "- Mouse left: dig/punch\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- T: chat\n" +#~ msgstr "" +#~ "Controale prestabilite:\n" +#~ "- WASD: mișcare\n" +#~ "- Spațiu: sărire/urcare\n" +#~ "- Shift: furișare/coborâre\n" +#~ "- Q: aruncă obiect\n" +#~ "- I: inventar\n" +#~ "- Mouse: întoarcere/vedere\n" +#~ "- Click stânga: săpare/lovire\n" +#~ "- Click dreapta: pune/folosește\n" +#~ "- Rotiță mouse: selectează obiect\n" +#~ "- T: chat\n" + +#~ msgid "Exit to OS" +#~ msgstr "Ieși din joc" + +#~ msgid "Exit to Menu" +#~ msgstr "Ieși în Meniu" + +#~ msgid "Sound Volume" +#~ msgstr "Volum Sunet" + +#~ msgid "Change Password" +#~ msgstr "Schimbă Parola" + +#~ msgid "Continue" +#~ msgstr "Continuă" + +#~ msgid "You died." +#~ msgstr "Ai murit." + +#~ msgid "Shutting down stuff..." +#~ msgstr "Se închide..." + +#~ msgid "Connecting to server..." +#~ msgstr "Se conectează la server..." + +#~ msgid "Resolving address..." +#~ msgstr "Se rezolvă adresa..." + +#~ msgid "Creating client..." +#~ msgstr "Se creează clientul..." + +#~ msgid "Creating server...." +#~ msgstr "Se crează serverul..." + +#~ msgid "Loading..." +#~ msgstr "Se încarcă..." + +#~ msgid "Local install" +#~ msgstr "Instalare locală" + +#~ msgid "Add mod:" +#~ msgstr "Adăugaţi mod:" + +#~ msgid "MODS" +#~ msgstr "MODURI" + +#~ msgid "TEXTURE PACKS" +#~ msgstr "PACHETE DE TEXTURĂ" + +#~ msgid "SINGLE PLAYER" +#~ msgstr "SINGLE PLAYER" + +#~ msgid "Finite Liquid" +#~ msgstr "Lichid finit" + +#~ msgid "Preload item visuals" +#~ msgstr "Pre-încarcă imaginile obiectelor" + +#~ msgid "SETTINGS" +#~ msgstr "SETĂRI" + +#~ msgid "Password" +#~ msgstr "Parolă" + +#~ msgid "Name" +#~ msgstr "Nume" + +#~ msgid "START SERVER" +#~ msgstr "DESCHIDE SERVERUL" + +#~ msgid "Favorites:" +#~ msgstr "Preferate:" + +#~ msgid "CLIENT" +#~ msgstr "CLIENT" + +#~ msgid "<<-- Add mod" +#~ msgstr "<<-- Adaugă modul" + +#~ msgid "Remove selected mod" +#~ msgstr "Șterge modul selectat" + +#~ msgid "EDIT GAME" +#~ msgstr "MODIFICĂ JOCUL" + +#~ msgid "new game" +#~ msgstr "joc nou" + +#~ msgid "edit game" +#~ msgstr "modifică jocul" + +#~ msgid "Mods:" +#~ msgstr "Moduri:" + +#~ msgid "Games" +#~ msgstr "Jocuri" + +#~ msgid "GAMES" +#~ msgstr "JOCURI" + +#~ msgid "Gamemgr: Unable to copy mod \"$1\" to game \"$2\"" +#~ msgstr "Gamemgr: Nu se poate copia modul \"$1\" în jocul \"$2\"" + +#~ msgid "Game Name" +#~ msgstr "Numele jocului" diff --git a/po/ru/minetest.po b/po/ru/minetest.po new file mode 100644 index 0000000..5d9d16d --- /dev/null +++ b/po/ru/minetest.po @@ -0,0 +1,1217 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2013-12-29 16:58+0200\n" +"Last-Translator: Сергей Голубев \n" +"Language-Team: Russian\n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 1.7-dev\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "Ok" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "Мир:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "Скрыть игру" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "Скрыть содержимое модпака" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "Мод:" + +#: builtin/mainmenu/dlg_config_world.lua:48 +msgid "Depends:" +msgstr "Зависит от:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Сохранить" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Отменить" + +#: builtin/mainmenu/dlg_config_world.lua:68 +msgid "Enable MP" +msgstr "Включить мультиплеер" + +#: builtin/mainmenu/dlg_config_world.lua:70 +msgid "Disable MP" +msgstr "Отключить мультиплеер" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "включено" + +#: builtin/mainmenu/dlg_config_world.lua:82 +msgid "Enable all" +msgstr "Включить всё" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Название мира" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "Сид" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "Генератор карты" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Игра" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Создать" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "Мир под названием \"$1\" уже существует" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "Не задано имя мира или не выбрана игра" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "Уверены, что хотите удалить \"$1\"?" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Да" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "Никак нет!" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "Modmgr: невозможно удалить \"$1\"" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "Modmgr: неправильный путь \"$1\"" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "Удалить мир \"$1\"?" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Нет" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "Переименовать модпак:" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "Принять" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "Установка мода: файл \"$1\"" + +#: builtin/mainmenu/modmgr.lua:343 +#, fuzzy +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" +"\n" +"Установка мода: неподдерживаемый тип \"$1\"" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "Ошибка при установке $1 в $2" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" +"Установка мода: невозможно найти подходящее имя директории для модпака $1" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "Установка мода: невозможно определить название мода для $1" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +#, fuzzy +msgid "Downloading" +msgstr "Загрузить" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +#, fuzzy +msgid "Shortname:" +msgstr "Название мира" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "Рейтинг" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "Переустановить" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "Установить" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "Страница $1 из $2" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Благодарности" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "Основные разработчики" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "Активные контрибьюторы" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "В отставке" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "Установленные моды:" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "Онлайн хранилище модов" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "Описание к моду отсутствует" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "Описание мода:" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "Переименовать" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "Удалить выбранный мод-пак" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "Удалить выбранный мод" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "Выберите файл с модом:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "Моды" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "Адрес/Порт" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Имя/Пароль" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "Список публичных серверов" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Удалить" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Подключиться" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "Клиент" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Новый" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Настроить" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "Начать игру" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Выберите мир:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Режим создания" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Включить урон" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Публичные" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "Порт" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "Сервер" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "Мягкое освещение" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "Включить частицы" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "3D облака" + +#: builtin/mainmenu/tab_settings.lua:140 +#, fuzzy +msgid "Fancy Trees" +msgstr "Красивые деревья" + +#: builtin/mainmenu/tab_settings.lua:142 +msgid "Opaque Water" +msgstr "Непрозрачная вода" + +#: builtin/mainmenu/tab_settings.lua:144 +#, fuzzy +msgid "Connected Glass" +msgstr "Подключиться" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "Mip-текстурирование" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "Анизотропная фильтрация" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "Билинейная фильтрация" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "Трилинейная фильтрация" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "Шейдеры" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "Смена управления" + +#: builtin/mainmenu/tab_settings.lua:167 +#, fuzzy +msgid "Reset singleplayer world" +msgstr "Одиночная игра" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +#, fuzzy +msgid "Bumpmapping" +msgstr "Mip-текстурирование" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "Для включения шейдеров необходим драйвер OpenGL." + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "Настройки" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +#, fuzzy +msgid "Start Singleplayer" +msgstr "Одиночная игра" + +#: builtin/mainmenu/tab_simple_main.lua:72 +#, fuzzy +msgid "Config mods" +msgstr "Настроить" + +#: builtin/mainmenu/tab_simple_main.lua:191 +#, fuzzy +msgid "Main" +msgstr "Главное меню" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Играть" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Одиночная игра" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "Выберите пакет текстур:" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "Описание отсутствует" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +#, fuzzy +msgid "Texturepacks" +msgstr "Пакеты текстур" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "Текстуры предметов..." + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "no" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "Возродиться" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "Описания предметов..." + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "Описания нод..." + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "Медиафайлы..." + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Подробная информация в debug.txt." + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "Продолжить" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" +"Сочетания клавиш. (Если это меню сломалось, удалите настройки из minetest." +"conf)" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "\"Использовать\" = вниз" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "Двойной прыжок = летать" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "Клавиша уже используется" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "нажмите клавишу" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Вперед" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Назад" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Влево" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Вправо" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Использовать" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Прыжок" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Красться" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Бросить" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Инвентарь" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Чат" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Команда" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Консоль" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Полёт" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Ускорение" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "Включить noclip" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "Зона видимости" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "Печать стеков" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "Старый пароль" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "Новый пароль" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "Подтверждение пароля" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "Изменить" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "Пароли не совпадают!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "Громкость звука: " + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Выход" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Левая кнопка" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Средняя кнопка" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Правая кнопка" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "Доп. кнопка 1" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "Назад" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "Очистить" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "Вернуться" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tab" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "Доп. кнопка 2" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "Caps Lock" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Ctrl" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Кана" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Меню" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Пауза" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Shift" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "Преобразовать" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Escape" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "Конец" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Кандзи" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "Не преобразовано" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "End" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Home" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "Mode Change" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Next" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Prior" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "Пробел" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Вниз" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "Выполнить" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Print" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Выбор" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Вверх" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Справка" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Insert" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "Cнимок" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "Левая кл. Win" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "Приложения" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Доп. клав. 0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Доп. клав. 1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "Прав. кл. Win" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Sleep" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Доп. клав. 2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Доп. клав. 3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Доп. клав. 4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Доп. клав. 5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Доп. клав. 6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Доп. клав. 7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Доп. клав. *" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Доп. клав. +" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Доп. клав. -" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Доп. клав. /" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Доп. клав. 8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Доп. клав. 9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Num Lock" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Scroll Lock" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Левый Shift" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Правый Shift" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Левый Ctrl" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Левая клавиша меню" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Правый Ctrl" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Правая клавиша меню" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Запятая" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Минус" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "Период" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Плюс" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "Внимание" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "CrSel" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "Стереть ОНС" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "ExSel" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "Очистить OEM" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PA1" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "Масштаб" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "Главное меню" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "Ошибка соединения (таймаут?)" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "Не выбран мир и не введен адрес." + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "Невозможно найти или загрузить игру \"" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "Неправильная конфигурация игры." + +#~ msgid "Left click: Move all items, Right click: Move single item" +#~ msgstr "ЛКМ: Переместить все предметы, ПКМ: Переместить один предмет" + +#~ msgid "is required by:" +#~ msgstr "требуется для:" + +#~ msgid "Configuration saved. " +#~ msgstr "Настройки сохранены. " + +#~ msgid "Warning: Configuration not consistent. " +#~ msgstr "Предупреждение: Неверная конфигурация. " + +#~ msgid "Cannot create world: Name contains invalid characters" +#~ msgstr "Невозможно создать мир: Имя содержит недопустимые символы" + +#~ msgid "Multiplayer" +#~ msgstr "Сетевая игра" + +#~ msgid "Advanced" +#~ msgstr "Дополнительно" + +#~ msgid "Show Public" +#~ msgstr "Публичные" + +#~ msgid "Show Favorites" +#~ msgstr "Избранные" + +#~ msgid "Leave address blank to start a local server." +#~ msgstr "Оставьте адрес пустым для запуска локального сервера." + +#~ msgid "Create world" +#~ msgstr "Создать мир" + +#~ msgid "Address required." +#~ msgstr "Нужно ввести адрес." + +#~ msgid "Cannot delete world: Nothing selected" +#~ msgstr "Невозможно удалить мир: Ничего не выбрано" + +#~ msgid "Files to be deleted" +#~ msgstr "Следующие файлы будут удалены" + +#~ msgid "Cannot create world: No games found" +#~ msgstr "Невозможно создать мир: Ни одной игры не найдено" + +#~ msgid "Cannot configure world: Nothing selected" +#~ msgstr "Невозможно настроить мир: ничего не выбрано" + +#~ msgid "Failed to delete all world files" +#~ msgstr "Ошибка при удалении файлов мира" + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: Walk\n" +#~ "- Mouse left: dig/hit\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- 0...9: select item\n" +#~ "- Shift: sneak\n" +#~ "- R: Toggle viewing all loaded chunks\n" +#~ "- I: Inventory menu\n" +#~ "- ESC: This menu\n" +#~ "- T: Chat\n" +#~ msgstr "" +#~ "Управление по умолчанию:\n" +#~ "- WASD: перемещение\n" +#~ "- ЛКМ: копать/ударить\n" +#~ "- ПКМ: поставить/использовать\n" +#~ "- Колесо мыши: выбор предмета\n" +#~ "- 0...9: выбор предмета\n" +#~ "- Shift: красться\n" +#~ "- R: смотреть далеко\n" +#~ "- I: инвентарь\n" +#~ "- ESC: это меню\n" +#~ "- T: чат\n" + +#~ msgid "" +#~ "Warning: Some configured mods are missing.\n" +#~ "Their setting will be removed when you save the configuration. " +#~ msgstr "" +#~ "Предупреждение: Некоторые моды не найдены.\n" +#~ "Их настройки будут удалены, когда вы сохраните конфигурацию. " + +#~ msgid "" +#~ "Warning: Some mods are not configured yet.\n" +#~ "They will be enabled by default when you save the configuration. " +#~ msgstr "" +#~ "Предупреждение: Некоторые моды еще не настроены.\n" +#~ "Их стандартные настройки будут установлены, когда вы сохраните " +#~ "конфигурацию. " + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: move\n" +#~ "- Space: jump/climb\n" +#~ "- Shift: sneak/go down\n" +#~ "- Q: drop item\n" +#~ "- I: inventory\n" +#~ "- Mouse: turn/look\n" +#~ "- Mouse left: dig/punch\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- T: chat\n" +#~ msgstr "" +#~ "Управление по умолчанию:\n" +#~ "- WASD: движение\n" +#~ "- Пробел: прыжок/вверх\n" +#~ "- Shift: красться/вниз\n" +#~ "- Q: бросить предмет\n" +#~ "- I: инвентарь\n" +#~ "- Мышка: поворот\n" +#~ "- ЛКМ: копать/удар\n" +#~ "- ПКМ: поставить/использовать\n" +#~ "- Колесико мыши: выбор предмета\n" +#~ "- T: чат\n" + +#~ msgid "Exit to OS" +#~ msgstr "Выход в ОС" + +#~ msgid "Exit to Menu" +#~ msgstr "Выход в меню" + +#~ msgid "Sound Volume" +#~ msgstr "Громкость звука" + +#~ msgid "Change Password" +#~ msgstr "Изменить пароль" + +#~ msgid "Continue" +#~ msgstr "Продолжить" + +#~ msgid "You died." +#~ msgstr "Вы умерли." + +#~ msgid "Shutting down stuff..." +#~ msgstr "Завершение работы..." + +#~ msgid "Connecting to server..." +#~ msgstr "Подключение к серверу..." + +#~ msgid "Resolving address..." +#~ msgstr "Получение адреса..." + +#~ msgid "Creating client..." +#~ msgstr "Создание клиента..." + +#~ msgid "Creating server...." +#~ msgstr "Создание сервера..." + +#~ msgid "Loading..." +#~ msgstr "Загрузка..." + +#~ msgid "Local install" +#~ msgstr "Локальная установка" + +#~ msgid "Add mod:" +#~ msgstr "Добавить мод:" + +#~ msgid "MODS" +#~ msgstr "МОДЫ" + +#~ msgid "TEXTURE PACKS" +#~ msgstr "ПАКЕТЫ ТЕКСТУР" + +#~ msgid "SINGLE PLAYER" +#~ msgstr "ОДИНОЧНАЯ ИГРА" + +#~ msgid "Finite Liquid" +#~ msgstr "Конечные жидкости" + +#~ msgid "Preload item visuals" +#~ msgstr "Предзагрузка изображений" + +#~ msgid "SETTINGS" +#~ msgstr "НАСТРОЙКИ" + +#~ msgid "Password" +#~ msgstr "Пароль" + +#~ msgid "Name" +#~ msgstr "Имя" + +#~ msgid "START SERVER" +#~ msgstr "СЕРВЕР" + +#~ msgid "Favorites:" +#~ msgstr "Избранное:" + +#~ msgid "CLIENT" +#~ msgstr "КЛИЕНТ" + +#~ msgid "<<-- Add mod" +#~ msgstr "<<-- Добавить мод" + +#~ msgid "Remove selected mod" +#~ msgstr "Удалить мод" + +#~ msgid "EDIT GAME" +#~ msgstr "РЕДАКТИРОВАНИЕ" + +#~ msgid "new game" +#~ msgstr "Создать игру" + +#~ msgid "edit game" +#~ msgstr "Редактировать" + +#~ msgid "Mods:" +#~ msgstr "Моды:" + +#~ msgid "Games" +#~ msgstr "Игры" + +#~ msgid "GAMES" +#~ msgstr "ИГРЫ" + +#~ msgid "Gamemgr: Unable to copy mod \"$1\" to game \"$2\"" +#~ msgstr "Gamemgr: Не могу скопировать мод \"$1\" в игру \"$2\"" + +#~ msgid "Game Name" +#~ msgstr "Название" diff --git a/po/tr/minetest.po b/po/tr/minetest.po new file mode 100644 index 0000000..385ae5b --- /dev/null +++ b/po/tr/minetest.po @@ -0,0 +1,1248 @@ +msgid "" +msgstr "" +"Project-Id-Version: 0.0.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-02-24 14:45+0200\n" +"PO-Revision-Date: 2015-02-24 15:11+0200\n" +"Last-Translator: Mahmut Elmas mahmutelmas06@gmail.com\n" +"Language-Team: Türkçe <>\n" +"Language: tr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Eazy Po 0.9.5.3\n" +"X-Poedit-Language: Turkish\n" +"X-Poedit-Basepath: \n" + +#: builtin/fstk/ui.lua:67 +#, approved +msgid "Ok" +msgstr "Tamam" + +#: builtin/mainmenu/dlg_config_world.lua:26 +#, approved +#| msgid "World:" +msgid "World:" +msgstr "Dünya:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +#, approved +#| msgid "Hide Game" +msgid "Hide Game" +msgstr "Kök Eklentiler" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +#, approved +msgid "Hide mp content" +msgstr "Paket detaylarını gizle" + +#: builtin/mainmenu/dlg_config_world.lua:46 +#, approved +#| msgid "Mod:" +msgid "Mod:" +msgstr "Eklnt: " + +#: builtin/mainmenu/dlg_config_world.lua:48 +#, approved +#| msgid "Depends:" +msgid "Depends:" +msgstr "Bağımlılıklar :" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +#, approved +msgid "Save" +msgstr "Kaydet" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +#, approved +#| msgid "Cancel" +msgid "Cancel" +msgstr "Vazgeç" + +#: builtin/mainmenu/dlg_config_world.lua:68 +#, approved +msgid "Enable MP" +msgstr "Eklenti paketini etkinleştir" + +#: builtin/mainmenu/dlg_config_world.lua:70 +#, approved +#| msgid "Disable MP" +msgid "Disable MP" +msgstr "Eklenti paketini devre dışı bırak" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +#, approved +#| msgid "enabled" +msgid "enabled" +msgstr "Etkinleştirildi" + +#: builtin/mainmenu/dlg_config_world.lua:82 +#, approved +#| msgid "Enable all" +msgid "Enable all" +msgstr "Hepsini etkinleştir" + +#: builtin/mainmenu/dlg_create_world.lua:50 +#, approved +msgid "World name" +msgstr "Dünya adı" + +#: builtin/mainmenu/dlg_create_world.lua:53 +#, approved +#| msgid "Seed" +msgid "Seed" +msgstr "Çekirdek" + +#: builtin/mainmenu/dlg_create_world.lua:56 +#, approved +#| msgid "Mapgen" +msgid "Mapgen" +msgstr "Mapgen" + +#: builtin/mainmenu/dlg_create_world.lua:59 +#, approved +msgid "Game" +msgstr "Oyun" + +#: builtin/mainmenu/dlg_create_world.lua:63 +#, approved +msgid "Create" +msgstr "Oluştur" + +#: builtin/mainmenu/dlg_create_world.lua:68 +#, approved +#| msgid "You have no subgames installed." +msgid "You have no subgames installed." +msgstr "Ek bir oyun modu yüklü değil." + +#: builtin/mainmenu/dlg_create_world.lua:69 +#, approved +#| msgid "Download one from minetest.net" +msgid "Download one from minetest.net" +msgstr "Minetest.net adresinden indirin" + +#: builtin/mainmenu/dlg_create_world.lua:72 +#, approved +msgid "" +"Warning: The minimal development test is meant for " +"developers." +msgstr "Uyarı : Minimal Development Test geliştiriciler içindir." + +#: builtin/mainmenu/dlg_create_world.lua:73 +#, approved +msgid "" +"Download a subgame, such as minetest_game, from " +"minetest.net" +msgstr "Minetest.net adresinden bir oyun modu indirin" + +#: builtin/mainmenu/dlg_create_world.lua:97 +#, approved +msgid "A world named \"$1\" already exists" +msgstr " \"$1\" isimli dünya zaten var" + +#: builtin/mainmenu/dlg_create_world.lua:116 +#, approved +msgid "No worldname given or no game selected" +msgstr "Dünya seçilmedi ya da adlandırılmadı" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +#, approved +msgid "Are you sure you want to delete \"$1\"?" +msgstr "" +" \"$1\" 'i silmek istediğinizden emin misiniz " +"?" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +#, approved +msgid "Yes" +msgstr "Evet" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +#, approved +msgid "No of course not!" +msgstr "Elbette hayır!" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +#, approved +msgid "Modmgr: failed to delete \"$1\"" +msgstr "Modmgr:\"$1\" dosyası silerken hata" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +#, approved +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "Modmgr: \"$1\" eklenti konumu yanlış" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +#, approved +msgid "Delete World \"$1\"?" +msgstr " \"$1\" dünyasını sil ?" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +#, approved +msgid "No" +msgstr "Hayır" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +#, approved +msgid "Rename Modpack:" +msgstr "Eklenti paketini yeniden adlandır :" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +#, approved +msgid "Accept" +msgstr "Kabul et" + +#: builtin/mainmenu/modmgr.lua:342 +#, approved +msgid "Install Mod: file: \"$1\"" +msgstr "Eklenti yükle: file: \"$1\"" + +#: builtin/mainmenu/modmgr.lua:343 +#, approved +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken " +"archive" +msgstr "" +"\n" +"Eklenti yükle: Desteklenmeyen dosya uzantısı \"$1\" veya bozuk " +"dosya" + +#: builtin/mainmenu/modmgr.lua:363 +#, approved +msgid "Failed to install $1 to $2" +msgstr " $1 arası $2 yükleme başarısız" + +#: builtin/mainmenu/modmgr.lua:366 +#, approved +msgid "" +"Install Mod: unable to find suitable foldername for modpack " +"$1" +msgstr "Eklenti yükle:$1 eklenti paketi için uygun bir klasör adı bulunamadı" + +#: builtin/mainmenu/modmgr.lua:386 +#, approved +msgid "" +"Install Mod: unable to find real modname for: " +"$1" +msgstr "Eklenti yükle: $1 için eklenti adı bulunamadı" + +#: builtin/mainmenu/store.lua:88 +#, approved +msgid "Unsorted" +msgstr "Sıralanmamış" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +#, approved +msgid "Search" +msgstr "Ara" + +#: builtin/mainmenu/store.lua:125 +#, approved +msgid "Downloading" +msgstr "İndiriliyor" + +#: builtin/mainmenu/store.lua:127 +#, approved +msgid "please wait..." +msgstr "lütfen bekleyin..." + +#: builtin/mainmenu/store.lua:159 +#, approved +msgid "Successfully installed:" +msgstr "Yükleme başarılı :" + +#: builtin/mainmenu/store.lua:163 +#, approved +msgid "Shortname:" +msgstr "Takma ad :" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +#, approved +msgid "ok" +msgstr "tamam" + +#: builtin/mainmenu/store.lua:476 +#, approved +msgid "Rating" +msgstr "Oylama" + +#: builtin/mainmenu/store.lua:501 +#, approved +msgid "re-Install" +msgstr "yeniden yükle" + +#: builtin/mainmenu/store.lua:503 +#, approved +msgid "Install" +msgstr "Yükle" + +#: builtin/mainmenu/store.lua:522 +#, approved +msgid "Close store" +msgstr "Mağazayı kapat" + +#: builtin/mainmenu/store.lua:530 +#, approved +msgid "Page $1 of $2" +msgstr "$2 sayfadan $1 'cisi" + +#: builtin/mainmenu/tab_credits.lua:22 +#, approved +msgid "Credits" +msgstr "Emeği Geçenler" + +#: builtin/mainmenu/tab_credits.lua:29 +#, approved +msgid "Core Developers" +msgstr "Ana geliştiriciler" + +#: builtin/mainmenu/tab_credits.lua:43 +#, approved +msgid "Active Contributors" +msgstr "Aktif katkı sağlayanlar" + +#: builtin/mainmenu/tab_credits.lua:48 +#, approved +msgid "Previous Contributors" +msgstr "Katkı sağlayanlar" + +#: builtin/mainmenu/tab_mods.lua:30 +#, approved +msgid "Installed Mods:" +msgstr "Yüklenen eklentiler :" + +#: builtin/mainmenu/tab_mods.lua:39 +#, approved +msgid "Online mod repository" +msgstr "Çevirimiçi eklenti deposu" + +#: builtin/mainmenu/tab_mods.lua:78 +#, approved +msgid "No mod description available" +msgstr "Eklenti bilgisi yok" + +#: builtin/mainmenu/tab_mods.lua:82 +#, approved +msgid "Mod information:" +msgstr "Eklenti bilgileri:" + +#: builtin/mainmenu/tab_mods.lua:93 +#, approved +msgid "Rename" +msgstr "Adlandır" + +#: builtin/mainmenu/tab_mods.lua:95 +#, approved +msgid "Uninstall selected modpack" +msgstr "Seçilen eklenti paketini sil" + +#: builtin/mainmenu/tab_mods.lua:106 +#, approved +msgid "Uninstall selected mod" +msgstr "Seçili eklentiyi sil" + +#: builtin/mainmenu/tab_mods.lua:121 +#, approved +msgid "Select Mod File:" +msgstr "Eklenti seç :" + +#: builtin/mainmenu/tab_mods.lua:165 +#, approved +msgid "Mods" +msgstr "Eklentiler" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +#, approved +msgid "Address/Port" +msgstr "Adres / Port" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +#, approved +msgid "Name/Password" +msgstr "Kullanıcı adı/Şifre" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +#, approved +msgid "Public Serverlist" +msgstr "Çevirimiçi Oyun Listesi" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +#, approved +msgid "Delete" +msgstr "Sil" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +#, approved +msgid "Connect" +msgstr "Bağlan" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +#, approved +msgid "Client" +msgstr "Çevirimiçi Oyna" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +#, approved +msgid "New" +msgstr "Yeni" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +#, approved +msgid "Configure" +msgstr "Ayarla" + +#: builtin/mainmenu/tab_server.lua:29 +#, approved +msgid "Start Game" +msgstr "Oyunu Başlat" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +#, approved +msgid "Select World:" +msgstr "Dünya seç :" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +#, approved +msgid "Creative Mode" +msgstr "Yaratıcı Mod" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +#, approved +msgid "Enable Damage" +msgstr "Hasarı etkinleştir" + +#: builtin/mainmenu/tab_server.lua:35 +#, approved +msgid "Public" +msgstr "Herkese Açık" + +#: builtin/mainmenu/tab_server.lua:45 +#, approved +msgid "Bind Address" +msgstr "Adresi doğrula" + +#: builtin/mainmenu/tab_server.lua:47 +#, approved +msgid "Port" +msgstr "Port" + +#: builtin/mainmenu/tab_server.lua:51 +#, approved +msgid "Server Port" +msgstr "Sunucu portu" + +#: builtin/mainmenu/tab_server.lua:174 +#, approved +msgid "Server" +msgstr "Sunucu Kur" + +#: builtin/mainmenu/tab_settings.lua:23 +#, approved +msgid "" +"Are you sure to reset your singleplayer " +"world?" +msgstr "" +"Tek kişilik dünyayı sıfırlamak istediğinizden emin misiniz " +"?" + +#: builtin/mainmenu/tab_settings.lua:27 +#, approved +msgid "No!!!" +msgstr "Hayır!!!" + +#: builtin/mainmenu/tab_settings.lua:134 +#, approved +msgid "Smooth Lighting" +msgstr "Pürüzsüz ışıklandırma" + +#: builtin/mainmenu/tab_settings.lua:136 +#, approved +msgid "Enable Particles" +msgstr "Parçacıkları etkinleştir" + +#: builtin/mainmenu/tab_settings.lua:138 +#, approved +msgid "3D Clouds" +msgstr "3 boyutlu bulutlar" + +#, approved +msgid "Fancy Trees" +msgstr "Şık ağaçlar" + +#: builtin/mainmenu/tab_settings.lua:142 +#, approved +msgid "Opaque Water" +msgstr "Şeffaf su" + +#, approved +msgid "Connected Glass" +msgstr "İçiçe geçmiş cam" + +#: builtin/mainmenu/tab_settings.lua:149 +#, approved +msgid "" +"Restart minetest for driver change to take " +"effect" +msgstr "" +"Değişikliklerin etkin olabilmesi için minetesti yeniden " +"başlatın" + +#: builtin/mainmenu/tab_settings.lua:151 +#, approved +msgid "Mip-Mapping" +msgstr "Mip-Mapping" + +#: builtin/mainmenu/tab_settings.lua:153 +#, approved +msgid "Anisotropic Filtering" +msgstr "Eşyönsüz süzme" + +#: builtin/mainmenu/tab_settings.lua:155 +#, approved +msgid "Bi-Linear Filtering" +msgstr "Çift yönlü süzme" + +#: builtin/mainmenu/tab_settings.lua:157 +#, approved +msgid "Tri-Linear Filtering" +msgstr "Üç yönlü süzme" + +#: builtin/mainmenu/tab_settings.lua:160 +#, approved +msgid "Shaders" +msgstr "Shaders" + +#: builtin/mainmenu/tab_settings.lua:164 +#, approved +msgid "Change keys" +msgstr "Tuşları değiştir" + +#: builtin/mainmenu/tab_settings.lua:167 +#, approved +msgid "Reset singleplayer world" +msgstr "Tek kişilik oyunu sıfırlayın" + +#: builtin/mainmenu/tab_settings.lua:171 +#, approved +msgid "GUI scale factor" +msgstr "Menü boyutları" + +#: builtin/mainmenu/tab_settings.lua:175 +#, approved +msgid "Scaling factor applied to menu elements: " +msgstr "Ölçeklendirme menülere işlendi:" + +#: builtin/mainmenu/tab_settings.lua:181 +#, approved +msgid "Touch free target" +msgstr "Touch free target" + +#: builtin/mainmenu/tab_settings.lua:187 +#, approved +msgid "Touchthreshold (px)" +msgstr "Touchthreshold (px)" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +#, approved +msgid "Bumpmapping" +msgstr "Engebeler" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +#, approved +msgid "Generate Normalmaps" +msgstr "Normal haritalar oluştur" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +#, approved +msgid "Parallax Occlusion" +msgstr "Parallax Occlusion" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +#, approved +msgid "Waving Water" +msgstr "Dalgalanan Su" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +#, approved +msgid "Waving Leaves" +msgstr "Dalgalanan Yapraklar" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +#, approved +msgid "Waving Plants" +msgstr "Dalgalanan Bitkiler" + +#: builtin/mainmenu/tab_settings.lua:255 +#, approved +msgid "" +"To enable shaders the OpenGL driver needs to be " +"used." +msgstr "OpenGL sürücüleri seçilmeden Shader etkinleştirilemez." + +#: builtin/mainmenu/tab_settings.lua:330 +#, approved +msgid "Settings" +msgstr "Ayarlar" + +#: builtin/mainmenu/tab_simple_main.lua:67 +#, approved +msgid "Fly mode" +msgstr "Uçuş modu" + +#: builtin/mainmenu/tab_simple_main.lua:71 +#, approved +msgid "Start Singleplayer" +msgstr "Tek kişilik oyunu başlat" + +#, approved +msgid "Config mods" +msgstr "Eklentileri ayarla" + +#: builtin/mainmenu/tab_simple_main.lua:191 +#, approved +msgid "Main" +msgstr "Ana" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +#, approved +msgid "Play" +msgstr "Oyna" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +#, approved +msgid "Singleplayer" +msgstr "Tek Kişilik" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +#, approved +msgid "Select texture pack:" +msgstr "Doku paketi seç :" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +#, approved +msgid "No information available" +msgstr "Bilgi yok" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +#, approved +msgid "Texturepacks" +msgstr "Doku paketleri" + +#: src/client.cpp:2726 +#, approved +msgid "Item textures..." +msgstr "Nesne dokuları ..." + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +#, approved +msgid "needs_fallback_font" +msgstr "needs_fallback_font" + +#: src/game.cpp:1063 +#, approved +msgid "Respawn" +msgstr "Yeniden Canlan" + +#: src/game.cpp:2250 +#, approved +msgid "Item definitions..." +msgstr "Nesne tanımlamaları..." + +#: src/game.cpp:2255 +#, approved +msgid "Node definitions..." +msgstr "Blok tanımlamaları..." + +#: src/game.cpp:2262 +#, approved +msgid "Media..." +msgstr "Media..." + +#: src/game.cpp:2267 +#, approved +msgid " KB/s" +msgstr " KB/s" + +#: src/game.cpp:2271 +#, approved +msgid " MB/s" +msgstr " MB/s" + +#: src/game.cpp:4220 +#, approved +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Hata ayrıntıları için debug.txt dosyasını inceleyin." + +#: src/guiFormSpecMenu.cpp:2055 +#, approved +msgid "Proceed" +msgstr "Uygula" + +#: src/guiFormSpecMenu.cpp:2846 +#, approved +msgid "Enter " +msgstr "Entrer " + +#: src/guiKeyChangeMenu.cpp:125 +#, approved +msgid "" +"Keybindings. (If this menu screws up, remove stuff from " +"minetest.conf)" +msgstr "Tuş ayaları. ( Olağandışı durumlarda minetest.conf 'u düzenleyin )" + +#: src/guiKeyChangeMenu.cpp:165 +#, approved +msgid "\"Use\" = climb down" +msgstr "\"Kullan\" = Aşağı in" + +#: src/guiKeyChangeMenu.cpp:180 +#, approved +msgid "Double tap \"jump\" to toggle fly" +msgstr "Çift zıplayarak uçma modunu aç/kapa" + +#: src/guiKeyChangeMenu.cpp:296 +#, approved +msgid "Key already in use" +msgstr "Tuş zaten kullanımda" + +#: src/guiKeyChangeMenu.cpp:371 +#, approved +msgid "press key" +msgstr "tuşa bas" + +#: src/guiKeyChangeMenu.cpp:397 +#, approved +msgid "Forward" +msgstr "İleri" + +#: src/guiKeyChangeMenu.cpp:398 +#, approved +msgid "Backward" +msgstr "Geri" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +#, approved +msgid "Left" +msgstr "Sol" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +#, approved +msgid "Right" +msgstr "Sağ" + +#: src/guiKeyChangeMenu.cpp:401 +#, approved +msgid "Use" +msgstr "Kullan" + +#: src/guiKeyChangeMenu.cpp:402 +#, approved +msgid "Jump" +msgstr "Zıpla" + +#: src/guiKeyChangeMenu.cpp:403 +#, approved +msgid "Sneak" +msgstr "Sessiz Yürü" + +#: src/guiKeyChangeMenu.cpp:404 +#, approved +msgid "Drop" +msgstr "Bırak" + +#: src/guiKeyChangeMenu.cpp:405 +#, approved +msgid "Inventory" +msgstr "Envanter" + +#: src/guiKeyChangeMenu.cpp:406 +#, approved +msgid "Chat" +msgstr "Konuşma" + +#: src/guiKeyChangeMenu.cpp:407 +#, approved +msgid "Command" +msgstr "Komut" + +#: src/guiKeyChangeMenu.cpp:408 +#, approved +msgid "Console" +msgstr "Konsol" + +#: src/guiKeyChangeMenu.cpp:409 +#, approved +msgid "Toggle fly" +msgstr "Uçuş modu aç/kapa" + +#: src/guiKeyChangeMenu.cpp:410 +#, approved +msgid "Toggle fast" +msgstr "Hız modu aç/kapa" + +#: src/guiKeyChangeMenu.cpp:411 +#, approved +msgid "Toggle noclip" +msgstr "Noclip aç/kapa" + +#: src/guiKeyChangeMenu.cpp:412 +#, approved +msgid "Range select" +msgstr "Uzaklık seçimi" + +#: src/guiKeyChangeMenu.cpp:413 +#, approved +msgid "Print stacks" +msgstr "Yazdırma yığınları" + +#: src/guiPasswordChange.cpp:106 +#, approved +msgid "Old Password" +msgstr "Eski şifre" + +#: src/guiPasswordChange.cpp:122 +#, approved +msgid "New Password" +msgstr "Yeni şifre" + +#: src/guiPasswordChange.cpp:137 +#, approved +msgid "Confirm Password" +msgstr "Şifreyi doğrulayın" + +#: src/guiPasswordChange.cpp:153 +#, approved +msgid "Change" +msgstr "Değiştir" + +#: src/guiPasswordChange.cpp:162 +#, approved +msgid "Passwords do not match!" +msgstr "Şifreler uyuşmuyor !" + +#: src/guiVolumeChange.cpp:106 +#, approved +msgid "Sound Volume: " +msgstr "Ses yüksekliği :" + +#: src/guiVolumeChange.cpp:120 +#, approved +msgid "Exit" +msgstr "Çıkış" + +#: src/keycode.cpp:224 +#, approved +msgid "Left Button" +msgstr "Sol tuşu" + +#: src/keycode.cpp:224 +#, approved +msgid "Middle Button" +msgstr "Orta Tuş" + +#: src/keycode.cpp:224 +#, approved +msgid "Right Button" +msgstr "Sağ tuş" + +#: src/keycode.cpp:224 +#, approved +msgid "X Button 1" +msgstr "X Button 1" + +#: src/keycode.cpp:225 +#, approved +msgid "Back" +msgstr "Geri" + +#: src/keycode.cpp:225 +#, approved +msgid "Clear" +msgstr "Temizle" + +#: src/keycode.cpp:225 +#, approved +msgid "Return" +msgstr "Return" + +#: src/keycode.cpp:225 +#, approved +msgid "Tab" +msgstr "Tab" + +#: src/keycode.cpp:225 +#, approved +msgid "X Button 2" +msgstr "X Button 2" + +#: src/keycode.cpp:226 +#, approved +msgid "Capital" +msgstr "Büyük" + +#: src/keycode.cpp:226 +#, approved +msgid "Control" +msgstr "Kontroller" + +#: src/keycode.cpp:226 +#, approved +msgid "Kana" +msgstr "Kana" + +#: src/keycode.cpp:226 +#, approved +msgid "Menu" +msgstr "Menü" + +#: src/keycode.cpp:226 +#, approved +msgid "Pause" +msgstr "Beklet" + +#: src/keycode.cpp:226 +#, approved +msgid "Shift" +msgstr "Shift" + +#: src/keycode.cpp:227 +#, approved +msgid "Convert" +msgstr "Dönüştür" + +#: src/keycode.cpp:227 +#, approved +msgid "Escape" +msgstr "Çıkış" + +#: src/keycode.cpp:227 +#, approved +msgid "Final" +msgstr "Bitiş" + +#: src/keycode.cpp:227 +#, approved +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +#, approved +msgid "Kanji" +msgstr "Kanji" + +#: src/keycode.cpp:227 +#, approved +msgid "Nonconvert" +msgstr "Dönüştürme" + +#: src/keycode.cpp:228 +#, approved +msgid "End" +msgstr "Son" + +#: src/keycode.cpp:228 +#, approved +msgid "Home" +msgstr "Ev" + +#: src/keycode.cpp:228 +#, approved +msgid "Mode Change" +msgstr "Mod değiştir" + +#: src/keycode.cpp:228 +#, approved +msgid "Next" +msgstr "İleri" + +#: src/keycode.cpp:228 +#, approved +msgid "Prior" +msgstr "Öncelikli" + +#: src/keycode.cpp:228 +#, approved +msgid "Space" +msgstr "Boşluk" + +#: src/keycode.cpp:229 +#, approved +msgid "Down" +msgstr "Aşağı" + +#: src/keycode.cpp:229 +#, approved +msgid "Execute" +msgstr "Çalıştır" + +#: src/keycode.cpp:229 +#, approved +msgid "Print" +msgstr "Yazdır" + +#: src/keycode.cpp:229 +#, approved +msgid "Select" +msgstr "Seç" + +#: src/keycode.cpp:229 +#, approved +msgid "Up" +msgstr "Yukarı" + +#: src/keycode.cpp:230 +#, approved +msgid "Help" +msgstr "Yardım" + +#: src/keycode.cpp:230 +#, approved +msgid "Insert" +msgstr "Insert" + +#: src/keycode.cpp:230 +#, approved +msgid "Snapshot" +msgstr "Ekran Resmi" + +#: src/keycode.cpp:233 +#, approved +msgid "Left Windows" +msgstr "Sol Windows tuşu" + +#: src/keycode.cpp:234 +#, approved +msgid "Apps" +msgstr "Uygulamalar" + +#: src/keycode.cpp:234 +#, approved +msgid "Numpad 0" +msgstr "Numpad 0" + +#: src/keycode.cpp:234 +#, approved +msgid "Numpad 1" +msgstr "Numpad 1" + +#: src/keycode.cpp:234 +#, approved +msgid "Right Windows" +msgstr "Sağ Windows tuşu" + +#: src/keycode.cpp:234 +#, approved +msgid "Sleep" +msgstr "Uyu" + +#: src/keycode.cpp:235 +#, approved +msgid "Numpad 2" +msgstr "Numpad 2" + +#: src/keycode.cpp:235 +#, approved +msgid "Numpad 3" +msgstr "Numpad 3" + +#: src/keycode.cpp:235 +#, approved +msgid "Numpad 4" +msgstr "Numpad 4" + +#: src/keycode.cpp:235 +#, approved +msgid "Numpad 5" +msgstr "Numpad 5" + +#: src/keycode.cpp:235 +#, approved +msgid "Numpad 6" +msgstr "Numpad 6" + +#: src/keycode.cpp:235 +#, approved +msgid "Numpad 7" +msgstr "Numpad 7" + +#: src/keycode.cpp:236 +#, approved +msgid "Numpad *" +msgstr "Numpad *" + +#: src/keycode.cpp:236 +#, approved +msgid "Numpad +" +msgstr "Numpad +" + +#: src/keycode.cpp:236 +#, approved +msgid "Numpad -" +msgstr "Numpad -" + +#: src/keycode.cpp:236 +#, approved +msgid "Numpad /" +msgstr "Numpad /" + +#: src/keycode.cpp:236 +#, approved +msgid "Numpad 8" +msgstr "Numpad 8" + +#: src/keycode.cpp:236 +#, approved +msgid "Numpad 9" +msgstr "Numpad 9" + +#: src/keycode.cpp:240 +#, approved +msgid "Num Lock" +msgstr "Num Lock" + +#: src/keycode.cpp:240 +#, approved +msgid "Scroll Lock" +msgstr "Scroll Lock" + +#: src/keycode.cpp:241 +#, approved +msgid "Left Shift" +msgstr "Sol Shift" + +#: src/keycode.cpp:241 +#, approved +msgid "Right Shift" +msgstr "Sağ Shift" + +#: src/keycode.cpp:242 +#, approved +msgid "Left Control" +msgstr "Sol CTRL" + +#: src/keycode.cpp:242 +#, approved +msgid "Left Menu" +msgstr "Sol Menu" + +#: src/keycode.cpp:242 +#, approved +msgid "Right Control" +msgstr "Sağ CTRL" + +#: src/keycode.cpp:242 +#, approved +msgid "Right Menu" +msgstr "Sağ Menu" + +#: src/keycode.cpp:244 +#, approved +msgid "Comma" +msgstr "Virgul" + +#: src/keycode.cpp:244 +#, approved +msgid "Minus" +msgstr "Eksi" + +#: src/keycode.cpp:244 +#, approved +msgid "Period" +msgstr "Dönem" + +#: src/keycode.cpp:244 +#, approved +msgid "Plus" +msgstr "Artı" + +#: src/keycode.cpp:248 +#, approved +msgid "Attn" +msgstr "Dikkat" + +#: src/keycode.cpp:248 +#, approved +msgid "CrSel" +msgstr "CrSel" + +#: src/keycode.cpp:249 +#, approved +msgid "Erase OEF" +msgstr "l'OEF 'i sil" + +#: src/keycode.cpp:249 +#, approved +msgid "ExSel" +msgstr "ExSel" + +#: src/keycode.cpp:249 +#, approved +msgid "OEM Clear" +msgstr "OEM Temizle" + +#: src/keycode.cpp:249 +#, approved +msgid "PA1" +msgstr "PA1" + +#: src/keycode.cpp:249 +#, approved +msgid "Zoom" +msgstr "Yakınlaştır" + +#: src/main.cpp:1681 +#, approved +msgid "Main Menu" +msgstr "Ana menu" + +#: src/main.cpp:1719 +#, approved +msgid "Player name too long." +msgstr "Kullanıcı adı çok uzun." + +#: src/main.cpp:1757 +#, approved +msgid "Connection error (timed out?)" +msgstr "Bağlantı hatası ( Zaman aşımı ? )" + +#: src/main.cpp:1919 +#, approved +msgid "" +"No world selected and no address provided. Nothing to " +"do." +msgstr "Dünya veya adres seçilmedi." + +#: src/main.cpp:1926 +#, approved +msgid "Provided world path doesn't exist: " +msgstr "Belirtilen dünya konumu yok:" + +#: src/main.cpp:1935 +#, approved +msgid "Could not find or load game \"" +msgstr "Oyun bulunamıyor veya yüklenemiyor \"" + +#: src/main.cpp:1953 +#, approved +msgid "Invalid gamespec." +msgstr "Geçersiz oyun özellikleri." diff --git a/po/uk/minetest.po b/po/uk/minetest.po new file mode 100644 index 0000000..f503948 --- /dev/null +++ b/po/uk/minetest.po @@ -0,0 +1,1157 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2013-06-27 01:22+0200\n" +"Last-Translator: Vladimir a \n" +"Language-Team: LANGUAGE \n" +"Language: uk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"X-Generator: Weblate 1.4-dev\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:26 +#, fuzzy +msgid "World:" +msgstr "Виберіть світ:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +#, fuzzy +msgid "Hide Game" +msgstr "Гра" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "" + +#: builtin/mainmenu/dlg_config_world.lua:48 +#, fuzzy +msgid "Depends:" +msgstr "залежить від:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "Зберегти" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "Відміна" + +#: builtin/mainmenu/dlg_config_world.lua:68 +#, fuzzy +msgid "Enable MP" +msgstr "Увімкнути Все" + +#: builtin/mainmenu/dlg_config_world.lua:70 +#, fuzzy +msgid "Disable MP" +msgstr "Вимкнути Усе" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "Увімкнено" + +#: builtin/mainmenu/dlg_config_world.lua:82 +#, fuzzy +msgid "Enable all" +msgstr "Увімкнути Все" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "Назва Світу" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "Гра" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "Створити" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +#, fuzzy +msgid "A world named \"$1\" already exists" +msgstr "Неможливо створити світ: Світ з таким ім'ям вже існує" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "Так" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +#, fuzzy +msgid "Delete World \"$1\"?" +msgstr "Видалити світ" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "Ні" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +#, fuzzy +msgid "Accept" +msgstr "Підтвердити" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:343 +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:363 +#, fuzzy +msgid "Failed to install $1 to $2" +msgstr "Не вдалося ініціалізувати світ" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +#, fuzzy +msgid "Downloading" +msgstr "Вниз" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +#, fuzzy +msgid "Shortname:" +msgstr "Назва Світу" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "Подяка" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "" + +#: builtin/mainmenu/tab_mods.lua:121 +#, fuzzy +msgid "Select Mod File:" +msgstr "Виберіть світ:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "Адреса/Порт" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "Ім'я/Пароль" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +#, fuzzy +msgid "Public Serverlist" +msgstr "Список публічних серверів:" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "Видалити" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "Підключитися" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "Новий" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "Налаштувати" + +#: builtin/mainmenu/tab_server.lua:29 +#, fuzzy +msgid "Start Game" +msgstr "Почати гру / Підключитися" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "Виберіть світ:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "Режим Створення" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "Ввімкнути урон" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "Публичний" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "Рівне освітлення" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "Ввімкнути частки" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "3D Хмари" + +#: builtin/mainmenu/tab_settings.lua:140 +#, fuzzy +msgid "Fancy Trees" +msgstr "Гарні дерева" + +#: builtin/mainmenu/tab_settings.lua:142 +#, fuzzy +msgid "Opaque Water" +msgstr "Непрозора вода" + +#: builtin/mainmenu/tab_settings.lua:144 +#, fuzzy +msgid "Connected Glass" +msgstr "Підключитися" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "MIP-текстурування" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "Анізотропна фільтрація" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "Білінійна фільтрація" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "Трилінійна фільтрація" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "Шейдери" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "Змінити клавіши" + +#: builtin/mainmenu/tab_settings.lua:167 +#, fuzzy +msgid "Reset singleplayer world" +msgstr "Одиночна гра" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +#, fuzzy +msgid "Bumpmapping" +msgstr "MIP-текстурування" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "Налаштування" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +#, fuzzy +msgid "Start Singleplayer" +msgstr "Одиночна гра" + +#: builtin/mainmenu/tab_simple_main.lua:72 +#, fuzzy +msgid "Config mods" +msgstr "Налаштувати" + +#: builtin/mainmenu/tab_simple_main.lua:191 +#, fuzzy +msgid "Main" +msgstr "Головне Меню" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "Грати" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "Одиночна гра" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +msgid "Texturepacks" +msgstr "" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "Текстура предметів..." + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "Народитися" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "" + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "" + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "" + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"Деталі у файлі debug.txt." + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "Далі" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "" +"Комбінації клавіш. (Якщо це меню зламалося, видаліть налаштування з minetest." +"conf)" + +#: src/guiKeyChangeMenu.cpp:165 +#, fuzzy +msgid "\"Use\" = climb down" +msgstr "\"Використовувати\" = підніматися в гору" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "Подвійний \"Стрибок\" щоб полетіти" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "Клавіша вже використовується" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "Натисніть клавішу" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "Уперед" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "Назад" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "Ліворуч" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "Праворуч" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "Використовувати" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "Стрибок" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "Крастися" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "Викинути" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "Інвентар" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "Чат" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "Комманда" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "Консоль" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "Переключити режим польоту" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "Переключити швидкий режим" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "Переключити режим проходження скрізь стін" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "Вибір діапазону" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "Надрукувати стек" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "Старий Пароль" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "Новий Пароль" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "Підтвердження нового пароля" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "Змінити" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "Паролі не збігаються!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "Гучність Звуку: " + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "Вихід" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "Ліва кнопка" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "Середня кнопка" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "Права кнопка" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "Додаткова кнопка 1" + +#: src/keycode.cpp:225 +#, fuzzy +msgid "Back" +msgstr "Назад" + +#: src/keycode.cpp:225 +#, fuzzy +msgid "Clear" +msgstr "Clear" + +#: src/keycode.cpp:225 +#, fuzzy +msgid "Return" +msgstr "Enter" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tab" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "Додаткова кнопка 2" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "Caps Lock" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Ctrl" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "Kana" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "Меню" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "Пауза" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Shift" + +#: src/keycode.cpp:227 +#, fuzzy +msgid "Convert" +msgstr "Конвертувати" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Esc" + +#: src/keycode.cpp:227 +#, fuzzy +msgid "Final" +msgstr "Кинець" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Kanji" + +#: src/keycode.cpp:227 +#, fuzzy +msgid "Nonconvert" +msgstr "Не конвертуванно" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "End" + +#: src/keycode.cpp:228 +#, fuzzy +msgid "Home" +msgstr "Home" + +#: src/keycode.cpp:228 +#, fuzzy +msgid "Mode Change" +msgstr "Mode" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "Page Up" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Page Down" + +#: src/keycode.cpp:228 +#, fuzzy +msgid "Space" +msgstr "Space" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "Вниз" + +#: src/keycode.cpp:229 +#, fuzzy +msgid "Execute" +msgstr "Виконати" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "Print Screen" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "Select" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "Вгору" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "Допомога" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "Insert" + +#: src/keycode.cpp:230 +#, fuzzy +msgid "Snapshot" +msgstr "Знімок" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "Ліва клавіша Win (Command)" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "Додатки" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "Num 0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "Num 1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "Права клавіша Win (Command)" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "Сон" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "Num 2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "Num 3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "Num 4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "Num 5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "Num 6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "Num 7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "Num *" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "Num +" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "Num -" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "Num /" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "Num 8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "Num 9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "Num Lock" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Scroll Lock" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "Ліва клавіша Shift" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "Права клавіша Shift" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "Ліва клавіша Control" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "Ліва клавіша Menu" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "Права клавіша Control" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "Права клавіша Menu" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "Кома" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "Мінус" + +#: src/keycode.cpp:244 +#, fuzzy +msgid "Period" +msgstr "Період" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "Плюс" + +#: src/keycode.cpp:248 +#, fuzzy +msgid "Attn" +msgstr "Увага" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "CrSel" + +#: src/keycode.cpp:249 +#, fuzzy +msgid "Erase OEF" +msgstr "Erase OEF" + +#: src/keycode.cpp:249 +#, fuzzy +msgid "ExSel" +msgstr "ExSel" + +#: src/keycode.cpp:249 +#, fuzzy +msgid "OEM Clear" +msgstr "OEM Очистити" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PA1" + +#: src/keycode.cpp:249 +#, fuzzy +msgid "Zoom" +msgstr "Збільшити" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "Головне Меню" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "Помилка з'єднання (час вийшов?)" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "Жоден світ не вибрано та не надано адреси. Нічого не робити." + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "Неможливо знайти, або завантажити гру \"" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "Помилкова конфігурація гри." + +#~ msgid "Left click: Move all items, Right click: Move single item" +#~ msgstr "" +#~ "Ліва кнопка миші: Перемістити усі предмети, Права кнопка миші: " +#~ "Перемістити один предмет" + +#~ msgid "is required by:" +#~ msgstr "необхідний для:" + +#~ msgid "Configuration saved. " +#~ msgstr "Налаштування Збережено. " + +#~ msgid "Warning: Configuration not consistent. " +#~ msgstr "Попередження: Помилкова конфігурація. " + +#~ msgid "Cannot create world: Name contains invalid characters" +#~ msgstr "Неможливо створити світ: Ім'я містить недопустимі символи" + +#~ msgid "Multiplayer" +#~ msgstr "Мережева гра" + +#~ msgid "Advanced" +#~ msgstr "Додатково" + +#~ msgid "Show Public" +#~ msgstr "Показати Публічні" + +#~ msgid "Show Favorites" +#~ msgstr "Показати Улюблені" + +#~ msgid "Leave address blank to start a local server." +#~ msgstr "Залишіть адресу незаповненою для створення локального серверу." + +#~ msgid "Create world" +#~ msgstr "Створити світ" + +#~ msgid "Address required." +#~ msgstr "Адреса необхідна." + +#~ msgid "Cannot delete world: Nothing selected" +#~ msgstr "Неможливо видалити світ: Нічого не вибрано" + +#~ msgid "Files to be deleted" +#~ msgstr "Файлів, що підлягають видаленню" + +#~ msgid "Cannot create world: No games found" +#~ msgstr "Неможливо створити світ: Не знайдено жодної гри" + +#~ msgid "Cannot configure world: Nothing selected" +#~ msgstr "Неможливо налаштувати світ: Нічого не вибрано" + +#~ msgid "Failed to delete all world files" +#~ msgstr "Помилка при видаленні файлів світу" + +#, fuzzy +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: move\n" +#~ "- Space: jump/climb\n" +#~ "- Shift: sneak/go down\n" +#~ "- Q: drop item\n" +#~ "- I: inventory\n" +#~ "- Mouse: turn/look\n" +#~ "- Mouse left: dig/punch\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- T: chat\n" +#~ msgstr "" +#~ "Управління за замовчанню:\n" +#~ "- WASD: рух\n" +#~ "- Space: стрибок/лізти в гору\n" +#~ "- Shift: крастися/лізти в низ\n" +#~ "- Q: кинути предмет\n" +#~ "- I: інвентар\n" +#~ "- Мишка: поворот/дивитися\n" +#~ "- Ліва клавіша миші: копати/удар\n" +#~ "- Права клавіша миші: поставити/використовувати\n" +#~ "- Колесо миші: вибір предмета\n" +#~ "- T: чат\n" + +#~ msgid "Exit to OS" +#~ msgstr "Вихід з гри" + +#~ msgid "Exit to Menu" +#~ msgstr "Вихід в меню" + +#~ msgid "Sound Volume" +#~ msgstr "Гучність звуку" + +#~ msgid "Change Password" +#~ msgstr "Змінити Пароль" + +#~ msgid "Continue" +#~ msgstr "Продовжити" + +#~ msgid "You died." +#~ msgstr "Ви загинули." + +#~ msgid "Connecting to server..." +#~ msgstr "Підключення до сервера..." + +#~ msgid "Resolving address..." +#~ msgstr "Отримання адреси..." + +#~ msgid "Creating client..." +#~ msgstr "Створення клієнта..." + +#~ msgid "Creating server...." +#~ msgstr "Створення сервера..." + +#~ msgid "Loading..." +#~ msgstr "Завантаження..." + +#, fuzzy +#~ msgid "Finite Liquid" +#~ msgstr "Кінцеві рідини" + +#~ msgid "Preload item visuals" +#~ msgstr "Попереднє завантаження зображень" + +#, fuzzy +#~ msgid "Password" +#~ msgstr "Старий Пароль" + +#~ msgid "Favorites:" +#~ msgstr "Улюблені:" + +#, fuzzy +#~ msgid "Games" +#~ msgstr "Гра" + +#, fuzzy +#~ msgid "Game Name" +#~ msgstr "Гра" diff --git a/po/zh_CN/minetest.po b/po/zh_CN/minetest.po new file mode 100644 index 0000000..e0b96ce --- /dev/null +++ b/po/zh_CN/minetest.po @@ -0,0 +1,1213 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: minetest\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-13 15:24+0100\n" +"PO-Revision-Date: 2013-11-25 10:04+0200\n" +"Last-Translator: Shen Zheyu \n" +"Language-Team: LANGUAGE \n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 1.7-dev\n" + +#: builtin/fstk/ui.lua:67 +msgid "Ok" +msgstr "OK" + +#: builtin/mainmenu/dlg_config_world.lua:26 +msgid "World:" +msgstr "世界:" + +#: builtin/mainmenu/dlg_config_world.lua:30 +#: builtin/mainmenu/dlg_config_world.lua:32 +msgid "Hide Game" +msgstr "隐藏游戏" + +#: builtin/mainmenu/dlg_config_world.lua:36 +#: builtin/mainmenu/dlg_config_world.lua:38 +msgid "Hide mp content" +msgstr "隐藏MOD包内容" + +#: builtin/mainmenu/dlg_config_world.lua:46 +msgid "Mod:" +msgstr "MOD:" + +#: builtin/mainmenu/dlg_config_world.lua:48 +msgid "Depends:" +msgstr "依赖于:" + +#: builtin/mainmenu/dlg_config_world.lua:51 src/guiKeyChangeMenu.cpp:191 +msgid "Save" +msgstr "保存" + +#: builtin/mainmenu/dlg_config_world.lua:52 +#: builtin/mainmenu/dlg_create_world.lua:64 +#: builtin/mainmenu/dlg_rename_modpack.lua:33 src/guiKeyChangeMenu.cpp:199 +#: src/keycode.cpp:224 +msgid "Cancel" +msgstr "取消" + +#: builtin/mainmenu/dlg_config_world.lua:68 +msgid "Enable MP" +msgstr "启用MOD包" + +#: builtin/mainmenu/dlg_config_world.lua:70 +msgid "Disable MP" +msgstr "禁用MOD包" + +#: builtin/mainmenu/dlg_config_world.lua:74 +#: builtin/mainmenu/dlg_config_world.lua:76 +msgid "enabled" +msgstr "启用" + +#: builtin/mainmenu/dlg_config_world.lua:82 +msgid "Enable all" +msgstr "全部启用" + +#: builtin/mainmenu/dlg_create_world.lua:50 +msgid "World name" +msgstr "世界名称" + +#: builtin/mainmenu/dlg_create_world.lua:53 +msgid "Seed" +msgstr "种子" + +#: builtin/mainmenu/dlg_create_world.lua:56 +msgid "Mapgen" +msgstr "地图生成器" + +#: builtin/mainmenu/dlg_create_world.lua:59 +msgid "Game" +msgstr "游戏" + +#: builtin/mainmenu/dlg_create_world.lua:63 +msgid "Create" +msgstr "创建" + +#: builtin/mainmenu/dlg_create_world.lua:68 +msgid "You have no subgames installed." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:69 +msgid "Download one from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:72 +msgid "Warning: The minimal development test is meant for developers." +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:73 +msgid "Download a subgame, such as minetest_game, from minetest.net" +msgstr "" + +#: builtin/mainmenu/dlg_create_world.lua:97 +msgid "A world named \"$1\" already exists" +msgstr "名为 \"$1\" 的世界已经存在" + +#: builtin/mainmenu/dlg_create_world.lua:116 +msgid "No worldname given or no game selected" +msgstr "未给定世界名或未选择游戏" + +#: builtin/mainmenu/dlg_delete_mod.lua:26 +msgid "Are you sure you want to delete \"$1\"?" +msgstr "你确认要删除\"$1\"?" + +#: builtin/mainmenu/dlg_delete_mod.lua:27 +#: builtin/mainmenu/dlg_delete_world.lua:25 +#: builtin/mainmenu/tab_settings.lua:25 +msgid "Yes" +msgstr "是" + +#: builtin/mainmenu/dlg_delete_mod.lua:28 +msgid "No of course not!" +msgstr "当然不!" + +#: builtin/mainmenu/dlg_delete_mod.lua:41 +msgid "Modmgr: failed to delete \"$1\"" +msgstr "MOD管理器:无法删除“$1“" + +#: builtin/mainmenu/dlg_delete_mod.lua:45 +msgid "Modmgr: invalid modpath \"$1\"" +msgstr "MOD管理器:MOD“$1“路径非法" + +#: builtin/mainmenu/dlg_delete_world.lua:24 +msgid "Delete World \"$1\"?" +msgstr "删除世界“$1”?" + +#: builtin/mainmenu/dlg_delete_world.lua:26 +msgid "No" +msgstr "否" + +#: builtin/mainmenu/dlg_rename_modpack.lua:26 +msgid "Rename Modpack:" +msgstr "重命名MOD包:" + +#: builtin/mainmenu/dlg_rename_modpack.lua:31 src/keycode.cpp:228 +msgid "Accept" +msgstr "接受" + +#: builtin/mainmenu/modmgr.lua:342 +msgid "Install Mod: file: \"$1\"" +msgstr "安装MOD:文件:”$1“" + +#: builtin/mainmenu/modmgr.lua:343 +#, fuzzy +msgid "" +"\n" +"Install Mod: unsupported filetype \"$1\" or broken archive" +msgstr "" +"\n" +"安装MOD:不支持的文件类型“$1“" + +#: builtin/mainmenu/modmgr.lua:363 +msgid "Failed to install $1 to $2" +msgstr "无法安装$1到$2" + +#: builtin/mainmenu/modmgr.lua:366 +msgid "Install Mod: unable to find suitable foldername for modpack $1" +msgstr "安装MOD:找不到MOD包$1的合适文件夹名" + +#: builtin/mainmenu/modmgr.lua:386 +msgid "Install Mod: unable to find real modname for: $1" +msgstr "安装MOD:找不到$1的真正MOD名" + +#: builtin/mainmenu/store.lua:88 +msgid "Unsorted" +msgstr "" + +#: builtin/mainmenu/store.lua:99 builtin/mainmenu/store.lua:584 +msgid "Search" +msgstr "" + +#: builtin/mainmenu/store.lua:125 +#, fuzzy +msgid "Downloading" +msgstr "下载" + +#: builtin/mainmenu/store.lua:127 +msgid "please wait..." +msgstr "" + +#: builtin/mainmenu/store.lua:159 +msgid "Successfully installed:" +msgstr "" + +#: builtin/mainmenu/store.lua:163 +#, fuzzy +msgid "Shortname:" +msgstr "世界名称" + +#: builtin/mainmenu/store.lua:167 src/guiFormSpecMenu.cpp:2866 +msgid "ok" +msgstr "" + +#: builtin/mainmenu/store.lua:476 +msgid "Rating" +msgstr "评级" + +#: builtin/mainmenu/store.lua:501 +msgid "re-Install" +msgstr "重新安装" + +#: builtin/mainmenu/store.lua:503 +msgid "Install" +msgstr "安装" + +#: builtin/mainmenu/store.lua:522 +msgid "Close store" +msgstr "" + +#: builtin/mainmenu/store.lua:530 +msgid "Page $1 of $2" +msgstr "第$1页,共$2页" + +#: builtin/mainmenu/tab_credits.lua:22 +msgid "Credits" +msgstr "关于" + +#: builtin/mainmenu/tab_credits.lua:29 +msgid "Core Developers" +msgstr "核心开发人员" + +#: builtin/mainmenu/tab_credits.lua:43 +msgid "Active Contributors" +msgstr "活跃的贡献者" + +#: builtin/mainmenu/tab_credits.lua:48 +msgid "Previous Contributors" +msgstr "以往的贡献者" + +#: builtin/mainmenu/tab_mods.lua:30 +msgid "Installed Mods:" +msgstr "已安装的MOD:" + +#: builtin/mainmenu/tab_mods.lua:39 +msgid "Online mod repository" +msgstr "在线mod库" + +#: builtin/mainmenu/tab_mods.lua:78 +msgid "No mod description available" +msgstr "无可用mod信息" + +#: builtin/mainmenu/tab_mods.lua:82 +msgid "Mod information:" +msgstr "mod信息:" + +#: builtin/mainmenu/tab_mods.lua:93 +msgid "Rename" +msgstr "重命名" + +#: builtin/mainmenu/tab_mods.lua:95 +msgid "Uninstall selected modpack" +msgstr "删除选定mod包" + +#: builtin/mainmenu/tab_mods.lua:106 +msgid "Uninstall selected mod" +msgstr "删除选中MOD" + +#: builtin/mainmenu/tab_mods.lua:121 +msgid "Select Mod File:" +msgstr "选择MOD文件:" + +#: builtin/mainmenu/tab_mods.lua:165 +msgid "Mods" +msgstr "MODS" + +#: builtin/mainmenu/tab_multiplayer.lua:23 +msgid "Address/Port" +msgstr "地址/端口" + +#: builtin/mainmenu/tab_multiplayer.lua:24 builtin/mainmenu/tab_server.lua:37 +#: builtin/mainmenu/tab_simple_main.lua:25 +msgid "Name/Password" +msgstr "名字/密码" + +#: builtin/mainmenu/tab_multiplayer.lua:29 +#: builtin/mainmenu/tab_simple_main.lua:30 +msgid "Public Serverlist" +msgstr "公共服务器列表" + +#: builtin/mainmenu/tab_multiplayer.lua:34 builtin/mainmenu/tab_server.lua:26 +#: builtin/mainmenu/tab_singleplayer.lua:85 src/keycode.cpp:230 +msgid "Delete" +msgstr "删除" + +#: builtin/mainmenu/tab_multiplayer.lua:38 +#: builtin/mainmenu/tab_simple_main.lua:34 +msgid "Connect" +msgstr "连接" + +#: builtin/mainmenu/tab_multiplayer.lua:252 +msgid "Client" +msgstr "客户端" + +#: builtin/mainmenu/tab_server.lua:27 builtin/mainmenu/tab_singleplayer.lua:86 +msgid "New" +msgstr "新建" + +#: builtin/mainmenu/tab_server.lua:28 builtin/mainmenu/tab_singleplayer.lua:87 +msgid "Configure" +msgstr "配置" + +#: builtin/mainmenu/tab_server.lua:29 +msgid "Start Game" +msgstr "启动游戏" + +#: builtin/mainmenu/tab_server.lua:30 builtin/mainmenu/tab_singleplayer.lua:89 +msgid "Select World:" +msgstr "选择世界:" + +#: builtin/mainmenu/tab_server.lua:31 builtin/mainmenu/tab_simple_main.lua:63 +#: builtin/mainmenu/tab_singleplayer.lua:90 +msgid "Creative Mode" +msgstr "创造模式" + +#: builtin/mainmenu/tab_server.lua:33 builtin/mainmenu/tab_simple_main.lua:65 +#: builtin/mainmenu/tab_singleplayer.lua:92 +msgid "Enable Damage" +msgstr "开启伤害" + +#: builtin/mainmenu/tab_server.lua:35 +msgid "Public" +msgstr "公共服务器" + +#: builtin/mainmenu/tab_server.lua:45 +msgid "Bind Address" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:47 +msgid "Port" +msgstr "" + +#: builtin/mainmenu/tab_server.lua:51 +msgid "Server Port" +msgstr "服务器端口" + +#: builtin/mainmenu/tab_server.lua:174 +msgid "Server" +msgstr "服务器" + +#: builtin/mainmenu/tab_settings.lua:23 +msgid "Are you sure to reset your singleplayer world?" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:27 +msgid "No!!!" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:134 +msgid "Smooth Lighting" +msgstr "平滑光照" + +#: builtin/mainmenu/tab_settings.lua:136 +msgid "Enable Particles" +msgstr "启用粒子效果" + +#: builtin/mainmenu/tab_settings.lua:138 +msgid "3D Clouds" +msgstr "3D云彩" + +#: builtin/mainmenu/tab_settings.lua:140 +#, fuzzy +msgid "Fancy Trees" +msgstr "更漂亮的树" + +#: builtin/mainmenu/tab_settings.lua:142 +msgid "Opaque Water" +msgstr "不透明的水" + +#: builtin/mainmenu/tab_settings.lua:144 +#, fuzzy +msgid "Connected Glass" +msgstr "连接" + +#: builtin/mainmenu/tab_settings.lua:149 +msgid "Restart minetest for driver change to take effect" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:151 +msgid "Mip-Mapping" +msgstr "贴图处理" + +#: builtin/mainmenu/tab_settings.lua:153 +msgid "Anisotropic Filtering" +msgstr "各向异性过滤" + +#: builtin/mainmenu/tab_settings.lua:155 +msgid "Bi-Linear Filtering" +msgstr "双线性过滤" + +#: builtin/mainmenu/tab_settings.lua:157 +msgid "Tri-Linear Filtering" +msgstr "三线性过滤" + +#: builtin/mainmenu/tab_settings.lua:160 +msgid "Shaders" +msgstr "着色器" + +#: builtin/mainmenu/tab_settings.lua:164 +msgid "Change keys" +msgstr "改变键位设置" + +#: builtin/mainmenu/tab_settings.lua:167 +#, fuzzy +msgid "Reset singleplayer world" +msgstr "单人游戏" + +#: builtin/mainmenu/tab_settings.lua:171 +msgid "GUI scale factor" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:175 +msgid "Scaling factor applied to menu elements: " +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:181 +msgid "Touch free target" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:187 +msgid "Touchthreshold (px)" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:194 builtin/mainmenu/tab_settings.lua:208 +#, fuzzy +msgid "Bumpmapping" +msgstr "贴图处理" + +#: builtin/mainmenu/tab_settings.lua:196 builtin/mainmenu/tab_settings.lua:209 +msgid "Generate Normalmaps" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:198 builtin/mainmenu/tab_settings.lua:210 +msgid "Parallax Occlusion" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:200 builtin/mainmenu/tab_settings.lua:211 +msgid "Waving Water" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:202 builtin/mainmenu/tab_settings.lua:212 +msgid "Waving Leaves" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:204 builtin/mainmenu/tab_settings.lua:213 +msgid "Waving Plants" +msgstr "" + +#: builtin/mainmenu/tab_settings.lua:255 +msgid "To enable shaders the OpenGL driver needs to be used." +msgstr "启用着色器需要使用OpenGL驱动。" + +#: builtin/mainmenu/tab_settings.lua:330 +msgid "Settings" +msgstr "设置" + +#: builtin/mainmenu/tab_simple_main.lua:67 +msgid "Fly mode" +msgstr "" + +#: builtin/mainmenu/tab_simple_main.lua:71 +#, fuzzy +msgid "Start Singleplayer" +msgstr "单人游戏" + +#: builtin/mainmenu/tab_simple_main.lua:72 +#, fuzzy +msgid "Config mods" +msgstr "配置" + +#: builtin/mainmenu/tab_simple_main.lua:191 +#, fuzzy +msgid "Main" +msgstr "主菜单" + +#: builtin/mainmenu/tab_singleplayer.lua:88 src/keycode.cpp:249 +msgid "Play" +msgstr "开始游戏" + +#: builtin/mainmenu/tab_singleplayer.lua:224 +msgid "Singleplayer" +msgstr "单人游戏" + +#: builtin/mainmenu/tab_texturepacks.lua:49 +msgid "Select texture pack:" +msgstr "选择材质包:" + +#: builtin/mainmenu/tab_texturepacks.lua:69 +msgid "No information available" +msgstr "无可用信息" + +#: builtin/mainmenu/tab_texturepacks.lua:114 +#, fuzzy +msgid "Texturepacks" +msgstr "材质包" + +#: src/client.cpp:2726 +msgid "Item textures..." +msgstr "物品材质..." + +#: src/fontengine.cpp:70 src/fontengine.cpp:226 +msgid "needs_fallback_font" +msgstr "yes" + +#: src/game.cpp:1063 +msgid "Respawn" +msgstr "重生" + +#: src/game.cpp:2250 +msgid "Item definitions..." +msgstr "物品定义..." + +#: src/game.cpp:2255 +msgid "Node definitions..." +msgstr "方块定义..." + +#: src/game.cpp:2262 +msgid "Media..." +msgstr "媒体..." + +#: src/game.cpp:2267 +msgid " KB/s" +msgstr "" + +#: src/game.cpp:2271 +msgid " MB/s" +msgstr "" + +#: src/game.cpp:4220 +msgid "" +"\n" +"Check debug.txt for details." +msgstr "" +"\n" +"查看 debug.txt 以获得详细信息。" + +#: src/guiFormSpecMenu.cpp:2055 +msgid "Proceed" +msgstr "继续游戏" + +#: src/guiFormSpecMenu.cpp:2846 +msgid "Enter " +msgstr "" + +#: src/guiKeyChangeMenu.cpp:125 +msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)" +msgstr "键位配置。(如果这个菜单被弄乱,从minetest.conf中删掉点东西)" + +#: src/guiKeyChangeMenu.cpp:165 +msgid "\"Use\" = climb down" +msgstr "“使用” = 向下爬" + +#: src/guiKeyChangeMenu.cpp:180 +msgid "Double tap \"jump\" to toggle fly" +msgstr "连按两次“跳”切换飞行状态" + +#: src/guiKeyChangeMenu.cpp:296 +msgid "Key already in use" +msgstr "按键已被占用" + +#: src/guiKeyChangeMenu.cpp:371 +msgid "press key" +msgstr "按键" + +#: src/guiKeyChangeMenu.cpp:397 +msgid "Forward" +msgstr "向前" + +#: src/guiKeyChangeMenu.cpp:398 +msgid "Backward" +msgstr "向后" + +#: src/guiKeyChangeMenu.cpp:399 src/keycode.cpp:229 +msgid "Left" +msgstr "向左" + +#: src/guiKeyChangeMenu.cpp:400 src/keycode.cpp:229 +msgid "Right" +msgstr "向右" + +#: src/guiKeyChangeMenu.cpp:401 +msgid "Use" +msgstr "使用" + +#: src/guiKeyChangeMenu.cpp:402 +msgid "Jump" +msgstr "跳" + +#: src/guiKeyChangeMenu.cpp:403 +msgid "Sneak" +msgstr "潜行" + +#: src/guiKeyChangeMenu.cpp:404 +msgid "Drop" +msgstr "丢出" + +#: src/guiKeyChangeMenu.cpp:405 +msgid "Inventory" +msgstr "物品栏" + +#: src/guiKeyChangeMenu.cpp:406 +msgid "Chat" +msgstr "聊天" + +#: src/guiKeyChangeMenu.cpp:407 +msgid "Command" +msgstr "命令" + +#: src/guiKeyChangeMenu.cpp:408 +msgid "Console" +msgstr "控制台" + +#: src/guiKeyChangeMenu.cpp:409 +msgid "Toggle fly" +msgstr "切换飞行状态" + +#: src/guiKeyChangeMenu.cpp:410 +msgid "Toggle fast" +msgstr "切换快速移动状态" + +#: src/guiKeyChangeMenu.cpp:411 +msgid "Toggle noclip" +msgstr "切换穿墙模式" + +#: src/guiKeyChangeMenu.cpp:412 +msgid "Range select" +msgstr "选择范围" + +#: src/guiKeyChangeMenu.cpp:413 +msgid "Print stacks" +msgstr "打印栈" + +#: src/guiPasswordChange.cpp:106 +msgid "Old Password" +msgstr "旧密码" + +#: src/guiPasswordChange.cpp:122 +msgid "New Password" +msgstr "新密码" + +#: src/guiPasswordChange.cpp:137 +msgid "Confirm Password" +msgstr "确认密码" + +#: src/guiPasswordChange.cpp:153 +msgid "Change" +msgstr "更改" + +#: src/guiPasswordChange.cpp:162 +msgid "Passwords do not match!" +msgstr "密码不匹配!" + +#: src/guiVolumeChange.cpp:106 +msgid "Sound Volume: " +msgstr "音量: " + +#: src/guiVolumeChange.cpp:120 +msgid "Exit" +msgstr "退出" + +#: src/keycode.cpp:224 +msgid "Left Button" +msgstr "左键" + +#: src/keycode.cpp:224 +msgid "Middle Button" +msgstr "中键" + +#: src/keycode.cpp:224 +msgid "Right Button" +msgstr "右键" + +#: src/keycode.cpp:224 +msgid "X Button 1" +msgstr "X键1" + +#: src/keycode.cpp:225 +msgid "Back" +msgstr "退格" + +#: src/keycode.cpp:225 +msgid "Clear" +msgstr "Clear键" + +#: src/keycode.cpp:225 +msgid "Return" +msgstr "回车" + +#: src/keycode.cpp:225 +msgid "Tab" +msgstr "Tab键" + +#: src/keycode.cpp:225 +msgid "X Button 2" +msgstr "X键2" + +#: src/keycode.cpp:226 +msgid "Capital" +msgstr "大写" + +#: src/keycode.cpp:226 +msgid "Control" +msgstr "Ctrl" + +#: src/keycode.cpp:226 +msgid "Kana" +msgstr "假名" + +#: src/keycode.cpp:226 +msgid "Menu" +msgstr "菜单" + +#: src/keycode.cpp:226 +msgid "Pause" +msgstr "暂停" + +#: src/keycode.cpp:226 +msgid "Shift" +msgstr "Shift键" + +#: src/keycode.cpp:227 +msgid "Convert" +msgstr "转换" + +#: src/keycode.cpp:227 +msgid "Escape" +msgstr "Escape键" + +#: src/keycode.cpp:227 +msgid "Final" +msgstr "Final键" + +#: src/keycode.cpp:227 +msgid "Junja" +msgstr "Junja键" + +#: src/keycode.cpp:227 +msgid "Kanji" +msgstr "Kanji键" + +#: src/keycode.cpp:227 +msgid "Nonconvert" +msgstr "无变换" + +#: src/keycode.cpp:228 +msgid "End" +msgstr "End键" + +#: src/keycode.cpp:228 +msgid "Home" +msgstr "Home键" + +#: src/keycode.cpp:228 +msgid "Mode Change" +msgstr "改变模式" + +#: src/keycode.cpp:228 +msgid "Next" +msgstr "下一个" + +#: src/keycode.cpp:228 +msgid "Prior" +msgstr "Prior键" + +#: src/keycode.cpp:228 +msgid "Space" +msgstr "空格" + +#: src/keycode.cpp:229 +msgid "Down" +msgstr "向下" + +#: src/keycode.cpp:229 +msgid "Execute" +msgstr "执行" + +#: src/keycode.cpp:229 +msgid "Print" +msgstr "打印" + +#: src/keycode.cpp:229 +msgid "Select" +msgstr "选择" + +#: src/keycode.cpp:229 +msgid "Up" +msgstr "向上" + +#: src/keycode.cpp:230 +msgid "Help" +msgstr "帮助" + +#: src/keycode.cpp:230 +msgid "Insert" +msgstr "插入" + +#: src/keycode.cpp:230 +msgid "Snapshot" +msgstr "快照" + +#: src/keycode.cpp:233 +msgid "Left Windows" +msgstr "左窗口" + +#: src/keycode.cpp:234 +msgid "Apps" +msgstr "应用" + +#: src/keycode.cpp:234 +msgid "Numpad 0" +msgstr "小键盘0" + +#: src/keycode.cpp:234 +msgid "Numpad 1" +msgstr "小键盘1" + +#: src/keycode.cpp:234 +msgid "Right Windows" +msgstr "右窗口" + +#: src/keycode.cpp:234 +msgid "Sleep" +msgstr "睡眠" + +#: src/keycode.cpp:235 +msgid "Numpad 2" +msgstr "小键盘2" + +#: src/keycode.cpp:235 +msgid "Numpad 3" +msgstr "小键盘3" + +#: src/keycode.cpp:235 +msgid "Numpad 4" +msgstr "小键盘4" + +#: src/keycode.cpp:235 +msgid "Numpad 5" +msgstr "小键盘5" + +#: src/keycode.cpp:235 +msgid "Numpad 6" +msgstr "小键盘6" + +#: src/keycode.cpp:235 +msgid "Numpad 7" +msgstr "小键盘7" + +#: src/keycode.cpp:236 +msgid "Numpad *" +msgstr "小键盘*" + +#: src/keycode.cpp:236 +msgid "Numpad +" +msgstr "小键盘+" + +#: src/keycode.cpp:236 +msgid "Numpad -" +msgstr "小键盘-" + +#: src/keycode.cpp:236 +msgid "Numpad /" +msgstr "小键盘/" + +#: src/keycode.cpp:236 +msgid "Numpad 8" +msgstr "小键盘8" + +#: src/keycode.cpp:236 +msgid "Numpad 9" +msgstr "小键盘9" + +#: src/keycode.cpp:240 +msgid "Num Lock" +msgstr "小键盘锁" + +#: src/keycode.cpp:240 +msgid "Scroll Lock" +msgstr "Scroll Lock键" + +#: src/keycode.cpp:241 +msgid "Left Shift" +msgstr "左Shift键" + +#: src/keycode.cpp:241 +msgid "Right Shift" +msgstr "右Shift键" + +#: src/keycode.cpp:242 +msgid "Left Control" +msgstr "左Control键" + +#: src/keycode.cpp:242 +msgid "Left Menu" +msgstr "左菜单" + +#: src/keycode.cpp:242 +msgid "Right Control" +msgstr "右Control键" + +#: src/keycode.cpp:242 +msgid "Right Menu" +msgstr "右菜单" + +#: src/keycode.cpp:244 +msgid "Comma" +msgstr "逗号" + +#: src/keycode.cpp:244 +msgid "Minus" +msgstr "减号" + +#: src/keycode.cpp:244 +msgid "Period" +msgstr "句号" + +#: src/keycode.cpp:244 +msgid "Plus" +msgstr "加号" + +#: src/keycode.cpp:248 +msgid "Attn" +msgstr "Attn键" + +#: src/keycode.cpp:248 +msgid "CrSel" +msgstr "CrSel键" + +#: src/keycode.cpp:249 +msgid "Erase OEF" +msgstr "Erase OEF键" + +#: src/keycode.cpp:249 +msgid "ExSel" +msgstr "ExSel键" + +#: src/keycode.cpp:249 +msgid "OEM Clear" +msgstr "OEM Clear键" + +#: src/keycode.cpp:249 +msgid "PA1" +msgstr "PA1键" + +#: src/keycode.cpp:249 +msgid "Zoom" +msgstr "缩放" + +#: src/main.cpp:1681 +msgid "Main Menu" +msgstr "主菜单" + +#: src/main.cpp:1719 +msgid "Player name too long." +msgstr "" + +#: src/main.cpp:1757 +msgid "Connection error (timed out?)" +msgstr "连接出错(超时?)" + +#: src/main.cpp:1919 +msgid "No world selected and no address provided. Nothing to do." +msgstr "没有选择世界或提供地址。未执行操作。" + +#: src/main.cpp:1926 +msgid "Provided world path doesn't exist: " +msgstr "" + +#: src/main.cpp:1935 +msgid "Could not find or load game \"" +msgstr "无法找到或载入游戏模式“" + +#: src/main.cpp:1953 +msgid "Invalid gamespec." +msgstr "非法游戏模式规格。" + +#~ msgid "Left click: Move all items, Right click: Move single item" +#~ msgstr "左键:移动所有物品,右键:移动单个物品" + +#~ msgid "is required by:" +#~ msgstr "被需要:" + +#~ msgid "Configuration saved. " +#~ msgstr "配置已保存。 " + +#~ msgid "Warning: Configuration not consistent. " +#~ msgstr "警告:配置不一致。 " + +#~ msgid "Cannot create world: Name contains invalid characters" +#~ msgstr "无法创建世界:名字包含非法字符" + +#~ msgid "Multiplayer" +#~ msgstr "多人游戏" + +#~ msgid "Advanced" +#~ msgstr "高级联机设置" + +#~ msgid "Show Public" +#~ msgstr "显示公共" + +#~ msgid "Show Favorites" +#~ msgstr "显示最爱" + +#~ msgid "Leave address blank to start a local server." +#~ msgstr "地址栏留空可启动本地服务器。" + +#~ msgid "Create world" +#~ msgstr "创造世界" + +#~ msgid "Address required." +#~ msgstr "需要地址。" + +#~ msgid "Cannot delete world: Nothing selected" +#~ msgstr "无法删除世界:没有选择世界" + +#~ msgid "Files to be deleted" +#~ msgstr "将被删除的文件" + +#~ msgid "Cannot create world: No games found" +#~ msgstr "无法创造世界:未找到游戏模式" + +#~ msgid "Cannot configure world: Nothing selected" +#~ msgstr "无法配置世界:没有选择世界" + +#~ msgid "Failed to delete all world files" +#~ msgstr "无法删除所有该世界的文件" + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: Walk\n" +#~ "- Mouse left: dig/hit\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- 0...9: select item\n" +#~ "- Shift: sneak\n" +#~ "- R: Toggle viewing all loaded chunks\n" +#~ "- I: Inventory menu\n" +#~ "- ESC: This menu\n" +#~ "- T: Chat\n" +#~ msgstr "" +#~ "默认控制:\n" +#~ "W/A/S/D: 走\n" +#~ "空格: 跳\n" +#~ "鼠标左键: 挖方块/攻击\n" +#~ "鼠标右键: 放置/使用\n" +#~ "鼠标滚轮: 选择物品\n" +#~ "0-9: 选择物品\n" +#~ "Shift: 潜行\n" +#~ "R:切换查看所有已载入区块\n" +#~ "I:物品栏\n" +#~ "ESC:菜单\n" +#~ "T:聊天\n" + +#~ msgid "" +#~ "Warning: Some configured mods are missing.\n" +#~ "Their setting will be removed when you save the configuration. " +#~ msgstr "" +#~ "警告:缺少一些设定了的MOD。\n" +#~ "它们的设置会在你保存配置的时候被移除。 " + +#~ msgid "" +#~ "Warning: Some mods are not configured yet.\n" +#~ "They will be enabled by default when you save the configuration. " +#~ msgstr "" +#~ "警告:一些MOD仍未设定。\n" +#~ "它们会在你保存配置的时候自动启用。 " + +#~ msgid "" +#~ "Default Controls:\n" +#~ "- WASD: move\n" +#~ "- Space: jump/climb\n" +#~ "- Shift: sneak/go down\n" +#~ "- Q: drop item\n" +#~ "- I: inventory\n" +#~ "- Mouse: turn/look\n" +#~ "- Mouse left: dig/punch\n" +#~ "- Mouse right: place/use\n" +#~ "- Mouse wheel: select item\n" +#~ "- T: chat\n" +#~ msgstr "" +#~ "默认控制:\n" +#~ "W/A/S/D: 移动\n" +#~ "空格: 跳/爬\n" +#~ "Shift: 潜行/向下\n" +#~ "Q: 丢物品\n" +#~ "I: 物品栏\n" +#~ "鼠标:转身/环顾\n" +#~ "鼠标左键: 挖\n" +#~ "鼠标右键: 放/使用\n" +#~ "鼠标滚轮: 选择物品\n" +#~ "T: 聊天\n" + +#~ msgid "Exit to OS" +#~ msgstr "退出至操作系统" + +#~ msgid "Exit to Menu" +#~ msgstr "退出至菜单" + +#~ msgid "Sound Volume" +#~ msgstr "音量" + +#~ msgid "Change Password" +#~ msgstr "更改密码" + +#~ msgid "Continue" +#~ msgstr "继续" + +#~ msgid "You died." +#~ msgstr "你死了。" + +#~ msgid "Shutting down stuff..." +#~ msgstr "关闭中......" + +#~ msgid "Connecting to server..." +#~ msgstr "正在连接服务器..." + +#~ msgid "Resolving address..." +#~ msgstr "正在解析地址..." + +#~ msgid "Creating client..." +#~ msgstr "正在建立客户端..." + +#~ msgid "Creating server...." +#~ msgstr "正在建立服务器...." + +#~ msgid "Loading..." +#~ msgstr "载入中..." + +#~ msgid "Local install" +#~ msgstr "本地安装" + +#~ msgid "Add mod:" +#~ msgstr "添加MOD:" + +#~ msgid "MODS" +#~ msgstr "MODS" + +#~ msgid "TEXTURE PACKS" +#~ msgstr "材质包" + +#~ msgid "SINGLE PLAYER" +#~ msgstr "单人游戏" + +#~ msgid "Finite Liquid" +#~ msgstr "液体有限延伸" + +#~ msgid "Preload item visuals" +#~ msgstr "预先加载物品图像" + +#~ msgid "SETTINGS" +#~ msgstr "设置" + +#~ msgid "Password" +#~ msgstr "密码" + +#~ msgid "Name" +#~ msgstr "名字" + +#~ msgid "START SERVER" +#~ msgstr "启动服务器" + +#~ msgid "Favorites:" +#~ msgstr "最爱的服务器:" + +#~ msgid "CLIENT" +#~ msgstr "客户端" + +#~ msgid "<<-- Add mod" +#~ msgstr "<<-- 添加MOD" + +#~ msgid "Remove selected mod" +#~ msgstr "删除选中MOD" + +#~ msgid "EDIT GAME" +#~ msgstr "编辑游戏" + +#~ msgid "new game" +#~ msgstr "新建游戏" + +#~ msgid "edit game" +#~ msgstr "编辑游戏" + +#~ msgid "Mods:" +#~ msgstr "MODS:" + +#~ msgid "Games" +#~ msgstr "游戏" + +#~ msgid "GAMES" +#~ msgstr "游戏" + +#~ msgid "Gamemgr: Unable to copy mod \"$1\" to game \"$2\"" +#~ msgstr "Gamemgr: 无法复制MOD“$1”到游戏“$2”" + +#~ msgid "Game Name" +#~ msgstr "游戏名" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..d56ec18 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,806 @@ +project(minetest) +cmake_minimum_required( VERSION 2.6 ) + +INCLUDE(CheckCSourceRuns) +INCLUDE(CheckIncludeFiles) + +# Add custom SemiDebug build mode +set(CMAKE_CXX_FLAGS_SEMIDEBUG "-O1 -g -Wall -Wabi" CACHE STRING + "Flags used by the C++ compiler during semidebug builds." + FORCE +) +set(CMAKE_C_FLAGS_SEMIDEBUG "-O1 -g -Wall -pedantic" CACHE STRING + "Flags used by the C compiler during semidebug builds." + FORCE +) +mark_as_advanced( + CMAKE_CXX_FLAGS_SEMIDEBUG + CMAKE_C_FLAGS_SEMIDEBUG +) +set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING + "Choose the type of build. Options are: None Debug SemiDebug RelWithDebInfo MinSizeRel." + FORCE +) + +# Set some random things default to not being visible in the GUI +mark_as_advanced(EXECUTABLE_OUTPUT_PATH LIBRARY_OUTPUT_PATH) + +option(ENABLE_CURL "Enable cURL support for fetching media" 1) + +if (NOT ENABLE_CURL) + mark_as_advanced(CLEAR CURL_LIBRARY CURL_INCLUDE_DIR) +endif(NOT ENABLE_CURL) + +if( ENABLE_CURL ) + find_package(CURL) +endif( ENABLE_CURL ) +set(USE_CURL 0) +if (CURL_FOUND AND ENABLE_CURL) + message(STATUS "cURL support enabled") + set(USE_CURL 1) +endif(CURL_FOUND AND ENABLE_CURL) + +# user-visible option to enable/disable gettext usage +OPTION(ENABLE_GETTEXT "Use GetText for internationalization" 0) + +# this is only set to 1 if gettext is enabled _and_ available +set(USE_GETTEXT 0) + +if(ENABLE_GETTEXT) + find_package(GettextLib) +else() + MARK_AS_ADVANCED(GETTEXT_ICONV_DLL GETTEXT_INCLUDE_DIR GETTEXT_LIBRARY GETTEXT_MSGFMT) +endif() + +if(GETTEXT_FOUND AND ENABLE_GETTEXT) + message(STATUS "gettext include path: ${GETTEXT_INCLUDE_DIR}") + message(STATUS "gettext msgfmt path: ${GETTEXT_MSGFMT}") + if(WIN32) + message(STATUS "gettext library: ${GETTEXT_LIBRARY}") + message(STATUS "gettext dll: ${GETTEXT_DLL}") + message(STATUS "gettext iconv dll: ${GETTEXT_ICONV_DLL}") + endif() + set(USE_GETTEXT 1) + message(STATUS "GetText enabled; locales found: ${GETTEXT_AVAILABLE_LOCALES}") +elseif(GETTEXT_FOUND AND NOT ENABLE_GETTEXT) + MESSAGE(STATUS "GetText found but disabled;") +else(GETTEXT_FOUND AND ENABLE_GETTEXT) + message(STATUS "GetText disabled") +endif(GETTEXT_FOUND AND ENABLE_GETTEXT) + +# user visible option to enable/disable sound +OPTION(ENABLE_SOUND "Enable sound" ON) + +# this is only set to 1 if sound is enabled _and_ available +set(USE_SOUND 0) +set(SOUND_PROBLEM 0) + +if(ENABLE_SOUND AND BUILD_CLIENT) + # Sound libraries + find_package(OpenAL) + find_package(Vorbis) + if(NOT OPENAL_FOUND) + message(STATUS "Sound enabled, but OpenAL not found!") + set(SOUND_PROBLEM 1) + MARK_AS_ADVANCED(CLEAR OPENAL_LIBRARY OPENAL_INCLUDE_DIR) + endif() + if(NOT VORBIS_FOUND) + message(STATUS "Sound enabled, but Vorbis libraries not found!") + set(SOUND_PROBLEM 1) + MARK_AS_ADVANCED(CLEAR OGG_INCLUDE_DIR VORBIS_INCLUDE_DIR OGG_LIBRARY VORBIS_LIBRARY VORBISFILE_LIBRARY) + endif() + if(OPENAL_FOUND AND VORBIS_FOUND) + set(USE_SOUND 1) + message(STATUS "Sound enabled") + endif() +endif(ENABLE_SOUND AND BUILD_CLIENT) + +if(SOUND_PROBLEM) + message(FATAL_ERROR "Sound enabled, but cannot be used.\n" + "To continue, either fill in the required paths or disable sound. (-DENABLE_SOUND=0)") +endif() +if(USE_SOUND) + set(sound_SRCS sound_openal.cpp) + set(SOUND_INCLUDE_DIRS + ${OPENAL_INCLUDE_DIR} + ${VORBIS_INCLUDE_DIR} + ${OGG_INCLUDE_DIR} + ) + set(SOUND_LIBRARIES + ${OPENAL_LIBRARY} + ${VORBIS_LIBRARIES} + ) +endif() + +option(ENABLE_FREETYPE "Enable freetype2 (truetype fonts and basic unicode support)" OFF) +set(USE_FREETYPE 0) +if(ENABLE_FREETYPE) + set(USE_FREETYPE 1) +endif(ENABLE_FREETYPE) + +if(NOT MSVC) + set(USE_GPROF 0 CACHE BOOL "Use -pg flag for g++") +endif() + +# Use cmake_config.h +add_definitions ( -DUSE_CMAKE_CONFIG_H ) + +if(WIN32) + # Windows + if(MSVC) # MSVC Specifics + set(PLATFORM_LIBS dbghelp.lib ${PLATFORM_LIBS}) + # Surpress some useless warnings + add_definitions ( /D "_CRT_SECURE_NO_DEPRECATE" /W1 ) + else() # Probably MinGW = GCC + set(PLATFORM_LIBS ws2_32.lib) + endif() + # Zlib stuff + set(ZLIB_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/../../zlib/zlib-1.2.5" + CACHE PATH "Zlib include directory") + set(ZLIB_LIBRARIES "${PROJECT_SOURCE_DIR}/../../zlib125dll/dll32/zlibwapi.lib" + CACHE FILEPATH "Path to zlibwapi.lib") + set(ZLIB_DLL "${PROJECT_SOURCE_DIR}/../../zlib125dll/dll32/zlibwapi.dll" + CACHE FILEPATH "Path to zlibwapi.dll (for installation)") + set(IRRLICHT_SOURCE_DIR "${PROJECT_SOURCE_DIR}/../../irrlicht-1.7.2" + CACHE PATH "irrlicht dir") + if(USE_FREETYPE) + set(FREETYPE_INCLUDE_DIR_ft2build "${PROJECT_SOURCE_DIR}/../../freetype2/include/" + CACHE PATH "freetype include dir") + set(FREETYPE_INCLUDE_DIR_freetype2 "${PROJECT_SOURCE_DIR}/../../freetype2/include/freetype" + CACHE PATH "freetype include dir") + set(FREETYPE_LIBRARY "${PROJECT_SOURCE_DIR}/../../freetype2/objs/win32/vc2005/freetype247.lib" + CACHE FILEPATH "Path to freetype247.lib") + endif(USE_FREETYPE) + if(ENABLE_SOUND) + set(OPENAL_DLL "" CACHE FILEPATH "Path to OpenAL32.dll for installation (optional)") + set(OGG_DLL "" CACHE FILEPATH "Path to libogg.dll for installation (optional)") + set(VORBIS_DLL "" CACHE FILEPATH "Path to libvorbis.dll for installation (optional)") + set(VORBISFILE_DLL "" CACHE FILEPATH "Path to libvorbisfile.dll for installation (optional)") + endif() +else() + # Unix probably + if(BUILD_CLIENT) + find_package(X11 REQUIRED) + find_package(OpenGL REQUIRED) + find_package(JPEG REQUIRED) + find_package(BZip2 REQUIRED) + find_package(PNG REQUIRED) + if(APPLE) + FIND_LIBRARY(CARBON_LIB Carbon) + FIND_LIBRARY(COCOA_LIB Cocoa) + FIND_LIBRARY(IOKIT_LIB IOKit) + mark_as_advanced( + CARBON_LIB + COCOA_LIB + IOKIT_LIB + ) + SET(CLIENT_PLATFORM_LIBS ${CLIENT_PLATFORM_LIBS} ${CARBON_LIB} ${COCOA_LIB} ${IOKIT_LIB}) + endif(APPLE) + endif(BUILD_CLIENT) + find_package(ZLIB REQUIRED) + set(PLATFORM_LIBS -lpthread ${CMAKE_DL_LIBS}) + if(APPLE) + set(PLATFORM_LIBS "-framework CoreFoundation" ${PLATFORM_LIBS}) + else() + set(PLATFORM_LIBS -lrt ${PLATFORM_LIBS}) + endif(APPLE) + #set(CLIENT_PLATFORM_LIBS -lXxf86vm) + # This way Xxf86vm is found on OpenBSD too + find_library(XXF86VM_LIBRARY Xxf86vm) + mark_as_advanced(XXF86VM_LIBRARY) + set(CLIENT_PLATFORM_LIBS ${CLIENT_PLATFORM_LIBS} ${XXF86VM_LIBRARY}) +endif() + +find_package(SQLite3 REQUIRED) +find_package(Json REQUIRED) + +option(ENABLE_GLES "Enable OpenGL ES support" 0) +mark_as_advanced(ENABLE_GLES) +if(ENABLE_GLES) + find_package(OpenGLES2) +endif(ENABLE_GLES) + +if(USE_FREETYPE) + if(UNIX) + include(FindPkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(FREETYPE QUIET freetype2) + if(FREETYPE_FOUND) + SET(FREETYPE_PKGCONFIG_FOUND TRUE) + SET(FREETYPE_LIBRARY ${FREETYPE_LIBRARIES}) + # because cmake is idiotic + string(REPLACE ";" " " FREETYPE_CFLAGS_STR ${FREETYPE_CFLAGS}) + string(REPLACE ";" " " FREETYPE_LDFLAGS_STR ${FREETYPE_LDFLAGS}) + endif(FREETYPE_FOUND) + endif(PKG_CONFIG_FOUND) + endif(UNIX) + if(NOT FREETYPE_FOUND) + find_package(Freetype REQUIRED) + endif(NOT FREETYPE_FOUND) + set(CGUITTFONT_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cguittfont") + set(CGUITTFONT_LIBRARY cguittfont) +endif(USE_FREETYPE) + +if (NOT DISABLE_LUAJIT) + find_library(LUA_LIBRARY luajit + NAMES luajit-5.1) + find_path(LUA_INCLUDE_DIR luajit.h + NAMES luajit.h + PATH_SUFFIXES luajit-2.0) + message (STATUS "LuaJIT library: ${LUA_LIBRARY}") + message (STATUS "LuaJIT headers: ${LUA_INCLUDE_DIR}") +else (NOT ${DISABLE_LUAJIT} MATCHES "1") + message (STATUS "LuaJIT detection disabled! (DISABLE_LUAJIT=1)") + set(LUA_LIBRARY "") + set(LUA_INCLUDE_DIR "") +endif (NOT DISABLE_LUAJIT) + +set(USE_LUAJIT 0) +if(LUA_LIBRARY AND LUA_INCLUDE_DIR) + message (STATUS "LuaJIT found, checking for broken versions...") + if(CMAKE_CROSSCOMPILING) + message(WARNING "Cross-compiling enabled, assuming LuaJIT is not broken") + set(VALID_LUAJIT_VERSION 1) + else(CMAKE_CROSSCOMPILING) + set(BACKUP_REQUIRED_INCS CMAKE_REQUIRED_INCLUDES) + set(CMAKE_REQUIRED_INCLUDES "${CMAKE_REQUIRED_INCLUDES} ${LUA_INCLUDE_DIR}") + CHECK_C_SOURCE_RUNS(" + #include + #include + #include + + #define ARRAYSIZE(a) (sizeof(a) / sizeof((a)[0])) + + static char *broken_luajit_versions[] = { + \"LuaJIT 2.0.0-beta7\", + \"LuaJIT 2.0.0-beta6\", + \"LuaJIT 2.0.0-beta5\", + \"LuaJIT 2.0.0-beta4\", + \"LuaJIT 2.0.0-beta3\", + \"LuaJIT 2.0.0-beta2\", + \"LuaJIT 2.0.0-beta1\" + }; + + int main(int argc, char *argv[]) { + unsigned int i; + for (i = 0; i < ARRAYSIZE(broken_luajit_versions); i++) { + if (strcmp(LUAJIT_VERSION, broken_luajit_versions[i]) == 0) { + return 1; + } + } + return 0; + } + " + VALID_LUAJIT_VERSION) + set(CMAKE_REQUIRED_INCLUDES BACKUP_REQUIRED_INCS) + endif(CMAKE_CROSSCOMPILING) + if (VALID_LUAJIT_VERSION) + message (STATUS "LuaJIT version ok") + set(USE_LUAJIT 1) + else (VALID_LUAJIT_VERSION) + message (STATUS "LuaJIT versions till 2.0.0beta7 known to be broken, update to at least beta8") + set(USE_LUAJIT 0) + endif (VALID_LUAJIT_VERSION) +endif (LUA_LIBRARY AND LUA_INCLUDE_DIR) + +if(NOT USE_LUAJIT) + message (STATUS "LuaJIT not found, using bundled Lua.") + set(LUA_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/lua/src") + set(LUA_LIBRARY "lua") + add_subdirectory(lua) +endif(NOT USE_LUAJIT) + +mark_as_advanced(LUA_LIBRARY) +mark_as_advanced(LUA_INCLUDE_DIR) + +set(USE_LEVELDB 0) + +OPTION(ENABLE_LEVELDB "Enable LevelDB backend") + +if(ENABLE_LEVELDB) + find_library(LEVELDB_LIBRARY leveldb) + find_path(LEVELDB_INCLUDE_DIR db.h PATH_SUFFIXES leveldb) + message (STATUS "LevelDB library: ${LEVELDB_LIBRARY}") + message (STATUS "LevelDB headers: ${LEVELDB_INCLUDE_DIR}") + if(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR) + set(USE_LEVELDB 1) + message(STATUS "LevelDB backend enabled") + include_directories(${LEVELDB_INCLUDE_DIR}) + else(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR) + set(USE_LEVELDB 0) + message(STATUS "LevelDB not found!") + endif(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR) +endif(ENABLE_LEVELDB) + +set(USE_REDIS 0) + +OPTION(ENABLE_REDIS "Enable redis backend" 0) + +if(ENABLE_REDIS) + find_library(REDIS_LIBRARY hiredis) + find_path(REDIS_INCLUDE_DIR hiredis.h PATH_SUFFIXES hiredis) + message(STATUS "redis library: ${REDIS_LIBRARY}") + message(STATUS "redis headers: ${REDIS_INCLUDE_DIR}") + if(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) + set(USE_REDIS 1) + message(STATUS "redis backend enabled") + include_directories(${REDIS_INCLUDE_DIR}) + else(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) + set(USE_REDIS 0) + message(STATUS "redis not found!") + endif(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) +endif(ENABLE_REDIS) + +CHECK_INCLUDE_FILES(endian.h HAVE_ENDIAN_H) +if(NOT HAVE_ENDIAN_H) + set(HAVE_ENDIAN_H 0) +endif(NOT HAVE_ENDIAN_H) + +configure_file( + "${PROJECT_SOURCE_DIR}/cmake_config.h.in" + "${PROJECT_BINARY_DIR}/cmake_config.h" +) + +# Add a target that always rebuilds cmake_config_githash.h +add_custom_target(GenerateVersion + COMMAND ${CMAKE_COMMAND} + -D "GENERATE_VERSION_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}" + -D "GENERATE_VERSION_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}" + -D "VERSION_STRING=${VERSION_STRING}" + -D "VERSION_EXTRA=${VERSION_EXTRA}" + -P "${CMAKE_SOURCE_DIR}/cmake/Modules/GenerateVersion.cmake" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") + +add_subdirectory(jthread) +add_subdirectory(network) +add_subdirectory(script) +add_subdirectory(util) + +set (unittests_SRCS + test.cpp +) + +set(common_SRCS + ban.cpp + cavegen.cpp + clientiface.cpp + collision.cpp + content_abm.cpp + content_mapnode.cpp + content_nodemeta.cpp + content_sao.cpp + convert_json.cpp + craftdef.cpp + database-dummy.cpp + database-leveldb.cpp + database-redis.cpp + database-sqlite3.cpp + database.cpp + debug.cpp + defaultsettings.cpp + dungeongen.cpp + emerge.cpp + environment.cpp + filesys.cpp + genericobject.cpp + gettext.cpp + httpfetch.cpp + inventory.cpp + inventorymanager.cpp + itemdef.cpp + light.cpp + log.cpp + map.cpp + mapblock.cpp + mapgen.cpp + mapgen_singlenode.cpp + mapgen_v5.cpp + mapgen_v6.cpp + mapgen_v7.cpp + mapnode.cpp + mapsector.cpp + mg_biome.cpp + mg_decoration.cpp + mg_ore.cpp + mg_schematic.cpp + mods.cpp + nameidmapping.cpp + nodedef.cpp + nodemetadata.cpp + nodetimer.cpp + noise.cpp + object_properties.cpp + pathfinder.cpp + player.cpp + porting.cpp + quicktune.cpp + rollback.cpp + rollback_interface.cpp + serialization.cpp + server.cpp + serverlist.cpp + serverobject.cpp + settings.cpp + socket.cpp + sound.cpp + staticobject.cpp + subgame.cpp + tool.cpp + treegen.cpp + version.cpp + voxel.cpp + voxelalgorithms.cpp + ${common_network_SRCS} + ${JTHREAD_SRCS} + ${common_SCRIPT_SRCS} + ${UTIL_SRCS} + ${unittests_SRCS} +) + +# This gives us the icon and file version information +if(WIN32) + set(WINRESOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/../misc/winresource.rc) + if(MINGW) + if(NOT CMAKE_RC_COMPILER) + set(CMAKE_RC_COMPILER "windres.exe") + endif() + ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/winresource_rc.o + COMMAND ${CMAKE_RC_COMPILER} -I${CMAKE_CURRENT_SOURCE_DIR} -I${CMAKE_CURRENT_BINARY_DIR} + -i${WINRESOURCE_FILE} + -o ${CMAKE_CURRENT_BINARY_DIR}/winresource_rc.o + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${WINRESOURCE_FILE}) + SET(common_SRCS ${common_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/winresource_rc.o) + else(MINGW) # Probably MSVC + set(common_SRCS ${common_SRCS} ${WINRESOURCE_FILE}) + endif(MINGW) +endif() + +# Client sources + +if (BUILD_CLIENT) + add_subdirectory(client) +endif(BUILD_CLIENT) + +set(minetest_SRCS + ${common_SRCS} + ${sound_SRCS} + ${client_SRCS} + ${client_network_SRCS} + camera.cpp + chat.cpp + client.cpp + clientmap.cpp + clientmedia.cpp + clientobject.cpp + clouds.cpp + content_cao.cpp + content_cso.cpp + content_mapblock.cpp + convert_json.cpp + drawscene.cpp + filecache.cpp + fontengine.cpp + game.cpp + guiChatConsole.cpp + guiEngine.cpp + guiFileSelectMenu.cpp + guiFormSpecMenu.cpp + guiKeyChangeMenu.cpp + guiPasswordChange.cpp + guiTable.cpp + guiVolumeChange.cpp + hud.cpp + keycode.cpp + localplayer.cpp + main.cpp + mapblock_mesh.cpp + mesh.cpp + particles.cpp + shader.cpp + sky.cpp + wieldmesh.cpp + ${minetest_SCRIPT_SRCS} +) +list(SORT minetest_SRCS) + +# Server sources +set(minetestserver_SRCS + ${common_SRCS} + main.cpp +) +list(SORT minetestserver_SRCS) + +include_directories( + ${PROJECT_BINARY_DIR} + ${PROJECT_SOURCE_DIR} + ${IRRLICHT_INCLUDE_DIR} + ${ZLIB_INCLUDE_DIR} + ${CMAKE_BUILD_TYPE} + ${PNG_INCLUDE_DIR} + ${GETTEXT_INCLUDE_DIR} + ${SOUND_INCLUDE_DIRS} + ${SQLITE3_INCLUDE_DIR} + ${LUA_INCLUDE_DIR} + ${JSON_INCLUDE_DIR} + ${PROJECT_SOURCE_DIR}/script +) + +if(USE_FREETYPE) + include_directories( + ${FREETYPE_INCLUDE_DIRS} + ${CGUITTFONT_INCLUDE_DIR} + ) +endif(USE_FREETYPE) + +if(USE_CURL) + include_directories( + ${CURL_INCLUDE_DIR} + ) +endif(USE_CURL) + +set(EXECUTABLE_OUTPUT_PATH "${CMAKE_SOURCE_DIR}/bin") + +if(BUILD_CLIENT) + add_executable(${PROJECT_NAME} ${minetest_SRCS}) + add_dependencies(${PROJECT_NAME} GenerateVersion) + set(minetest_LIBS + ${PROJECT_NAME} + ${ZLIB_LIBRARIES} + ${IRRLICHT_LIBRARY} + ${OPENGL_LIBRARIES} + ${JPEG_LIBRARIES} + ${BZIP2_LIBRARIES} + ${PNG_LIBRARIES} + ${X11_LIBRARIES} + ${GETTEXT_LIBRARY} + ${SOUND_LIBRARIES} + ${SQLITE3_LIBRARY} + ${LUA_LIBRARY} + ${JSON_LIBRARY} + ${OPENGLES2_LIBRARIES} + ${PLATFORM_LIBS} + ${CLIENT_PLATFORM_LIBS} + ) + if(APPLE) + target_link_libraries( + ${minetest_LIBS} + ${ICONV_LIBRARY} + ) + else() + target_link_libraries( + ${minetest_LIBS} + ) + endif() + if(USE_CURL) + target_link_libraries( + ${PROJECT_NAME} + ${CURL_LIBRARY} + ) + endif(USE_CURL) + if(USE_FREETYPE) + if(FREETYPE_PKGCONFIG_FOUND) + set_target_properties(${PROJECT_NAME} + PROPERTIES + COMPILE_FLAGS "${FREETYPE_CFLAGS_STR}" + ) + endif(FREETYPE_PKGCONFIG_FOUND) + target_link_libraries( + ${PROJECT_NAME} + ${FREETYPE_LIBRARY} + ${CGUITTFONT_LIBRARY} + ) + endif(USE_FREETYPE) + if (USE_LEVELDB) + target_link_libraries(${PROJECT_NAME} ${LEVELDB_LIBRARY}) + endif(USE_LEVELDB) + if (USE_REDIS) + target_link_libraries(${PROJECT_NAME} ${REDIS_LIBRARY}) + endif(USE_REDIS) +endif(BUILD_CLIENT) + +if(BUILD_SERVER) + add_executable(${PROJECT_NAME}server ${minetestserver_SRCS}) + add_dependencies(${PROJECT_NAME}server GenerateVersion) + target_link_libraries( + ${PROJECT_NAME}server + ${ZLIB_LIBRARIES} + ${SQLITE3_LIBRARY} + ${JSON_LIBRARY} + ${GETTEXT_LIBRARY} + ${LUA_LIBRARY} + ${PLATFORM_LIBS} + ) + if (USE_LEVELDB) + target_link_libraries(${PROJECT_NAME}server ${LEVELDB_LIBRARY}) + endif(USE_LEVELDB) + if (USE_REDIS) + target_link_libraries(${PROJECT_NAME}server ${REDIS_LIBRARY}) + endif(USE_REDIS) + if(USE_CURL) + target_link_libraries( + ${PROJECT_NAME}server + ${CURL_LIBRARY} + ) + endif(USE_CURL) +endif(BUILD_SERVER) + + +# +# Set some optimizations and tweaks +# + +include(CheckCXXCompilerFlag) + +if(MSVC) + # Visual Studio + + # EHa enables SEH exceptions (used for catching segfaults) + set(CMAKE_CXX_FLAGS_RELEASE "/EHa /Ox /Ob2 /Oi /Ot /Oy /GL /FD /MT /GS- /arch:SSE /fp:fast /D NDEBUG /D _HAS_ITERATOR_DEBUGGING=0 /TP") + #set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/LTCG /NODEFAULTLIB:\"libcmtd.lib\" /NODEFAULTLIB:\"libcmt.lib\"") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/LTCG") + + set(CMAKE_CXX_FLAGS_SEMIDEBUG "/MDd /Zi /Ob0 /O1 /RTC1") + + # Debug build doesn't catch exceptions by itself + # Add some optimizations because otherwise it's VERY slow + set(CMAKE_CXX_FLAGS_DEBUG "/MDd /Zi /Ob0 /Od /RTC1") + + # Flags for C files (sqlite) + # /MT = Link statically with standard library stuff + set(CMAKE_C_FLAGS_RELEASE "/O2 /Ob2 /MT") + + if(BUILD_SERVER) + set_target_properties(${PROJECT_NAME}server PROPERTIES + COMPILE_DEFINITIONS "SERVER") + endif(BUILD_SERVER) + +else() + # Probably GCC + if(APPLE) + SET( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pagezero_size 10000 -image_base 100000000" ) + endif() + if(WARN_ALL) + set(RELEASE_WARNING_FLAGS "-Wall") + else() + set(RELEASE_WARNING_FLAGS "") + endif() + + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + # clang does not understand __extern_always_inline but libc headers use it + set(OTHER_FLAGS "${OTHER_FLAGS} \"-D__extern_always_inline=extern __always_inline\"") + endif() + + if(MINGW) + set(OTHER_FLAGS "-mthreads -fexceptions") + endif() + + set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG ${RELEASE_WARNING_FLAGS} ${WARNING_FLAGS} ${OTHER_FLAGS} -ffast-math -Wall -pipe -funroll-loops") + if(APPLE) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os") + else() + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -fomit-frame-pointer") + endif(APPLE) + set(CMAKE_CXX_FLAGS_SEMIDEBUG "-g -O1 -Wall -Wabi ${WARNING_FLAGS} ${OTHER_FLAGS}") + set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wabi ${WARNING_FLAGS} ${OTHER_FLAGS}") + + if(USE_GPROF) + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg") + endif() + + if(BUILD_SERVER) + set_target_properties(${PROJECT_NAME}server PROPERTIES + COMPILE_DEFINITIONS "SERVER") + endif(BUILD_SERVER) + +endif() + +#MESSAGE(STATUS "CMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE}") +#MESSAGE(STATUS "CMAKE_CXX_FLAGS_DEBUG=${CMAKE_CXX_FLAGS_DEBUG}") + +# +# Installation +# +if(WIN32) + if(USE_SOUND) + if(OPENAL_DLL) + install(FILES ${OPENAL_DLL} DESTINATION ${BINDIR}) + endif() + if(OGG_DLL) + install(FILES ${OGG_DLL} DESTINATION ${BINDIR}) + endif() + if(VORBIS_DLL) + install(FILES ${VORBIS_DLL} DESTINATION ${BINDIR}) + endif() + if(VORBISFILE_DLL) + install(FILES ${VORBISFILE_DLL} DESTINATION ${BINDIR}) + endif() + endif() + if(CURL_DLL) + install(FILES ${CURL_DLL} DESTINATION ${BINDIR}) + endif() + if(ZLIB_DLL) + install(FILES ${ZLIB_DLL} DESTINATION ${BINDIR}) + endif() + if(ZLIBWAPI_DLL) + install(FILES ${ZLIBWAPI_DLL} DESTINATION ${BINDIR}) + endif() + if(FREETYPE_DLL) + install(FILES ${FREETYPE_DLL} DESTINATION ${BINDIR}) + endif() + if(SQLITE3_DLL) + install(FILES ${SQLITE3_DLL} DESTINATION ${BINDIR}) + endif() + if(LEVELDB_DLL) + install(FILES ${LEVELDB_DLL} DESTINATION ${BINDIR}) + endif() +endif() + +if(BUILD_CLIENT) + install(TARGETS ${PROJECT_NAME} DESTINATION ${BINDIR}) + + if(USE_GETTEXT) + foreach(LOCALE ${GETTEXT_AVAILABLE_LOCALES}) + set_mo_paths(MO_BUILD_PATH MO_DEST_PATH ${LOCALE}) + set(MO_BUILD_PATH "${MO_BUILD_PATH}/${PROJECT_NAME}.mo") + install(FILES ${MO_BUILD_PATH} DESTINATION ${MO_DEST_PATH}) + endforeach(LOCALE ${GETTEXT_AVAILABLE_LOCALES}) + endif() + + if(WIN32) + if(DEFINED IRRLICHT_DLL) + install(FILES ${IRRLICHT_DLL} DESTINATION ${BINDIR}) + endif() + if(USE_GETTEXT) + if(DEFINED GETTEXT_DLL) + install(FILES ${GETTEXT_DLL} DESTINATION ${BINDIR}) + endif() + if(DEFINED GETTEXT_ICONV_DLL) + install(FILES ${GETTEXT_ICONV_DLL} DESTINATION ${BINDIR}) + endif() + endif(USE_GETTEXT) + endif() +endif(BUILD_CLIENT) + +if(BUILD_SERVER) + install(TARGETS ${PROJECT_NAME}server DESTINATION ${BINDIR}) +endif(BUILD_SERVER) + +if (USE_GETTEXT) + set(MO_FILES) + + foreach(LOCALE ${GETTEXT_AVAILABLE_LOCALES}) + set(PO_FILE_PATH "${GETTEXT_PO_PATH}/${LOCALE}/minetest.po") + set_mo_paths(MO_BUILD_PATH MO_DEST_PATH ${LOCALE}) + set(MO_FILE_PATH "${MO_BUILD_PATH}/${PROJECT_NAME}.mo") + + add_custom_command(OUTPUT ${MO_BUILD_PATH} + COMMAND ${CMAKE_COMMAND} -E make_directory ${MO_BUILD_PATH} + COMMENT "mo-update [${LOCALE}]: Creating locale directory.") + + add_custom_command( + OUTPUT ${MO_FILE_PATH} + COMMAND ${GETTEXT_MSGFMT} -o ${MO_FILE_PATH} ${PO_FILE_PATH} + DEPENDS ${MO_BUILD_PATH} ${PO_FILE_PATH} + WORKING_DIRECTORY "${GETTEXT_PO_PATH}/${LOCALE}" + COMMENT "mo-update [${LOCALE}]: Creating mo file." + ) + + set(MO_FILES ${MO_FILES} ${MO_FILE_PATH}) + endforeach(LOCALE ${GETTEXT_AVAILABLE_LOCALES}) + + add_custom_target(translations ALL COMMENT "mo update" DEPENDS ${MO_FILES}) +endif(USE_GETTEXT) + +# Subdirectories + +if (BUILD_CLIENT AND USE_FREETYPE) + add_subdirectory(cguittfont) +endif (BUILD_CLIENT AND USE_FREETYPE) + +if (JSON_FOUND) +else (JSON_FOUND) + add_subdirectory(json) +endif (JSON_FOUND) + +#end diff --git a/src/activeobject.h b/src/activeobject.h new file mode 100644 index 0000000..48f078d --- /dev/null +++ b/src/activeobject.h @@ -0,0 +1,86 @@ +/* +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. +*/ + +#ifndef ACTIVEOBJECT_HEADER +#define ACTIVEOBJECT_HEADER + +#include "irr_aabb3d.h" +#include + +enum ActiveObjectType { + ACTIVEOBJECT_TYPE_INVALID = 0, + ACTIVEOBJECT_TYPE_TEST = 1, +// Deprecated stuff + ACTIVEOBJECT_TYPE_ITEM = 2, + ACTIVEOBJECT_TYPE_RAT = 3, + ACTIVEOBJECT_TYPE_OERKKI1 = 4, + ACTIVEOBJECT_TYPE_FIREFLY = 5, + ACTIVEOBJECT_TYPE_MOBV2 = 6, +// End deprecated stuff + ACTIVEOBJECT_TYPE_LUAENTITY = 7, +// Special type, not stored as a static object + ACTIVEOBJECT_TYPE_PLAYER = 100, +// Special type, only exists as CAO + ACTIVEOBJECT_TYPE_GENERIC = 101, +}; +// Other types are defined in content_object.h + +struct ActiveObjectMessage +{ + ActiveObjectMessage(u16 id_, bool reliable_=true, std::string data_=""): + id(id_), + reliable(reliable_), + datastring(data_) + {} + + u16 id; + bool reliable; + std::string datastring; +}; + +/* + Parent class for ServerActiveObject and ClientActiveObject +*/ +class ActiveObject +{ +public: + ActiveObject(u16 id): + m_id(id) + { + } + + u16 getId() + { + return m_id; + } + + void setId(u16 id) + { + m_id = id; + } + + virtual ActiveObjectType getType() const = 0; + virtual bool getCollisionBox(aabb3f *toset) = 0; + virtual bool collideWithObjects() = 0; +protected: + u16 m_id; // 0 is invalid, "no id" +}; + +#endif + diff --git a/src/ban.cpp b/src/ban.cpp new file mode 100644 index 0000000..55d9b22 --- /dev/null +++ b/src/ban.cpp @@ -0,0 +1,155 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#include "ban.h" +#include +#include "jthread/jmutexautolock.h" +#include +#include +#include "strfnd.h" +#include "util/string.h" +#include "log.h" +#include "filesys.h" + +BanManager::BanManager(const std::string &banfilepath): + m_banfilepath(banfilepath), + m_modified(false) +{ + try{ + load(); + } + catch(SerializationError &e) + { + infostream<<"WARNING: BanManager: creating " + <::iterator + i = m_ips.begin(); + i != m_ips.end(); i++) + { + ss << i->first << "|" << i->second << "\n"; + } + + if(!fs::safeWriteToFile(m_banfilepath, ss.str())) { + infostream<<"BanManager: failed saving to "<::iterator + i = m_ips.begin(); + i != m_ips.end(); i++) + { + if(i->first == ip_or_name || i->second == ip_or_name + || ip_or_name == "") + s += i->first + "|" + i->second + ", "; + } + s = s.substr(0, s.size()-2); + return s; +} + +std::string BanManager::getBanName(const std::string &ip) +{ + JMutexAutoLock lock(m_mutex); + std::map::iterator i = m_ips.find(ip); + if(i == m_ips.end()) + return ""; + return i->second; +} + +void BanManager::add(const std::string &ip, const std::string &name) +{ + JMutexAutoLock lock(m_mutex); + m_ips[ip] = name; + m_modified = true; +} + +void BanManager::remove(const std::string &ip_or_name) +{ + JMutexAutoLock lock(m_mutex); + for(std::map::iterator + i = m_ips.begin(); + i != m_ips.end();) + { + if((i->first == ip_or_name) || (i->second == ip_or_name)) { + m_ips.erase(i++); + } else { + ++i; + } + } + m_modified = true; +} + + +bool BanManager::isModified() +{ + JMutexAutoLock lock(m_mutex); + return m_modified; +} + diff --git a/src/ban.h b/src/ban.h new file mode 100644 index 0000000..02a472f --- /dev/null +++ b/src/ban.h @@ -0,0 +1,51 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef BAN_HEADER +#define BAN_HEADER + +#include +#include +#include "jthread/jthread.h" +#include "jthread/jmutex.h" +#include "exceptions.h" + +class BanManager +{ +public: + BanManager(const std::string &bannfilepath); + ~BanManager(); + void load(); + void save(); + bool isIpBanned(const std::string &ip); + // Supplying ip_or_name = "" lists all bans. + std::string getBanDescription(const std::string &ip_or_name); + std::string getBanName(const std::string &ip); + void add(const std::string &ip, const std::string &name); + void remove(const std::string &ip_or_name); + bool isModified(); +private: + JMutex m_mutex; + std::string m_banfilepath; + std::map m_ips; + bool m_modified; + +}; + +#endif diff --git a/src/camera.cpp b/src/camera.cpp new file mode 100644 index 0000000..5200f71 --- /dev/null +++ b/src/camera.cpp @@ -0,0 +1,700 @@ +/* +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. +*/ + +#include "camera.h" +#include "debug.h" +#include "client.h" +#include "main.h" // for g_settings +#include "map.h" +#include "clientmap.h" // MapDrawControl +#include "player.h" +#include +#include "settings.h" +#include "wieldmesh.h" +#include "noise.h" // easeCurve +#include "gamedef.h" +#include "sound.h" +#include "event.h" +#include "profiler.h" +#include "util/numeric.h" +#include "util/mathconstants.h" +#include "constants.h" + +#define CAMERA_OFFSET_STEP 200 + +#include "nodedef.h" + +Camera::Camera(scene::ISceneManager* smgr, MapDrawControl& draw_control, + IGameDef *gamedef): + m_playernode(NULL), + m_headnode(NULL), + m_cameranode(NULL), + + m_wieldmgr(NULL), + m_wieldnode(NULL), + + m_draw_control(draw_control), + m_gamedef(gamedef), + + m_camera_position(0,0,0), + m_camera_direction(0,0,0), + m_camera_offset(0,0,0), + + m_aspect(1.0), + m_fov_x(1.0), + m_fov_y(1.0), + + m_added_busytime(0), + m_added_frames(0), + m_range_old(0), + m_busytime_old(0), + m_frametime_counter(0), + m_time_per_range(30. / 50), // a sane default of 30ms per 50 nodes of range + + m_view_bobbing_anim(0), + m_view_bobbing_state(0), + m_view_bobbing_speed(0), + m_view_bobbing_fall(0), + + m_digging_anim(0), + m_digging_button(-1), + + m_wield_change_timer(0.125), + m_wield_item_next(), + + m_camera_mode(CAMERA_MODE_FIRST) +{ + //dstream<<__FUNCTION_NAME<addEmptySceneNode(smgr->getRootSceneNode()); + m_headnode = smgr->addEmptySceneNode(m_playernode); + m_cameranode = smgr->addCameraSceneNode(smgr->getRootSceneNode()); + m_cameranode->bindTargetAndRotation(true); + + // This needs to be in its own scene manager. It is drawn after + // all other 3D scene nodes and before the GUI. + m_wieldmgr = smgr->createNewSceneManager(); + m_wieldmgr->addCameraSceneNode(); + m_wieldnode = new WieldMeshSceneNode(m_wieldmgr->getRootSceneNode(), m_wieldmgr, -1, true); + m_wieldnode->setItem(ItemStack(), m_gamedef); + m_wieldnode->drop(); // m_wieldmgr grabbed it + m_wieldlightnode = m_wieldmgr->addLightSceneNode(NULL, v3f(0.0, 50.0, 0.0)); + + /* TODO: Add a callback function so these can be updated when a setting + * changes. At this point in time it doesn't matter (e.g. /set + * is documented to change server settings only) + * + * TODO: Local caching of settings is not optimal and should at some stage + * be updated to use a global settings object for getting thse values + * (as opposed to the this local caching). This can be addressed in + * a later release. + */ + m_cache_fall_bobbing_amount = g_settings->getFloat("fall_bobbing_amount"); + m_cache_view_bobbing_amount = g_settings->getFloat("view_bobbing_amount"); + m_cache_wanted_fps = g_settings->getFloat("wanted_fps"); + m_cache_fov = g_settings->getFloat("fov"); + m_cache_view_bobbing = g_settings->getBool("view_bobbing"); +} + +Camera::~Camera() +{ + m_wieldmgr->drop(); +} + +bool Camera::successfullyCreated(std::wstring& error_message) +{ + if (m_playernode == NULL) + { + error_message = L"Failed to create the player scene node"; + return false; + } + if (m_headnode == NULL) + { + error_message = L"Failed to create the head scene node"; + return false; + } + if (m_cameranode == NULL) + { + error_message = L"Failed to create the camera scene node"; + return false; + } + if (m_wieldmgr == NULL) + { + error_message = L"Failed to create the wielded item scene manager"; + return false; + } + if (m_wieldnode == NULL) + { + error_message = L"Failed to create the wielded item scene node"; + return false; + } + return true; +} + +// Returns the fractional part of x +inline f32 my_modf(f32 x) +{ + double dummy; + return modf(x, &dummy); +} + +void Camera::step(f32 dtime) +{ + if(m_view_bobbing_fall > 0) + { + m_view_bobbing_fall -= 3 * dtime; + if(m_view_bobbing_fall <= 0) + m_view_bobbing_fall = -1; // Mark the effect as finished + } + + bool was_under_zero = m_wield_change_timer < 0; + m_wield_change_timer = MYMIN(m_wield_change_timer + dtime, 0.125); + + if (m_wield_change_timer >= 0 && was_under_zero) + m_wieldnode->setItem(m_wield_item_next, m_gamedef); + + if (m_view_bobbing_state != 0) + { + //f32 offset = dtime * m_view_bobbing_speed * 0.035; + f32 offset = dtime * m_view_bobbing_speed * 0.030; + if (m_view_bobbing_state == 2) + { +#if 0 + // Animation is getting turned off + if (m_view_bobbing_anim < 0.5) + m_view_bobbing_anim -= offset; + else + m_view_bobbing_anim += offset; + if (m_view_bobbing_anim <= 0 || m_view_bobbing_anim >= 1) + { + m_view_bobbing_anim = 0; + m_view_bobbing_state = 0; + } +#endif +#if 1 + // Animation is getting turned off + if(m_view_bobbing_anim < 0.25) + { + m_view_bobbing_anim -= offset; + } else if(m_view_bobbing_anim > 0.75) { + m_view_bobbing_anim += offset; + } + if(m_view_bobbing_anim < 0.5) + { + m_view_bobbing_anim += offset; + if(m_view_bobbing_anim > 0.5) + m_view_bobbing_anim = 0.5; + } else { + m_view_bobbing_anim -= offset; + if(m_view_bobbing_anim < 0.5) + m_view_bobbing_anim = 0.5; + } + if(m_view_bobbing_anim <= 0 || m_view_bobbing_anim >= 1 || + fabs(m_view_bobbing_anim - 0.5) < 0.01) + { + m_view_bobbing_anim = 0; + m_view_bobbing_state = 0; + } +#endif + } + else + { + float was = m_view_bobbing_anim; + m_view_bobbing_anim = my_modf(m_view_bobbing_anim + offset); + bool step = (was == 0 || + (was < 0.5f && m_view_bobbing_anim >= 0.5f) || + (was > 0.5f && m_view_bobbing_anim <= 0.5f)); + if(step) + { + MtEvent *e = new SimpleTriggerEvent("ViewBobbingStep"); + m_gamedef->event()->put(e); + } + } + } + + if (m_digging_button != -1) + { + f32 offset = dtime * 3.5; + float m_digging_anim_was = m_digging_anim; + m_digging_anim += offset; + if (m_digging_anim >= 1) + { + m_digging_anim = 0; + m_digging_button = -1; + } + float lim = 0.15; + if(m_digging_anim_was < lim && m_digging_anim >= lim) + { + if(m_digging_button == 0) + { + MtEvent *e = new SimpleTriggerEvent("CameraPunchLeft"); + m_gamedef->event()->put(e); + } else if(m_digging_button == 1) { + MtEvent *e = new SimpleTriggerEvent("CameraPunchRight"); + m_gamedef->event()->put(e); + } + } + } +} + +void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, + f32 tool_reload_ratio, ClientEnvironment &c_env) +{ + // Get player position + // Smooth the movement when walking up stairs + v3f old_player_position = m_playernode->getPosition(); + v3f player_position = player->getPosition(); + if (player->isAttached && player->parent) + player_position = player->parent->getPosition(); + //if(player->touching_ground && player_position.Y > old_player_position.Y) + if(player->touching_ground && + player_position.Y > old_player_position.Y) + { + f32 oldy = old_player_position.Y; + f32 newy = player_position.Y; + f32 t = exp(-23*frametime); + player_position.Y = oldy * t + newy * (1-t); + } + + // Set player node transformation + m_playernode->setPosition(player_position); + m_playernode->setRotation(v3f(0, -1 * player->getYaw(), 0)); + m_playernode->updateAbsolutePosition(); + + // Get camera tilt timer (hurt animation) + float cameratilt = fabs(fabs(player->hurt_tilt_timer-0.75)-0.75); + + // Fall bobbing animation + float fall_bobbing = 0; + if(player->camera_impact >= 1 && m_camera_mode < CAMERA_MODE_THIRD) + { + if(m_view_bobbing_fall == -1) // Effect took place and has finished + player->camera_impact = m_view_bobbing_fall = 0; + else if(m_view_bobbing_fall == 0) // Initialize effect + m_view_bobbing_fall = 1; + + // Convert 0 -> 1 to 0 -> 1 -> 0 + fall_bobbing = m_view_bobbing_fall < 0.5 ? m_view_bobbing_fall * 2 : -(m_view_bobbing_fall - 0.5) * 2 + 1; + // Smoothen and invert the above + fall_bobbing = sin(fall_bobbing * 0.5 * M_PI) * -1; + // Amplify according to the intensity of the impact + fall_bobbing *= (1 - rangelim(50 / player->camera_impact, 0, 1)) * 5; + + fall_bobbing *= m_cache_fall_bobbing_amount; + } + + // Calculate players eye offset for different camera modes + v3f PlayerEyeOffset = player->getEyeOffset(); + if (m_camera_mode == CAMERA_MODE_FIRST) + PlayerEyeOffset += player->eye_offset_first; + else + PlayerEyeOffset += player->eye_offset_third; + + // Set head node transformation + m_headnode->setPosition(PlayerEyeOffset+v3f(0,cameratilt*-player->hurt_tilt_strength+fall_bobbing,0)); + m_headnode->setRotation(v3f(player->getPitch(), 0, cameratilt*player->hurt_tilt_strength)); + m_headnode->updateAbsolutePosition(); + + // Compute relative camera position and target + v3f rel_cam_pos = v3f(0,0,0); + v3f rel_cam_target = v3f(0,0,1); + v3f rel_cam_up = v3f(0,1,0); + + if (m_view_bobbing_anim != 0 && m_camera_mode < CAMERA_MODE_THIRD) + { + f32 bobfrac = my_modf(m_view_bobbing_anim * 2); + f32 bobdir = (m_view_bobbing_anim < 0.5) ? 1.0 : -1.0; + + #if 1 + f32 bobknob = 1.2; + f32 bobtmp = sin(pow(bobfrac, bobknob) * M_PI); + //f32 bobtmp2 = cos(pow(bobfrac, bobknob) * M_PI); + + v3f bobvec = v3f( + 0.3 * bobdir * sin(bobfrac * M_PI), + -0.28 * bobtmp * bobtmp, + 0.); + + //rel_cam_pos += 0.2 * bobvec; + //rel_cam_target += 0.03 * bobvec; + //rel_cam_up.rotateXYBy(0.02 * bobdir * bobtmp * M_PI); + float f = 1.0; + f *= m_cache_view_bobbing_amount; + rel_cam_pos += bobvec * f; + //rel_cam_target += 0.995 * bobvec * f; + rel_cam_target += bobvec * f; + rel_cam_target.Z -= 0.005 * bobvec.Z * f; + //rel_cam_target.X -= 0.005 * bobvec.X * f; + //rel_cam_target.Y -= 0.005 * bobvec.Y * f; + rel_cam_up.rotateXYBy(-0.03 * bobdir * bobtmp * M_PI * f); + #else + f32 angle_deg = 1 * bobdir * sin(bobfrac * M_PI); + f32 angle_rad = angle_deg * M_PI / 180; + f32 r = 0.05; + v3f off = v3f( + r * sin(angle_rad), + r * (cos(angle_rad) - 1), + 0); + rel_cam_pos += off; + //rel_cam_target += off; + rel_cam_up.rotateXYBy(angle_deg); + #endif + + } + + // Compute absolute camera position and target + m_headnode->getAbsoluteTransformation().transformVect(m_camera_position, rel_cam_pos); + m_headnode->getAbsoluteTransformation().rotateVect(m_camera_direction, rel_cam_target - rel_cam_pos); + + v3f abs_cam_up; + m_headnode->getAbsoluteTransformation().rotateVect(abs_cam_up, rel_cam_up); + + // Seperate camera position for calculation + v3f my_cp = m_camera_position; + + // Reposition the camera for third person view + if (m_camera_mode > CAMERA_MODE_FIRST) + { + if (m_camera_mode == CAMERA_MODE_THIRD_FRONT) + m_camera_direction *= -1; + + my_cp.Y += 2; + + // Calculate new position + bool abort = false; + for (int i = BS; i <= BS*2.75; i++) + { + my_cp.X = m_camera_position.X + m_camera_direction.X*-i; + my_cp.Z = m_camera_position.Z + m_camera_direction.Z*-i; + if (i > 12) + my_cp.Y = m_camera_position.Y + (m_camera_direction.Y*-i); + + // Prevent camera positioned inside nodes + INodeDefManager *nodemgr = m_gamedef->ndef(); + MapNode n = c_env.getClientMap().getNodeNoEx(floatToInt(my_cp, BS)); + const ContentFeatures& features = nodemgr->get(n); + if(features.walkable) + { + my_cp.X += m_camera_direction.X*-1*-BS/2; + my_cp.Z += m_camera_direction.Z*-1*-BS/2; + my_cp.Y += m_camera_direction.Y*-1*-BS/2; + abort = true; + break; + } + } + + // If node blocks camera position don't move y to heigh + if (abort && my_cp.Y > player_position.Y+BS*2) + my_cp.Y = player_position.Y+BS*2; + } + + // Update offset if too far away from the center of the map + m_camera_offset.X += CAMERA_OFFSET_STEP* + (((s16)(my_cp.X/BS) - m_camera_offset.X)/CAMERA_OFFSET_STEP); + m_camera_offset.Y += CAMERA_OFFSET_STEP* + (((s16)(my_cp.Y/BS) - m_camera_offset.Y)/CAMERA_OFFSET_STEP); + m_camera_offset.Z += CAMERA_OFFSET_STEP* + (((s16)(my_cp.Z/BS) - m_camera_offset.Z)/CAMERA_OFFSET_STEP); + + // Set camera node transformation + m_cameranode->setPosition(my_cp-intToFloat(m_camera_offset, BS)); + m_cameranode->setUpVector(abs_cam_up); + // *100.0 helps in large map coordinates + m_cameranode->setTarget(my_cp-intToFloat(m_camera_offset, BS) + 100 * m_camera_direction); + + // update the camera position in front-view mode to render blocks behind player + if (m_camera_mode == CAMERA_MODE_THIRD_FRONT) + m_camera_position = my_cp; + + // Get FOV setting + f32 fov_degrees = m_cache_fov; + fov_degrees = MYMAX(fov_degrees, 10.0); + fov_degrees = MYMIN(fov_degrees, 170.0); + + // FOV and aspect ratio + m_aspect = (f32) porting::getWindowSize().X / (f32) porting::getWindowSize().Y; + m_fov_y = fov_degrees * M_PI / 180.0; + // Increase vertical FOV on lower aspect ratios (<16:10) + m_fov_y *= MYMAX(1.0, MYMIN(1.4, sqrt(16./10. / m_aspect))); + m_fov_x = 2 * atan(m_aspect * tan(0.5 * m_fov_y)); + m_cameranode->setAspectRatio(m_aspect); + m_cameranode->setFOV(m_fov_y); + + // Position the wielded item + //v3f wield_position = v3f(45, -35, 65); + v3f wield_position = v3f(55, -35, 65); + //v3f wield_rotation = v3f(-100, 120, -100); + v3f wield_rotation = v3f(-100, 120, -100); + wield_position.Y += fabs(m_wield_change_timer)*320 - 40; + if(m_digging_anim < 0.05 || m_digging_anim > 0.5) + { + f32 frac = 1.0; + if(m_digging_anim > 0.5) + frac = 2.0 * (m_digging_anim - 0.5); + // This value starts from 1 and settles to 0 + f32 ratiothing = pow((1.0f - tool_reload_ratio), 0.5f); + //f32 ratiothing2 = pow(ratiothing, 0.5f); + f32 ratiothing2 = (easeCurve(ratiothing*0.5))*2.0; + wield_position.Y -= frac * 25.0 * pow(ratiothing2, 1.7f); + //wield_position.Z += frac * 5.0 * ratiothing2; + wield_position.X -= frac * 35.0 * pow(ratiothing2, 1.1f); + wield_rotation.Y += frac * 70.0 * pow(ratiothing2, 1.4f); + //wield_rotation.X -= frac * 15.0 * pow(ratiothing2, 1.4f); + //wield_rotation.Z += frac * 15.0 * pow(ratiothing2, 1.0f); + } + if (m_digging_button != -1) + { + f32 digfrac = m_digging_anim; + wield_position.X -= 50 * sin(pow(digfrac, 0.8f) * M_PI); + wield_position.Y += 24 * sin(digfrac * 1.8 * M_PI); + wield_position.Z += 25 * 0.5; + + // Euler angles are PURE EVIL, so why not use quaternions? + core::quaternion quat_begin(wield_rotation * core::DEGTORAD); + core::quaternion quat_end(v3f(80, 30, 100) * core::DEGTORAD); + core::quaternion quat_slerp; + quat_slerp.slerp(quat_begin, quat_end, sin(digfrac * M_PI)); + quat_slerp.toEuler(wield_rotation); + wield_rotation *= core::RADTODEG; + } else { + f32 bobfrac = my_modf(m_view_bobbing_anim); + wield_position.X -= sin(bobfrac*M_PI*2.0) * 3.0; + wield_position.Y += sin(my_modf(bobfrac*2.0)*M_PI) * 3.0; + } + m_wieldnode->setPosition(wield_position); + m_wieldnode->setRotation(wield_rotation); + + // Shine light upon the wield mesh + video::SColor black(255,0,0,0); + m_wieldmgr->setAmbientLight(player->light_color.getInterpolated(black, 0.7)); + m_wieldlightnode->getLightData().DiffuseColor = player->light_color.getInterpolated(black, 0.3); + m_wieldlightnode->setPosition(v3f(30+5*sin(2*player->getYaw()*M_PI/180), -50, 0)); + + // Render distance feedback loop + updateViewingRange(frametime, busytime); + + // If the player is walking, swimming, or climbing, + // view bobbing is enabled and free_move is off, + // start (or continue) the view bobbing animation. + v3f speed = player->getSpeed(); + const bool movement_XZ = hypot(speed.X, speed.Z) > BS; + const bool movement_Y = abs(speed.Y) > BS; + + const bool walking = movement_XZ && player->touching_ground; + const bool swimming = (movement_XZ || player->swimming_vertical) && player->in_liquid; + const bool climbing = movement_Y && player->is_climbing; + if ((walking || swimming || climbing) && + m_cache_view_bobbing && + (!g_settings->getBool("free_move") || !m_gamedef->checkLocalPrivilege("fly"))) + { + // Start animation + m_view_bobbing_state = 1; + m_view_bobbing_speed = MYMIN(speed.getLength(), 40); + } + else if (m_view_bobbing_state == 1) + { + // Stop animation + m_view_bobbing_state = 2; + m_view_bobbing_speed = 60; + } +} + +void Camera::updateViewingRange(f32 frametime_in, f32 busytime_in) +{ + if (m_draw_control.range_all) + return; + + m_added_busytime += busytime_in; + m_added_frames += 1; + + m_frametime_counter -= frametime_in; + if (m_frametime_counter > 0) + return; + m_frametime_counter = 0.2; // Same as ClientMap::updateDrawList interval + + /*dstream<<__FUNCTION_NAME + <<": Collected "<getFloat("viewing_range_nodes_max"); + viewing_range_max = MYMAX(viewing_range_min, viewing_range_max); + + // Immediately apply hard limits + if(m_draw_control.wanted_range < viewing_range_min) + m_draw_control.wanted_range = viewing_range_min; + if(m_draw_control.wanted_range > viewing_range_max) + m_draw_control.wanted_range = viewing_range_max; + + // Just so big a value that everything rendered is visible + // Some more allowance than viewing_range_max * BS because of clouds, + // active objects, etc. + if(viewing_range_max < 200*BS) + m_cameranode->setFarValue(200 * BS * 10); + else + m_cameranode->setFarValue(viewing_range_max * BS * 10); + + f32 wanted_fps = m_cache_wanted_fps; + wanted_fps = MYMAX(wanted_fps, 1.0); + f32 wanted_frametime = 1.0 / wanted_fps; + + m_draw_control.wanted_min_range = viewing_range_min; + m_draw_control.wanted_max_blocks = (2.0*m_draw_control.blocks_would_have_drawn)+1; + if (m_draw_control.wanted_max_blocks < 10) + m_draw_control.wanted_max_blocks = 10; + + f32 block_draw_ratio = 1.0; + if (m_draw_control.blocks_would_have_drawn != 0) + { + block_draw_ratio = (f32)m_draw_control.blocks_drawn + / (f32)m_draw_control.blocks_would_have_drawn; + } + + // Calculate the average frametime in the case that all wanted + // blocks had been drawn + f32 frametime = m_added_busytime / m_added_frames / block_draw_ratio; + + m_added_busytime = 0.0; + m_added_frames = 0; + + f32 wanted_frametime_change = wanted_frametime - frametime; + //dstream<<"wanted_frametime_change="<avg("wanted_frametime_change", wanted_frametime_change); + + // If needed frametime change is small, just return + // This value was 0.4 for many months until 2011-10-18 by c55; + if (fabs(wanted_frametime_change) < wanted_frametime*0.33) + { + //dstream<<"ignoring small wanted_frametime_change"< 0) + m_wield_change_timer = -m_wield_change_timer; + else if (m_wield_change_timer == 0) + m_wield_change_timer = -0.001; + } +} + +void Camera::drawWieldedTool(irr::core::matrix4* translation) +{ + // Clear Z buffer so that the wielded tool stay in front of world geometry + m_wieldmgr->getVideoDriver()->clearZBuffer(); + + // Draw the wielded node (in a separate scene manager) + scene::ICameraSceneNode* cam = m_wieldmgr->getActiveCamera(); + cam->setAspectRatio(m_cameranode->getAspectRatio()); + cam->setFOV(72.0*M_PI/180.0); + cam->setNearValue(0.1); + cam->setFarValue(1000); + if (translation != NULL) + { + irr::core::matrix4 startMatrix = cam->getAbsoluteTransformation(); + irr::core::vector3df focusPoint = (cam->getTarget() + - cam->getAbsolutePosition()).setLength(1) + + cam->getAbsolutePosition(); + + irr::core::vector3df camera_pos = + (startMatrix * *translation).getTranslation(); + cam->setPosition(camera_pos); + cam->setTarget(focusPoint); + } + m_wieldmgr->drawAll(); +} diff --git a/src/camera.h b/src/camera.h new file mode 100644 index 0000000..b537388 --- /dev/null +++ b/src/camera.h @@ -0,0 +1,220 @@ +/* +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. +*/ + +#ifndef CAMERA_HEADER +#define CAMERA_HEADER + +#include "irrlichttypes_extrabloated.h" +#include "inventory.h" +#include "mesh.h" +#include "client/tile.h" +#include "util/numeric.h" +#include + +#include "client.h" + +class LocalPlayer; +struct MapDrawControl; +class IGameDef; +class WieldMeshSceneNode; + +enum CameraMode {CAMERA_MODE_FIRST, CAMERA_MODE_THIRD, CAMERA_MODE_THIRD_FRONT}; + +/* + Client camera class, manages the player and camera scene nodes, the viewing distance + and performs view bobbing etc. It also displays the wielded tool in front of the + first-person camera. +*/ +class Camera +{ +public: + Camera(scene::ISceneManager* smgr, MapDrawControl& draw_control, + IGameDef *gamedef); + ~Camera(); + + // Get player scene node. + // This node is positioned at the player's torso (without any view bobbing), + // as given by Player::m_position. Yaw is applied but not pitch. + inline scene::ISceneNode* getPlayerNode() const + { + return m_playernode; + } + + // Get head scene node. + // It has the eye transformation and pitch applied, + // but no view bobbing. + inline scene::ISceneNode* getHeadNode() const + { + return m_headnode; + } + + // Get camera scene node. + // It has the eye transformation, pitch and view bobbing applied. + inline scene::ICameraSceneNode* getCameraNode() const + { + return m_cameranode; + } + + // Get the camera position (in absolute scene coordinates). + // This has view bobbing applied. + inline v3f getPosition() const + { + return m_camera_position; + } + + // Get the camera direction (in absolute camera coordinates). + // This has view bobbing applied. + inline v3f getDirection() const + { + return m_camera_direction; + } + + // Get the camera offset + inline v3s16 getOffset() const + { + return m_camera_offset; + } + + // Horizontal field of view + inline f32 getFovX() const + { + return m_fov_x; + } + + // Vertical field of view + inline f32 getFovY() const + { + return m_fov_y; + } + + // Get maximum of getFovX() and getFovY() + inline f32 getFovMax() const + { + return MYMAX(m_fov_x, m_fov_y); + } + + // Checks if the constructor was able to create the scene nodes + bool successfullyCreated(std::wstring& error_message); + + // Step the camera: updates the viewing range and view bobbing. + void step(f32 dtime); + + // Update the camera from the local player's position. + // busytime is used to adjust the viewing range. + void update(LocalPlayer* player, f32 frametime, f32 busytime, + f32 tool_reload_ratio, ClientEnvironment &c_env); + + // Render distance feedback loop + void updateViewingRange(f32 frametime_in, f32 busytime_in); + + // Start digging animation + // Pass 0 for left click, 1 for right click + void setDigging(s32 button); + + // Replace the wielded item mesh + void wield(const ItemStack &item); + + // Draw the wielded tool. + // This has to happen *after* the main scene is drawn. + // Warning: This clears the Z buffer. + void drawWieldedTool(irr::core::matrix4* translation=NULL); + + // Toggle the current camera mode + void toggleCameraMode() { + if (m_camera_mode == CAMERA_MODE_FIRST) + m_camera_mode = CAMERA_MODE_THIRD; + else if (m_camera_mode == CAMERA_MODE_THIRD) + m_camera_mode = CAMERA_MODE_THIRD_FRONT; + else + m_camera_mode = CAMERA_MODE_FIRST; + } + + //read the current camera mode + inline CameraMode getCameraMode() + { + return m_camera_mode; + } + +private: + // Nodes + scene::ISceneNode* m_playernode; + scene::ISceneNode* m_headnode; + scene::ICameraSceneNode* m_cameranode; + + scene::ISceneManager* m_wieldmgr; + WieldMeshSceneNode* m_wieldnode; + scene::ILightSceneNode* m_wieldlightnode; + + // draw control + MapDrawControl& m_draw_control; + + IGameDef *m_gamedef; + + // Absolute camera position + v3f m_camera_position; + // Absolute camera direction + v3f m_camera_direction; + // Camera offset + v3s16 m_camera_offset; + + // Field of view and aspect ratio stuff + f32 m_aspect; + f32 m_fov_x; + f32 m_fov_y; + + // Stuff for viewing range calculations + f32 m_added_busytime; + s16 m_added_frames; + f32 m_range_old; + f32 m_busytime_old; + f32 m_frametime_counter; + f32 m_time_per_range; + + // View bobbing animation frame (0 <= m_view_bobbing_anim < 1) + f32 m_view_bobbing_anim; + // If 0, view bobbing is off (e.g. player is standing). + // If 1, view bobbing is on (player is walking). + // If 2, view bobbing is getting switched off. + s32 m_view_bobbing_state; + // Speed of view bobbing animation + f32 m_view_bobbing_speed; + // Fall view bobbing + f32 m_view_bobbing_fall; + + // Digging animation frame (0 <= m_digging_anim < 1) + f32 m_digging_anim; + // If -1, no digging animation + // If 0, left-click digging animation + // If 1, right-click digging animation + s32 m_digging_button; + + // Animation when changing wielded item + f32 m_wield_change_timer; + ItemStack m_wield_item_next; + + CameraMode m_camera_mode; + + f32 m_cache_fall_bobbing_amount; + f32 m_cache_view_bobbing_amount; + f32 m_cache_wanted_fps; + f32 m_cache_fov; + bool m_cache_view_bobbing; +}; + +#endif diff --git a/src/cavegen.cpp b/src/cavegen.cpp new file mode 100644 index 0000000..8fb1a72 --- /dev/null +++ b/src/cavegen.cpp @@ -0,0 +1,823 @@ +/* +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. +*/ + +#include "util/numeric.h" +#include "map.h" +#include "mapgen.h" +#include "mapgen_v5.h" +#include "mapgen_v6.h" +#include "mapgen_v7.h" +#include "cavegen.h" + +NoiseParams nparams_caveliquids(0, 1, v3f(150.0, 150.0, 150.0), 776, 3, 0.6, 2.0); + + +///////////////////////////////////////// Caves V5 + + +CaveV5::CaveV5(MapgenV5 *mg, PseudoRandom *ps) { + this->mg = mg; + this->vm = mg->vm; + this->ndef = mg->ndef; + this->water_level = mg->water_level; + this->ps = ps; + this->c_water_source = mg->c_water_source; + this->c_lava_source = mg->c_lava_source; + this->c_ice = mg->c_ice; + this->np_caveliquids = &nparams_caveliquids; + + dswitchint = ps->range(1, 14); + flooded = ps->range(1, 2) == 2; + + part_max_length_rs = ps->range(2, 4); + tunnel_routepoints = ps->range(5, ps->range(15, 30)); + min_tunnel_diameter = 5; + max_tunnel_diameter = ps->range(7, ps->range(8, 24)); + + large_cave_is_flat = (ps->range(0, 1) == 0); +} + + +void CaveV5::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) { + node_min = nmin; + node_max = nmax; + main_direction = v3f(0, 0, 0); + + // Allowed route area size in nodes + ar = node_max - node_min + v3s16(1, 1, 1); + // Area starting point in nodes + of = node_min; + + // Allow a bit more + //(this should be more than the maximum radius of the tunnel) + s16 insure = 10; + s16 more = MYMAX(MAP_BLOCKSIZE - max_tunnel_diameter / 2 - insure, 1); + ar += v3s16(1,0,1) * more * 2; + of -= v3s16(1,0,1) * more; + + route_y_min = 0; + // Allow half a diameter + 7 over stone surface + route_y_max = -of.Y + max_stone_y + max_tunnel_diameter / 2 + 7; + + // Limit maximum to area + route_y_max = rangelim(route_y_max, 0, ar.Y - 1); + + s16 min = 0; + if (node_min.Y < water_level && node_max.Y > water_level) { + min = water_level - max_tunnel_diameter/3 - of.Y; + route_y_max = water_level + max_tunnel_diameter/3 - of.Y; + } + route_y_min = ps->range(min, min + max_tunnel_diameter); + route_y_min = rangelim(route_y_min, 0, route_y_max); + + s16 route_start_y_min = route_y_min; + s16 route_start_y_max = route_y_max; + + route_start_y_min = rangelim(route_start_y_min, 0, ar.Y - 1); + route_start_y_max = rangelim(route_start_y_max, route_start_y_min, ar.Y - 1); + + // Randomize starting position + orp = v3f( + (float)(ps->next() % ar.X) + 0.5, + (float)(ps->range(route_start_y_min, route_start_y_max)) + 0.5, + (float)(ps->next() % ar.Z) + 0.5 + ); + + // Add generation notify begin event + v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); + GenNotifyType notifytype = GENNOTIFY_LARGECAVE_BEGIN; + mg->gennotify.addEvent(notifytype, abs_pos); + + // Generate some tunnel starting from orp + for (u16 j = 0; j < tunnel_routepoints; j++) + makeTunnel(j % dswitchint == 0); + + // Add generation notify end event + abs_pos = v3s16(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); + notifytype = GENNOTIFY_LARGECAVE_END; + mg->gennotify.addEvent(notifytype, abs_pos); +} + + +void CaveV5::makeTunnel(bool dirswitch) { + + // Randomize size + s16 min_d = min_tunnel_diameter; + s16 max_d = max_tunnel_diameter; + rs = ps->range(min_d, max_d); + s16 rs_part_max_length_rs = rs * part_max_length_rs; + + v3s16 maxlen; + maxlen = v3s16( + rs_part_max_length_rs, + rs_part_max_length_rs / 2, + rs_part_max_length_rs + ); + + v3f vec; + // Jump downward sometimes + vec = v3f( + (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2, + (float)(ps->next() % maxlen.Y) - (float)maxlen.Y / 2, + (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2 + ); + + // Do not make large caves that are above ground. + // It is only necessary to check the startpoint and endpoint. + v3s16 orpi(orp.X, orp.Y, orp.Z); + v3s16 veci(vec.X, vec.Y, vec.Z); + v3s16 p; + + p = orpi + veci + of + rs / 2; + if (p.Z >= node_min.Z && p.Z <= node_max.Z && + p.X >= node_min.X && p.X <= node_max.X) { + u32 index = (p.Z - node_min.Z) * mg->ystride + (p.X - node_min.X); + s16 h = mg->heightmap[index]; + if (h < p.Y) + return; + } else if (p.Y > water_level) { + return; // If it's not in our heightmap, use a simple heuristic + } + + p = orpi + of + rs / 2; + if (p.Z >= node_min.Z && p.Z <= node_max.Z && + p.X >= node_min.X && p.X <= node_max.X) { + u32 index = (p.Z - node_min.Z) * mg->ystride + (p.X - node_min.X); + s16 h = mg->heightmap[index]; + if (h < p.Y) + return; + } else if (p.Y > water_level) { + return; + } + + vec += main_direction; + + v3f rp = orp + vec; + if (rp.X < 0) + rp.X = 0; + else if (rp.X >= ar.X) + rp.X = ar.X - 1; + + if (rp.Y < route_y_min) + rp.Y = route_y_min; + else if (rp.Y >= route_y_max) + rp.Y = route_y_max - 1; + + if (rp.Z < 0) + rp.Z = 0; + else if (rp.Z >= ar.Z) + rp.Z = ar.Z - 1; + + vec = rp - orp; + + float veclen = vec.getLength(); + if (veclen < 0.05) + veclen = 1.0; + + // Every second section is rough + bool randomize_xz = (ps->range(1, 2) == 1); + + // Make a ravine every once in a while if it's long enough + //float xylen = vec.X * vec.X + vec.Z * vec.Z; + //disable ravines for now + bool is_ravine = false; //(xylen > 500.0) && !large_cave && (ps->range(1, 8) == 1); + + // Carve routes + for (float f = 0; f < 1.0; f += 1.0 / veclen) + carveRoute(vec, f, randomize_xz, is_ravine); + + orp = rp; +} + + +void CaveV5::carveRoute(v3f vec, float f, bool randomize_xz, bool is_ravine) { + MapNode airnode(CONTENT_AIR); + MapNode waternode(c_water_source); + MapNode lavanode(c_lava_source); + + v3s16 startp(orp.X, orp.Y, orp.Z); + startp += of; + + float nval = NoisePerlin3D(np_caveliquids, startp.X, + startp.Y, startp.Z, mg->seed); + MapNode liquidnode = nval < 0.40 ? lavanode : waternode; + + v3f fp = orp + vec * f; + fp.X += 0.1 * ps->range(-10, 10); + fp.Z += 0.1 * ps->range(-10, 10); + v3s16 cp(fp.X, fp.Y, fp.Z); + + s16 d0 = -rs/2; + s16 d1 = d0 + rs; + if (randomize_xz) { + d0 += ps->range(-1, 1); + d1 += ps->range(-1, 1); + } + + bool should_make_cave_hole = ps->range(1, 10) == 1; + + for (s16 z0 = d0; z0 <= d1; z0++) { + s16 si = rs / 2 - MYMAX(0, abs(z0) - rs / 7 - 1); + for (s16 x0 = -si - ps->range(0,1); x0 <= si - 1 + ps->range(0,1); x0++) { + s16 maxabsxz = MYMAX(abs(x0), abs(z0)); + + s16 si2 = is_ravine ? MYMIN(ps->range(25, 26), ar.Y) : + rs / 2 - MYMAX(0, maxabsxz - rs / 7 - 1); + + for (s16 y0 = -si2; y0 <= si2; y0++) { + if (large_cave_is_flat) { + // Make large caves not so tall + if (rs > 7 && abs(y0) >= rs / 3) + continue; + } + + v3s16 p(cp.X + x0, cp.Y + y0, cp.Z + z0); + p += of; + + if (!is_ravine && mg->heightmap && should_make_cave_hole && + p.X <= node_max.X && p.Z <= node_max.Z) { + int maplen = node_max.X - node_min.X + 1; + int idx = (p.Z - node_min.Z) * maplen + (p.X - node_min.X); + if (p.Y >= mg->heightmap[idx] - 2) + continue; + } + + if (vm->m_area.contains(p) == false) + continue; + + u32 i = vm->m_area.index(p); + + // Don't replace air, water, lava, or ice + content_t c = vm->m_data[i].getContent(); + if (!ndef->get(c).is_ground_content || c == CONTENT_AIR || + c == c_water_source || c == c_lava_source || c == c_ice) + continue; + + int full_ymin = node_min.Y - MAP_BLOCKSIZE; + int full_ymax = node_max.Y + MAP_BLOCKSIZE; + + if (flooded && full_ymin < water_level && full_ymax > water_level) + vm->m_data[i] = (p.Y <= water_level) ? waternode : airnode; + else if (flooded && full_ymax < water_level) + vm->m_data[i] = (p.Y < startp.Y - 4) ? liquidnode : airnode; + else + vm->m_data[i] = airnode; + } + } + } +} + + +///////////////////////////////////////// Caves V6 + + +CaveV6::CaveV6(MapgenV6 *mg, PseudoRandom *ps, PseudoRandom *ps2, bool is_large_cave) { + this->mg = mg; + this->vm = mg->vm; + this->ndef = mg->ndef; + this->water_level = mg->water_level; + this->large_cave = is_large_cave; + this->ps = ps; + this->ps2 = ps2; + this->c_water_source = mg->c_water_source; + this->c_lava_source = mg->c_lava_source; + + min_tunnel_diameter = 2; + max_tunnel_diameter = ps->range(2, 6); + dswitchint = ps->range(1, 14); + flooded = true; + + if (large_cave) { + part_max_length_rs = ps->range(2,4); + tunnel_routepoints = ps->range(5, ps->range(15,30)); + min_tunnel_diameter = 5; + max_tunnel_diameter = ps->range(7, ps->range(8,24)); + } else { + part_max_length_rs = ps->range(2,9); + tunnel_routepoints = ps->range(10, ps->range(15,30)); + } + + large_cave_is_flat = (ps->range(0,1) == 0); +} + + +void CaveV6::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) { + node_min = nmin; + node_max = nmax; + max_stone_y = max_stone_height; + main_direction = v3f(0, 0, 0); + + // Allowed route area size in nodes + ar = node_max - node_min + v3s16(1, 1, 1); + // Area starting point in nodes + of = node_min; + + // Allow a bit more + //(this should be more than the maximum radius of the tunnel) + const s16 max_spread_amount = MAP_BLOCKSIZE; + s16 insure = 10; + s16 more = MYMAX(max_spread_amount - max_tunnel_diameter / 2 - insure, 1); + ar += v3s16(1,0,1) * more * 2; + of -= v3s16(1,0,1) * more; + + route_y_min = 0; + // Allow half a diameter + 7 over stone surface + route_y_max = -of.Y + max_stone_y + max_tunnel_diameter / 2 + 7; + + // Limit maximum to area + route_y_max = rangelim(route_y_max, 0, ar.Y - 1); + + if (large_cave) { + s16 min = 0; + if (node_min.Y < water_level && node_max.Y > water_level) { + min = water_level - max_tunnel_diameter/3 - of.Y; + route_y_max = water_level + max_tunnel_diameter/3 - of.Y; + } + route_y_min = ps->range(min, min + max_tunnel_diameter); + route_y_min = rangelim(route_y_min, 0, route_y_max); + } + + s16 route_start_y_min = route_y_min; + s16 route_start_y_max = route_y_max; + + route_start_y_min = rangelim(route_start_y_min, 0, ar.Y-1); + route_start_y_max = rangelim(route_start_y_max, route_start_y_min, ar.Y-1); + + // Randomize starting position + orp = v3f( + (float)(ps->next() % ar.X) + 0.5, + (float)(ps->range(route_start_y_min, route_start_y_max)) + 0.5, + (float)(ps->next() % ar.Z) + 0.5 + ); + + // Add generation notify begin event + v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); + GenNotifyType notifytype = large_cave ? + GENNOTIFY_LARGECAVE_BEGIN : GENNOTIFY_CAVE_BEGIN; + mg->gennotify.addEvent(notifytype, abs_pos); + + // Generate some tunnel starting from orp + for (u16 j = 0; j < tunnel_routepoints; j++) + makeTunnel(j % dswitchint == 0); + + // Add generation notify end event + abs_pos = v3s16(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); + notifytype = large_cave ? + GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END; + mg->gennotify.addEvent(notifytype, abs_pos); +} + + +void CaveV6::makeTunnel(bool dirswitch) { + if (dirswitch && !large_cave) { + main_direction = v3f( + ((float)(ps->next() % 20) - (float)10) / 10, + ((float)(ps->next() % 20) - (float)10) / 30, + ((float)(ps->next() % 20) - (float)10) / 10 + ); + main_direction *= (float)ps->range(0, 10) / 10; + } + + // Randomize size + s16 min_d = min_tunnel_diameter; + s16 max_d = max_tunnel_diameter; + rs = ps->range(min_d, max_d); + s16 rs_part_max_length_rs = rs * part_max_length_rs; + + v3s16 maxlen; + if (large_cave) { + maxlen = v3s16( + rs_part_max_length_rs, + rs_part_max_length_rs / 2, + rs_part_max_length_rs + ); + } else { + maxlen = v3s16( + rs_part_max_length_rs, + ps->range(1, rs_part_max_length_rs), + rs_part_max_length_rs + ); + } + + v3f vec( + (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2, + (float)(ps->next() % maxlen.Y) - (float)maxlen.Y / 2, + (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2 + ); + + // Jump downward sometimes + if (!large_cave && ps->range(0, 12) == 0) { + vec = v3f( + (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2, + (float)(ps->next() % (maxlen.Y * 2)) - (float)maxlen.Y, + (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2 + ); + } + + // Do not make large caves that are entirely above ground. + // It is only necessary to check the startpoint and endpoint. + if (large_cave) { + v3s16 orpi(orp.X, orp.Y, orp.Z); + v3s16 veci(vec.X, vec.Y, vec.Z); + s16 h1; + s16 h2; + + v3s16 p1 = orpi + veci + of + rs / 2; + if (p1.Z >= node_min.Z && p1.Z <= node_max.Z && + p1.X >= node_min.X && p1.X <= node_max.X) { + u32 index1 = (p1.Z - node_min.Z) * mg->ystride + (p1.X - node_min.X); + h1 = mg->heightmap[index1]; + } else { + h1 = water_level; // If not in heightmap + } + + v3s16 p2 = orpi + of + rs / 2; + if (p2.Z >= node_min.Z && p2.Z <= node_max.Z && + p2.X >= node_min.X && p2.X <= node_max.X) { + u32 index2 = (p2.Z - node_min.Z) * mg->ystride + (p2.X - node_min.X); + h2 = mg->heightmap[index2]; + } else { + h2 = water_level; + } + + if (p1.Y > h1 && p2.Y > h2) // If startpoint and endpoint are above ground + return; + } + + vec += main_direction; + + v3f rp = orp + vec; + if (rp.X < 0) + rp.X = 0; + else if (rp.X >= ar.X) + rp.X = ar.X - 1; + + if (rp.Y < route_y_min) + rp.Y = route_y_min; + else if (rp.Y >= route_y_max) + rp.Y = route_y_max - 1; + + if (rp.Z < 0) + rp.Z = 0; + else if (rp.Z >= ar.Z) + rp.Z = ar.Z - 1; + + vec = rp - orp; + + float veclen = vec.getLength(); + // As odd as it sounds, veclen is *exactly* 0.0 sometimes, causing a FPE + if (veclen < 0.05) + veclen = 1.0; + + // Every second section is rough + bool randomize_xz = (ps2->range(1, 2) == 1); + + // Carve routes + for (float f = 0; f < 1.0; f += 1.0 / veclen) + carveRoute(vec, f, randomize_xz); + + orp = rp; +} + + +void CaveV6::carveRoute(v3f vec, float f, bool randomize_xz) { + MapNode airnode(CONTENT_AIR); + MapNode waternode(c_water_source); + MapNode lavanode(c_lava_source); + + v3s16 startp(orp.X, orp.Y, orp.Z); + startp += of; + + v3f fp = orp + vec * f; + fp.X += 0.1 * ps->range(-10, 10); + fp.Z += 0.1 * ps->range(-10, 10); + v3s16 cp(fp.X, fp.Y, fp.Z); + + s16 d0 = -rs/2; + s16 d1 = d0 + rs; + if (randomize_xz) { + d0 += ps->range(-1, 1); + d1 += ps->range(-1, 1); + } + + for (s16 z0 = d0; z0 <= d1; z0++) { + s16 si = rs / 2 - MYMAX(0, abs(z0) - rs / 7 - 1); + for (s16 x0 = -si - ps->range(0,1); x0 <= si - 1 + ps->range(0,1); x0++) { + s16 maxabsxz = MYMAX(abs(x0), abs(z0)); + s16 si2 = rs / 2 - MYMAX(0, maxabsxz - rs / 7 - 1); + for (s16 y0 = -si2; y0 <= si2; y0++) { + if (large_cave_is_flat) { + // Make large caves not so tall + if (rs > 7 && abs(y0) >= rs / 3) + continue; + } + + v3s16 p(cp.X + x0, cp.Y + y0, cp.Z + z0); + p += of; + + if (vm->m_area.contains(p) == false) + continue; + + u32 i = vm->m_area.index(p); + content_t c = vm->m_data[i].getContent(); + if (!ndef->get(c).is_ground_content) + continue; + + if (large_cave) { + int full_ymin = node_min.Y - MAP_BLOCKSIZE; + int full_ymax = node_max.Y + MAP_BLOCKSIZE; + + if (flooded && full_ymin < water_level && full_ymax > water_level) { + vm->m_data[i] = (p.Y <= water_level) ? waternode : airnode; + } else if (flooded && full_ymax < water_level) { + vm->m_data[i] = (p.Y < startp.Y - 2) ? lavanode : airnode; + } else { + vm->m_data[i] = airnode; + } + } else { + // Don't replace air or water or lava or ignore + if (c == CONTENT_IGNORE || c == CONTENT_AIR || + c == c_water_source || c == c_lava_source) + continue; + + vm->m_data[i] = airnode; + vm->m_flags[i] |= VMANIP_FLAG_CAVE; + } + } + } + } +} + + +///////////////////////////////////////// Caves V7 + + +CaveV7::CaveV7(MapgenV7 *mg, PseudoRandom *ps) { + this->mg = mg; + this->vm = mg->vm; + this->ndef = mg->ndef; + this->water_level = mg->water_level; + this->ps = ps; + this->c_water_source = mg->c_water_source; + this->c_lava_source = mg->c_lava_source; + this->c_ice = mg->c_ice; + this->np_caveliquids = &nparams_caveliquids; + + dswitchint = ps->range(1, 14); + flooded = ps->range(1, 2) == 2; + + part_max_length_rs = ps->range(2, 4); + tunnel_routepoints = ps->range(5, ps->range(15, 30)); + min_tunnel_diameter = 5; + max_tunnel_diameter = ps->range(7, ps->range(8, 24)); + + large_cave_is_flat = (ps->range(0, 1) == 0); +} + + +void CaveV7::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) { + node_min = nmin; + node_max = nmax; + max_stone_y = max_stone_height; + main_direction = v3f(0, 0, 0); + + // Allowed route area size in nodes + ar = node_max - node_min + v3s16(1, 1, 1); + // Area starting point in nodes + of = node_min; + + // Allow a bit more + //(this should be more than the maximum radius of the tunnel) + s16 insure = 10; + s16 more = MYMAX(MAP_BLOCKSIZE - max_tunnel_diameter / 2 - insure, 1); + ar += v3s16(1,0,1) * more * 2; + of -= v3s16(1,0,1) * more; + + route_y_min = 0; + // Allow half a diameter + 7 over stone surface + route_y_max = -of.Y + max_stone_y + max_tunnel_diameter / 2 + 7; + + // Limit maximum to area + route_y_max = rangelim(route_y_max, 0, ar.Y - 1); + + s16 min = 0; + if (node_min.Y < water_level && node_max.Y > water_level) { + min = water_level - max_tunnel_diameter/3 - of.Y; + route_y_max = water_level + max_tunnel_diameter/3 - of.Y; + } + route_y_min = ps->range(min, min + max_tunnel_diameter); + route_y_min = rangelim(route_y_min, 0, route_y_max); + + s16 route_start_y_min = route_y_min; + s16 route_start_y_max = route_y_max; + + route_start_y_min = rangelim(route_start_y_min, 0, ar.Y - 1); + route_start_y_max = rangelim(route_start_y_max, route_start_y_min, ar.Y - 1); + + // Randomize starting position + orp = v3f( + (float)(ps->next() % ar.X) + 0.5, + (float)(ps->range(route_start_y_min, route_start_y_max)) + 0.5, + (float)(ps->next() % ar.Z) + 0.5 + ); + + // Add generation notify begin event + v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); + GenNotifyType notifytype = GENNOTIFY_LARGECAVE_BEGIN; + mg->gennotify.addEvent(notifytype, abs_pos); + + // Generate some tunnel starting from orp + for (u16 j = 0; j < tunnel_routepoints; j++) + makeTunnel(j % dswitchint == 0); + + // Add generation notify end event + abs_pos = v3s16(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); + notifytype = GENNOTIFY_LARGECAVE_END; + mg->gennotify.addEvent(notifytype, abs_pos); +} + + +void CaveV7::makeTunnel(bool dirswitch) { + + // Randomize size + s16 min_d = min_tunnel_diameter; + s16 max_d = max_tunnel_diameter; + rs = ps->range(min_d, max_d); + s16 rs_part_max_length_rs = rs * part_max_length_rs; + + v3s16 maxlen; + maxlen = v3s16( + rs_part_max_length_rs, + rs_part_max_length_rs / 2, + rs_part_max_length_rs + ); + + v3f vec; + // Jump downward sometimes + vec = v3f( + (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2, + (float)(ps->next() % maxlen.Y) - (float)maxlen.Y / 2, + (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2 + ); + + // Do not make large caves that are above ground. + // It is only necessary to check the startpoint and endpoint. + v3s16 orpi(orp.X, orp.Y, orp.Z); + v3s16 veci(vec.X, vec.Y, vec.Z); + v3s16 p; + + p = orpi + veci + of + rs / 2; + if (p.Z >= node_min.Z && p.Z <= node_max.Z && + p.X >= node_min.X && p.X <= node_max.X) { + u32 index = (p.Z - node_min.Z) * mg->ystride + (p.X - node_min.X); + s16 h = mg->ridge_heightmap[index]; + if (h < p.Y) + return; + } else if (p.Y > water_level) { + return; // If it's not in our heightmap, use a simple heuristic + } + + p = orpi + of + rs / 2; + if (p.Z >= node_min.Z && p.Z <= node_max.Z && + p.X >= node_min.X && p.X <= node_max.X) { + u32 index = (p.Z - node_min.Z) * mg->ystride + (p.X - node_min.X); + s16 h = mg->ridge_heightmap[index]; + if (h < p.Y) + return; + } else if (p.Y > water_level) { + return; + } + + vec += main_direction; + + v3f rp = orp + vec; + if (rp.X < 0) + rp.X = 0; + else if (rp.X >= ar.X) + rp.X = ar.X - 1; + + if (rp.Y < route_y_min) + rp.Y = route_y_min; + else if (rp.Y >= route_y_max) + rp.Y = route_y_max - 1; + + if (rp.Z < 0) + rp.Z = 0; + else if (rp.Z >= ar.Z) + rp.Z = ar.Z - 1; + + vec = rp - orp; + + float veclen = vec.getLength(); + if (veclen < 0.05) + veclen = 1.0; + + // Every second section is rough + bool randomize_xz = (ps->range(1, 2) == 1); + + // Make a ravine every once in a while if it's long enough + //float xylen = vec.X * vec.X + vec.Z * vec.Z; + //disable ravines for now + bool is_ravine = false; //(xylen > 500.0) && !large_cave && (ps->range(1, 8) == 1); + + // Carve routes + for (float f = 0; f < 1.0; f += 1.0 / veclen) + carveRoute(vec, f, randomize_xz, is_ravine); + + orp = rp; +} + + +void CaveV7::carveRoute(v3f vec, float f, bool randomize_xz, bool is_ravine) { + MapNode airnode(CONTENT_AIR); + MapNode waternode(c_water_source); + MapNode lavanode(c_lava_source); + + v3s16 startp(orp.X, orp.Y, orp.Z); + startp += of; + + float nval = NoisePerlin3D(np_caveliquids, startp.X, + startp.Y, startp.Z, mg->seed); + MapNode liquidnode = (nval < 0.40 && node_max.Y < -256) ? lavanode : waternode; + + v3f fp = orp + vec * f; + fp.X += 0.1 * ps->range(-10, 10); + fp.Z += 0.1 * ps->range(-10, 10); + v3s16 cp(fp.X, fp.Y, fp.Z); + + s16 d0 = -rs/2; + s16 d1 = d0 + rs; + if (randomize_xz) { + d0 += ps->range(-1, 1); + d1 += ps->range(-1, 1); + } + + bool should_make_cave_hole = ps->range(1, 10) == 1; + + for (s16 z0 = d0; z0 <= d1; z0++) { + s16 si = rs / 2 - MYMAX(0, abs(z0) - rs / 7 - 1); + for (s16 x0 = -si - ps->range(0,1); x0 <= si - 1 + ps->range(0,1); x0++) { + s16 maxabsxz = MYMAX(abs(x0), abs(z0)); + + s16 si2 = is_ravine ? MYMIN(ps->range(25, 26), ar.Y) : + rs / 2 - MYMAX(0, maxabsxz - rs / 7 - 1); + + for (s16 y0 = -si2; y0 <= si2; y0++) { + if (large_cave_is_flat) { + // Make large caves not so tall + if (rs > 7 && abs(y0) >= rs / 3) + continue; + } + + v3s16 p(cp.X + x0, cp.Y + y0, cp.Z + z0); + p += of; + + if (!is_ravine && mg->heightmap && should_make_cave_hole && + p.X <= node_max.X && p.Z <= node_max.Z) { + int maplen = node_max.X - node_min.X + 1; + int idx = (p.Z - node_min.Z) * maplen + (p.X - node_min.X); + if (p.Y >= mg->heightmap[idx] - 2) + continue; + } + + if (vm->m_area.contains(p) == false) + continue; + + u32 i = vm->m_area.index(p); + + // Don't replace air, water, lava, or ice + content_t c = vm->m_data[i].getContent(); + if (!ndef->get(c).is_ground_content || c == CONTENT_AIR || + c == c_water_source || c == c_lava_source || c == c_ice) + continue; + + int full_ymin = node_min.Y - MAP_BLOCKSIZE; + int full_ymax = node_max.Y + MAP_BLOCKSIZE; + + if (flooded && full_ymin < water_level && full_ymax > water_level) + vm->m_data[i] = (p.Y <= water_level) ? waternode : airnode; + else if (flooded && full_ymax < water_level) + vm->m_data[i] = (p.Y < startp.Y - 4) ? liquidnode : airnode; + else + vm->m_data[i] = airnode; + } + } + } +} + diff --git a/src/cavegen.h b/src/cavegen.h new file mode 100644 index 0000000..2762675 --- /dev/null +++ b/src/cavegen.h @@ -0,0 +1,163 @@ +/* +Minetest +Copyright (C) 2010-2013 kwolekr, Ryan Kwolek + +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. +*/ + +#ifndef CAVEGEN_HEADER +#define CAVEGEN_HEADER + +#define VMANIP_FLAG_CAVE VOXELFLAG_CHECKED1 + +class MapgenV5; +class MapgenV6; +class MapgenV7; + +class CaveV5 { +public: + MapgenV5 *mg; + MMVManip *vm; + INodeDefManager *ndef; + + NoiseParams *np_caveliquids; + + s16 min_tunnel_diameter; + s16 max_tunnel_diameter; + u16 tunnel_routepoints; + int dswitchint; + int part_max_length_rs; + + bool large_cave_is_flat; + bool flooded; + + s16 max_stone_y; + v3s16 node_min; + v3s16 node_max; + + v3f orp; // starting point, relative to caved space + v3s16 of; // absolute coordinates of caved space + v3s16 ar; // allowed route area + s16 rs; // tunnel radius size + v3f main_direction; + + s16 route_y_min; + s16 route_y_max; + + PseudoRandom *ps; + + content_t c_water_source; + content_t c_lava_source; + content_t c_ice; + + int water_level; + + CaveV5() {} + CaveV5(MapgenV5 *mg, PseudoRandom *ps); + void makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height); + void makeTunnel(bool dirswitch); + void carveRoute(v3f vec, float f, bool randomize_xz, bool is_ravine); +}; + +class CaveV6 { +public: + MapgenV6 *mg; + MMVManip *vm; + INodeDefManager *ndef; + + s16 min_tunnel_diameter; + s16 max_tunnel_diameter; + u16 tunnel_routepoints; + int dswitchint; + int part_max_length_rs; + + bool large_cave; + bool large_cave_is_flat; + bool flooded; + + s16 max_stone_y; + v3s16 node_min; + v3s16 node_max; + + v3f orp; // starting point, relative to caved space + v3s16 of; // absolute coordinates of caved space + v3s16 ar; // allowed route area + s16 rs; // tunnel radius size + v3f main_direction; + + s16 route_y_min; + s16 route_y_max; + + PseudoRandom *ps; + PseudoRandom *ps2; + + content_t c_water_source; + content_t c_lava_source; + + int water_level; + + CaveV6() {} + CaveV6(MapgenV6 *mg, PseudoRandom *ps, PseudoRandom *ps2, bool large_cave); + void makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height); + void makeTunnel(bool dirswitch); + void carveRoute(v3f vec, float f, bool randomize_xz); +}; + +class CaveV7 { +public: + MapgenV7 *mg; + MMVManip *vm; + INodeDefManager *ndef; + + NoiseParams *np_caveliquids; + + s16 min_tunnel_diameter; + s16 max_tunnel_diameter; + u16 tunnel_routepoints; + int dswitchint; + int part_max_length_rs; + + bool large_cave_is_flat; + bool flooded; + + s16 max_stone_y; + v3s16 node_min; + v3s16 node_max; + + v3f orp; // starting point, relative to caved space + v3s16 of; // absolute coordinates of caved space + v3s16 ar; // allowed route area + s16 rs; // tunnel radius size + v3f main_direction; + + s16 route_y_min; + s16 route_y_max; + + PseudoRandom *ps; + + content_t c_water_source; + content_t c_lava_source; + content_t c_ice; + + int water_level; + + CaveV7() {} + CaveV7(MapgenV7 *mg, PseudoRandom *ps); + void makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height); + void makeTunnel(bool dirswitch); + void carveRoute(v3f vec, float f, bool randomize_xz, bool is_ravine); +}; + +#endif diff --git a/src/cguittfont/CGUITTFont.cpp b/src/cguittfont/CGUITTFont.cpp new file mode 100644 index 0000000..2342eb7 --- /dev/null +++ b/src/cguittfont/CGUITTFont.cpp @@ -0,0 +1,1175 @@ +/* + CGUITTFont FreeType class for Irrlicht + Copyright (c) 2009-2010 John Norman + + 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. + + The original version of this class can be located at: + http://irrlicht.suckerfreegames.com/ + + John Norman + john@suckerfreegames.com +*/ + +#include +#include +#include "CGUITTFont.h" + +namespace irr +{ +namespace gui +{ + +// Manages the FT_Face cache. +struct SGUITTFace : public virtual irr::IReferenceCounted +{ + SGUITTFace() : face_buffer(0), face_buffer_size(0) + { + memset((void*)&face, 0, sizeof(FT_Face)); + } + + ~SGUITTFace() + { + FT_Done_Face(face); + delete[] face_buffer; + } + + FT_Face face; + FT_Byte* face_buffer; + FT_Long face_buffer_size; +}; + +// Static variables. +FT_Library CGUITTFont::c_library; +core::map CGUITTFont::c_faces; +bool CGUITTFont::c_libraryLoaded = false; +scene::IMesh* CGUITTFont::shared_plane_ptr_ = 0; +scene::SMesh CGUITTFont::shared_plane_; + +// + +/** Checks that no dimension of the FT_BitMap object is negative. If either is + * negative, abort execution. + */ +inline void checkFontBitmapSize(const FT_Bitmap &bits) +{ + if ((s32)bits.rows < 0 || (s32)bits.width < 0) { + std::cout << "Insane font glyph size. File: " + << __FILE__ << " Line " << __LINE__ + << std::endl; + abort(); + } +} + +video::IImage* SGUITTGlyph::createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const +{ + // Make sure our casts to s32 in the loops below will not cause problems + checkFontBitmapSize(bits); + + // Determine what our texture size should be. + // Add 1 because textures are inclusive-exclusive. + core::dimension2du d(bits.width + 1, bits.rows + 1); + core::dimension2du texture_size; + //core::dimension2du texture_size(bits.width + 1, bits.rows + 1); + + // Create and load our image now. + video::IImage* image = 0; + switch (bits.pixel_mode) + { + case FT_PIXEL_MODE_MONO: + { + // Create a blank image and fill it with transparent pixels. + texture_size = d.getOptimalSize(true, true); + image = driver->createImage(video::ECF_A1R5G5B5, texture_size); + image->fill(video::SColor(0, 255, 255, 255)); + + // Load the monochrome data in. + const u32 image_pitch = image->getPitch() / sizeof(u16); + u16* image_data = (u16*)image->lock(); + u8* glyph_data = bits.buffer; + + for (s32 y = 0; y < (s32)bits.rows; ++y) + { + u16* row = image_data; + for (s32 x = 0; x < (s32)bits.width; ++x) + { + // Monochrome bitmaps store 8 pixels per byte. The left-most pixel is the bit 0x80. + // So, we go through the data each bit at a time. + if ((glyph_data[y * bits.pitch + (x / 8)] & (0x80 >> (x % 8))) != 0) + *row = 0xFFFF; + ++row; + } + image_data += image_pitch; + } + image->unlock(); + break; + } + + case FT_PIXEL_MODE_GRAY: + { + // Create our blank image. + texture_size = d.getOptimalSize(!driver->queryFeature(video::EVDF_TEXTURE_NPOT), !driver->queryFeature(video::EVDF_TEXTURE_NSQUARE), true, 0); + image = driver->createImage(video::ECF_A8R8G8B8, texture_size); + image->fill(video::SColor(0, 255, 255, 255)); + + // Load the grayscale data in. + const float gray_count = static_cast(bits.num_grays); + const u32 image_pitch = image->getPitch() / sizeof(u32); + u32* image_data = (u32*)image->lock(); + u8* glyph_data = bits.buffer; + for (s32 y = 0; y < (s32)bits.rows; ++y) + { + u8* row = glyph_data; + for (s32 x = 0; x < (s32)bits.width; ++x) + { + image_data[y * image_pitch + x] |= static_cast(255.0f * (static_cast(*row++) / gray_count)) << 24; + //data[y * image_pitch + x] |= ((u32)(*bitsdata++) << 24); + } + glyph_data += bits.pitch; + } + image->unlock(); + break; + } + default: + // TODO: error message? + return 0; + } + return image; +} + +void SGUITTGlyph::preload(u32 char_index, FT_Face face, video::IVideoDriver* driver, u32 font_size, const FT_Int32 loadFlags) +{ + if (isLoaded) return; + + // Set the size of the glyph. + FT_Set_Pixel_Sizes(face, 0, font_size); + + // Attempt to load the glyph. + if (FT_Load_Glyph(face, char_index, loadFlags) != FT_Err_Ok) + // TODO: error message? + return; + + FT_GlyphSlot glyph = face->glyph; + FT_Bitmap bits = glyph->bitmap; + + // Setup the glyph information here: + advance = glyph->advance; + offset = core::vector2di(glyph->bitmap_left, glyph->bitmap_top); + + // Try to get the last page with available slots. + CGUITTGlyphPage* page = parent->getLastGlyphPage(); + + // If we need to make a new page, do that now. + if (!page) + { + page = parent->createGlyphPage(bits.pixel_mode); + if (!page) + // TODO: add error message? + return; + } + + glyph_page = parent->getLastGlyphPageIndex(); + u32 texture_side_length = page->texture->getOriginalSize().Width; + core::vector2di page_position( + (page->used_slots % (texture_side_length / font_size)) * font_size, + (page->used_slots / (texture_side_length / font_size)) * font_size + ); + source_rect.UpperLeftCorner = page_position; + source_rect.LowerRightCorner = core::vector2di(page_position.X + bits.width, page_position.Y + bits.rows); + + page->dirty = true; + ++page->used_slots; + --page->available_slots; + + // We grab the glyph bitmap here so the data won't be removed when the next glyph is loaded. + surface = createGlyphImage(bits, driver); + + // Set our glyph as loaded. + isLoaded = true; +} + +void SGUITTGlyph::unload() +{ + if (surface) + { + surface->drop(); + surface = 0; + } + isLoaded = false; +} + +////////////////////// + +CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency, const u32 shadow, const u32 shadow_alpha) +{ + if (!c_libraryLoaded) + { + if (FT_Init_FreeType(&c_library)) + return 0; + c_libraryLoaded = true; + } + + CGUITTFont* font = new CGUITTFont(env); + bool ret = font->load(filename, size, antialias, transparency); + if (!ret) + { + font->drop(); + return 0; + } + + font->shadow_offset = shadow; + font->shadow_alpha = shadow_alpha; + + return font; +} + +CGUITTFont* CGUITTFont::createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency) +{ + if (!c_libraryLoaded) + { + if (FT_Init_FreeType(&c_library)) + return 0; + c_libraryLoaded = true; + } + + CGUITTFont* font = new CGUITTFont(device->getGUIEnvironment()); + font->Device = device; + bool ret = font->load(filename, size, antialias, transparency); + if (!ret) + { + font->drop(); + return 0; + } + + return font; +} + +CGUITTFont* CGUITTFont::create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency) +{ + return CGUITTFont::createTTFont(env, filename, size, antialias, transparency); +} + +CGUITTFont* CGUITTFont::create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency) +{ + return CGUITTFont::createTTFont(device, filename, size, antialias, transparency); +} + +////////////////////// + +//! Constructor. +CGUITTFont::CGUITTFont(IGUIEnvironment *env) +: use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true), +batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0) +{ + #ifdef _DEBUG + setDebugName("CGUITTFont"); + #endif + + if (Environment) + { + // don't grab environment, to avoid circular references + Driver = Environment->getVideoDriver(); + } + + if (Driver) + Driver->grab(); + + setInvisibleCharacters(L" "); + + // Glyphs aren't reference counted, so don't try to delete them when we free the array. + Glyphs.set_free_when_destroyed(false); +} + +bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antialias, const bool transparency) +{ + // Some sanity checks. + if (Environment == 0 || Driver == 0) return false; + if (size == 0) return false; + if (filename.size() == 0) return false; + + io::IFileSystem* filesystem = Environment->getFileSystem(); + irr::ILogger* logger = (Device != 0 ? Device->getLogger() : 0); + this->size = size; + this->filename = filename; + + // Update the font loading flags when the font is first loaded. + this->use_monochrome = !antialias; + this->use_transparency = transparency; + update_load_flags(); + + // Log. + if (logger) + logger->log(L"CGUITTFont", core::stringw(core::stringw(L"Creating new font: ") + core::ustring(filename).toWCHAR_s() + L" " + core::stringc(size) + L"pt " + (antialias ? L"+antialias " : L"-antialias ") + (transparency ? L"+transparency" : L"-transparency")).c_str(), irr::ELL_INFORMATION); + + // Grab the face. + SGUITTFace* face = 0; + core::map::Node* node = c_faces.find(filename); + if (node == 0) + { + face = new SGUITTFace(); + c_faces.set(filename, face); + + if (filesystem) + { + // Read in the file data. + io::IReadFile* file = filesystem->createAndOpenFile(filename); + if (file == 0) + { + if (logger) logger->log(L"CGUITTFont", L"Failed to open the file.", irr::ELL_INFORMATION); + + c_faces.remove(filename); + delete face; + face = 0; + return false; + } + face->face_buffer = new FT_Byte[file->getSize()]; + file->read(face->face_buffer, file->getSize()); + face->face_buffer_size = file->getSize(); + file->drop(); + + // Create the face. + if (FT_New_Memory_Face(c_library, face->face_buffer, face->face_buffer_size, 0, &face->face)) + { + if (logger) logger->log(L"CGUITTFont", L"FT_New_Memory_Face failed.", irr::ELL_INFORMATION); + + c_faces.remove(filename); + delete face; + face = 0; + return false; + } + } + else + { + core::ustring converter(filename); + if (FT_New_Face(c_library, reinterpret_cast(converter.toUTF8_s().c_str()), 0, &face->face)) + { + if (logger) logger->log(L"CGUITTFont", L"FT_New_Face failed.", irr::ELL_INFORMATION); + + c_faces.remove(filename); + delete face; + face = 0; + return false; + } + } + } + else + { + // Using another instance of this face. + face = node->getValue(); + face->grab(); + } + + // Store our face. + tt_face = face->face; + + // Store font metrics. + FT_Set_Pixel_Sizes(tt_face, size, 0); + font_metrics = tt_face->size->metrics; + + // Allocate our glyphs. + Glyphs.clear(); + Glyphs.reallocate(tt_face->num_glyphs); + Glyphs.set_used(tt_face->num_glyphs); + for (FT_Long i = 0; i < tt_face->num_glyphs; ++i) + { + Glyphs[i].isLoaded = false; + Glyphs[i].glyph_page = 0; + Glyphs[i].source_rect = core::recti(); + Glyphs[i].offset = core::vector2di(); + Glyphs[i].advance = FT_Vector(); + Glyphs[i].surface = 0; + Glyphs[i].parent = this; + } + + // Cache the first 127 ascii characters. + u32 old_size = batch_load_size; + batch_load_size = 127; + getGlyphIndexByChar((uchar32_t)0); + batch_load_size = old_size; + + return true; +} + +CGUITTFont::~CGUITTFont() +{ + // Delete the glyphs and glyph pages. + reset_images(); + CGUITTAssistDelete::Delete(Glyphs); + //Glyphs.clear(); + + // We aren't using this face anymore. + core::map::Node* n = c_faces.find(filename); + if (n) + { + SGUITTFace* f = n->getValue(); + + // Drop our face. If this was the last face, the destructor will clean up. + if (f->drop()) + c_faces.remove(filename); + + // If there are no more faces referenced by FreeType, clean up. + if (c_faces.size() == 0) + { + FT_Done_FreeType(c_library); + c_libraryLoaded = false; + } + } + + // Drop our driver now. + if (Driver) + Driver->drop(); +} + +void CGUITTFont::reset_images() +{ + // Delete the glyphs. + for (u32 i = 0; i != Glyphs.size(); ++i) + Glyphs[i].unload(); + + // Unload the glyph pages from video memory. + for (u32 i = 0; i != Glyph_Pages.size(); ++i) + delete Glyph_Pages[i]; + Glyph_Pages.clear(); + + // Always update the internal FreeType loading flags after resetting. + update_load_flags(); +} + +void CGUITTFont::update_glyph_pages() const +{ + for (u32 i = 0; i != Glyph_Pages.size(); ++i) + { + if (Glyph_Pages[i]->dirty) + Glyph_Pages[i]->updateTexture(); + } +} + +CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const +{ + CGUITTGlyphPage* page = 0; + if (Glyph_Pages.empty()) + return 0; + else + { + page = Glyph_Pages[getLastGlyphPageIndex()]; + if (page->available_slots == 0) + page = 0; + } + return page; +} + +CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode) +{ + CGUITTGlyphPage* page = 0; + + // Name of our page. + io::path name("TTFontGlyphPage_"); + name += tt_face->family_name; + name += "."; + name += tt_face->style_name; + name += "."; + name += size; + name += "_"; + name += Glyph_Pages.size(); // The newly created page will be at the end of the collection. + + // Create the new page. + page = new CGUITTGlyphPage(Driver, name); + + // Determine our maximum texture size. + // If we keep getting 0, set it to 1024x1024, as that number is pretty safe. + core::dimension2du max_texture_size = max_page_texture_size; + if (max_texture_size.Width == 0 || max_texture_size.Height == 0) + max_texture_size = Driver->getMaxTextureSize(); + if (max_texture_size.Width == 0 || max_texture_size.Height == 0) + max_texture_size = core::dimension2du(1024, 1024); + + // We want to try to put at least 144 glyphs on a single texture. + core::dimension2du page_texture_size; + if (size <= 21) page_texture_size = core::dimension2du(256, 256); + else if (size <= 42) page_texture_size = core::dimension2du(512, 512); + else if (size <= 84) page_texture_size = core::dimension2du(1024, 1024); + else if (size <= 168) page_texture_size = core::dimension2du(2048, 2048); + else page_texture_size = core::dimension2du(4096, 4096); + + if (page_texture_size.Width > max_texture_size.Width || page_texture_size.Height > max_texture_size.Height) + page_texture_size = max_texture_size; + + if (!page->createPageTexture(pixel_mode, page_texture_size)) + // TODO: add error message? + return 0; + + if (page) + { + // Determine the number of glyph slots on the page and add it to the list of pages. + page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size); + Glyph_Pages.push_back(page); + } + return page; +} + +void CGUITTFont::setTransparency(const bool flag) +{ + use_transparency = flag; + reset_images(); +} + +void CGUITTFont::setMonochrome(const bool flag) +{ + use_monochrome = flag; + reset_images(); +} + +void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hinting) +{ + use_hinting = enable; + use_auto_hinting = enable_auto_hinting; + reset_images(); +} + +void CGUITTFont::draw(const core::stringw& text, const core::rect& position, video::SColor color, bool hcenter, bool vcenter, const core::rect* clip) +{ + if (!Driver) + return; + + // Clear the glyph pages of their render information. + for (u32 i = 0; i < Glyph_Pages.size(); ++i) + { + Glyph_Pages[i]->render_positions.clear(); + Glyph_Pages[i]->render_source_rects.clear(); + } + + // Set up some variables. + core::dimension2d textDimension; + core::position2d offset = position.UpperLeftCorner; + + // Determine offset positions. + if (hcenter || vcenter) + { + textDimension = getDimension(text.c_str()); + + if (hcenter) + offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X; + + if (vcenter) + offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y; + } + + // Convert to a unicode string. + core::ustring utext(text); + + // Set up our render map. + core::map Render_Map; + + // Start parsing characters. + u32 n; + uchar32_t previousChar = 0; + core::ustring::const_iterator iter(utext); + while (!iter.atEnd()) + { + uchar32_t currentChar = *iter; + n = getGlyphIndexByChar(currentChar); + bool visible = (Invisible.findFirst(currentChar) == -1); + bool lineBreak=false; + if (currentChar == L'\r') // Mac or Windows breaks + { + lineBreak = true; + if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks. + currentChar = *(++iter); + } + else if (currentChar == (uchar32_t)'\n') // Unix breaks + { + lineBreak = true; + } + + if (lineBreak) + { + previousChar = 0; + offset.Y += font_metrics.height / 64; + offset.X = position.UpperLeftCorner.X; + + if (hcenter) + offset.X += (position.getWidth() - textDimension.Width) >> 1; + ++iter; + continue; + } + + if (n > 0 && visible) + { + // Calculate the glyph offset. + s32 offx = Glyphs[n-1].offset.X; + s32 offy = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y; + + // Apply kerning. + core::vector2di k = getKerning(currentChar, previousChar); + offset.X += k.X; + offset.Y += k.Y; + + // Determine rendering information. + SGUITTGlyph& glyph = Glyphs[n-1]; + CGUITTGlyphPage* const page = Glyph_Pages[glyph.glyph_page]; + page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy)); + page->render_source_rects.push_back(glyph.source_rect); + Render_Map.set(glyph.glyph_page, page); + } + offset.X += getWidthFromCharacter(currentChar); + + previousChar = currentChar; + ++iter; + } + + // Draw now. + update_glyph_pages(); + core::map::Iterator j = Render_Map.getIterator(); + while (!j.atEnd()) + { + core::map::Node* n = j.getNode(); + j++; + if (n == 0) continue; + + CGUITTGlyphPage* page = n->getValue(); + + if (!use_transparency) color.color |= 0xff000000; + + if (shadow_offset) { + for (size_t i = 0; i < page->render_positions.size(); ++i) + page->render_positions[i] += core::vector2di(shadow_offset, shadow_offset); + Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, video::SColor(shadow_alpha,0,0,0), true); + for (size_t i = 0; i < page->render_positions.size(); ++i) + page->render_positions[i] -= core::vector2di(shadow_offset, shadow_offset); + } + Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, color, true); + } +} + +core::dimension2d CGUITTFont::getCharDimension(const wchar_t ch) const +{ + return core::dimension2d(getWidthFromCharacter(ch), getHeightFromCharacter(ch)); +} + +core::dimension2d CGUITTFont::getDimension(const wchar_t* text) const +{ + return getDimension(core::ustring(text)); +} + +core::dimension2d CGUITTFont::getDimension(const core::ustring& text) const +{ + // Get the maximum font height. Unfortunately, we have to do this hack as + // Irrlicht will draw things wrong. In FreeType, the font size is the + // maximum size for a single glyph, but that glyph may hang "under" the + // draw line, increasing the total font height to beyond the set size. + // Irrlicht does not understand this concept when drawing fonts. Also, I + // add +1 to give it a 1 pixel blank border. This makes things like + // tooltips look nicer. + s32 test1 = getHeightFromCharacter((uchar32_t)'g') + 1; + s32 test2 = getHeightFromCharacter((uchar32_t)'j') + 1; + s32 test3 = getHeightFromCharacter((uchar32_t)'_') + 1; + s32 max_font_height = core::max_(test1, core::max_(test2, test3)); + + core::dimension2d text_dimension(0, max_font_height); + core::dimension2d line(0, max_font_height); + + uchar32_t previousChar = 0; + core::ustring::const_iterator iter = text.begin(); + for (; !iter.atEnd(); ++iter) + { + uchar32_t p = *iter; + bool lineBreak = false; + if (p == '\r') // Mac or Windows line breaks. + { + lineBreak = true; + if (*(iter + 1) == '\n') + { + ++iter; + p = *iter; + } + } + else if (p == '\n') // Unix line breaks. + { + lineBreak = true; + } + + // Kerning. + core::vector2di k = getKerning(p, previousChar); + line.Width += k.X; + previousChar = p; + + // Check for linebreak. + if (lineBreak) + { + previousChar = 0; + text_dimension.Height += line.Height; + if (text_dimension.Width < line.Width) + text_dimension.Width = line.Width; + line.Width = 0; + line.Height = max_font_height; + continue; + } + line.Width += getWidthFromCharacter(p); + } + if (text_dimension.Width < line.Width) + text_dimension.Width = line.Width; + + return text_dimension; +} + +inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const +{ + return getWidthFromCharacter((uchar32_t)c); +} + +inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const +{ + // Set the size of the face. + // This is because we cache faces and the face may have been set to a different size. + //FT_Set_Pixel_Sizes(tt_face, 0, size); + + u32 n = getGlyphIndexByChar(c); + if (n > 0) + { + int w = Glyphs[n-1].advance.x / 64; + return w; + } + if (c >= 0x2000) + return (font_metrics.ascender / 64); + else return (font_metrics.ascender / 64) / 2; +} + +inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const +{ + return getHeightFromCharacter((uchar32_t)c); +} + +inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const +{ + // Set the size of the face. + // This is because we cache faces and the face may have been set to a different size. + //FT_Set_Pixel_Sizes(tt_face, 0, size); + + u32 n = getGlyphIndexByChar(c); + if (n > 0) + { + // Grab the true height of the character, taking into account underhanging glyphs. + s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight(); + return height; + } + if (c >= 0x2000) + return (font_metrics.ascender / 64); + else return (font_metrics.ascender / 64) / 2; +} + +u32 CGUITTFont::getGlyphIndexByChar(wchar_t c) const +{ + return getGlyphIndexByChar((uchar32_t)c); +} + +u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const +{ + // Get the glyph. + u32 glyph = FT_Get_Char_Index(tt_face, c); + + // Check for a valid glyph. If it is invalid, attempt to use the replacement character. + if (glyph == 0) + glyph = FT_Get_Char_Index(tt_face, core::unicode::UTF_REPLACEMENT_CHARACTER); + + // If our glyph is already loaded, don't bother doing any batch loading code. + if (glyph != 0 && Glyphs[glyph - 1].isLoaded) + return glyph; + + // Determine our batch loading positions. + u32 half_size = (batch_load_size / 2); + u32 start_pos = 0; + if (c > half_size) start_pos = c - half_size; + u32 end_pos = start_pos + batch_load_size; + + // Load all our characters. + do + { + // Get the character we are going to load. + u32 char_index = FT_Get_Char_Index(tt_face, start_pos); + + // If the glyph hasn't been loaded yet, do it now. + if (char_index) + { + SGUITTGlyph& glyph = Glyphs[char_index - 1]; + if (!glyph.isLoaded) + { + glyph.preload(char_index, tt_face, Driver, size, load_flags); + Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph); + } + } + } + while (++start_pos < end_pos); + + // Return our original character. + return glyph; +} + +s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const +{ + return getCharacterFromPos(core::ustring(text), pixel_x); +} + +s32 CGUITTFont::getCharacterFromPos(const core::ustring& text, s32 pixel_x) const +{ + s32 x = 0; + //s32 idx = 0; + + u32 character = 0; + uchar32_t previousChar = 0; + core::ustring::const_iterator iter = text.begin(); + while (!iter.atEnd()) + { + uchar32_t c = *iter; + x += getWidthFromCharacter(c); + + // Kerning. + core::vector2di k = getKerning(c, previousChar); + x += k.X; + + if (x >= pixel_x) + return character; + + previousChar = c; + ++iter; + ++character; + } + + return -1; +} + +void CGUITTFont::setKerningWidth(s32 kerning) +{ + GlobalKerningWidth = kerning; +} + +void CGUITTFont::setKerningHeight(s32 kerning) +{ + GlobalKerningHeight = kerning; +} + +s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const +{ + if (tt_face == 0) + return GlobalKerningWidth; + if (thisLetter == 0 || previousLetter == 0) + return 0; + + return getKerningWidth((uchar32_t)*thisLetter, (uchar32_t)*previousLetter); +} + +s32 CGUITTFont::getKerningWidth(const uchar32_t thisLetter, const uchar32_t previousLetter) const +{ + // Return only the kerning width. + return getKerning(thisLetter, previousLetter).X; +} + +s32 CGUITTFont::getKerningHeight() const +{ + // FreeType 2 currently doesn't return any height kerning information. + return GlobalKerningHeight; +} + +core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const +{ + return getKerning((uchar32_t)thisLetter, (uchar32_t)previousLetter); +} + +core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const +{ + if (tt_face == 0 || thisLetter == 0 || previousLetter == 0) + return core::vector2di(); + + // Set the size of the face. + // This is because we cache faces and the face may have been set to a different size. + FT_Set_Pixel_Sizes(tt_face, 0, size); + + core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight); + + // If we don't have kerning, no point in continuing. + if (!FT_HAS_KERNING(tt_face)) + return ret; + + // Get the kerning information. + FT_Vector v; + FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), getGlyphIndexByChar(thisLetter), FT_KERNING_DEFAULT, &v); + + // If we have a scalable font, the return value will be in font points. + if (FT_IS_SCALABLE(tt_face)) + { + // Font points, so divide by 64. + ret.X += (v.x / 64); + ret.Y += (v.y / 64); + } + else + { + // Pixel units. + ret.X += v.x; + ret.Y += v.y; + } + return ret; +} + +void CGUITTFont::setInvisibleCharacters(const wchar_t *s) +{ + core::ustring us(s); + Invisible = us; +} + +void CGUITTFont::setInvisibleCharacters(const core::ustring& s) +{ + Invisible = s; +} + +video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch) +{ + u32 n = getGlyphIndexByChar(ch); + const SGUITTGlyph& glyph = Glyphs[n-1]; + CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page]; + + if (page->dirty) + page->updateTexture(); + + video::ITexture* tex = page->texture; + + // Acquire a read-only lock of the corresponding page texture. + #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8 + void* ptr = tex->lock(video::ETLM_READ_ONLY); + #else + void* ptr = tex->lock(true); + #endif + + video::ECOLOR_FORMAT format = tex->getColorFormat(); + core::dimension2du tex_size = tex->getOriginalSize(); + video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false); + + // Copy the image data out of the page texture. + core::dimension2du glyph_size(glyph.source_rect.getSize()); + video::IImage* image = Driver->createImage(format, glyph_size); + pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect); + + tex->unlock(); + return image; +} + +video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const +{ + if (page_index < Glyph_Pages.size()) + return Glyph_Pages[page_index]->texture; + else + return 0; +} + +void CGUITTFont::createSharedPlane() +{ + /* + 2___3 + | /| + | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1) + |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1) + 0---1 + */ + + using namespace core; + using namespace video; + using namespace scene; + S3DVertex vertices[4]; + u16 indices[6] = {0,2,3,3,1,0}; + vertices[0] = S3DVertex(vector3df(0,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,1)); + vertices[1] = S3DVertex(vector3df(1,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,1)); + vertices[2] = S3DVertex(vector3df(0, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,0)); + vertices[3] = S3DVertex(vector3df(1, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,0)); + + SMeshBuffer* buf = new SMeshBuffer(); + buf->append(vertices, 4, indices, 6); + + shared_plane_.addMeshBuffer( buf ); + + shared_plane_ptr_ = &shared_plane_; + buf->drop(); //the addMeshBuffer method will grab it, so we can drop this ptr. +} + +core::dimension2d CGUITTFont::getDimensionUntilEndOfLine(const wchar_t* p) const +{ + core::stringw s; + for (const wchar_t* temp = p; temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp ) + s.append(*temp); + + return getDimension(s.c_str()); +} + +core::array CGUITTFont::addTextSceneNode(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent, const video::SColor& color, bool center) +{ + using namespace core; + using namespace video; + using namespace scene; + + array container; + + if (!Driver || !smgr) return container; + if (!parent) + parent = smgr->addEmptySceneNode(smgr->getRootSceneNode(), -1); + // if you don't specify parent, then we add a empty node attached to the root node + // this is generally undesirable. + + if (!shared_plane_ptr_) //this points to a static mesh that contains the plane + createSharedPlane(); //if it's not initialized, we create one. + + dimension2d text_size(getDimension(text)); //convert from unsigned to signed. + vector3df start_point(0, 0, 0), offset; + + /** NOTICE: + Because we are considering adding texts into 3D world, all Y axis vectors are inverted. + **/ + + // There's currently no "vertical center" concept when you apply text scene node to the 3D world. + if (center) + { + offset.X = start_point.X = -text_size.Width / 2.f; + offset.Y = start_point.Y = +text_size.Height/ 2.f; + offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> 1; + } + + // the default font material + SMaterial mat; + mat.setFlag(video::EMF_LIGHTING, true); + mat.setFlag(video::EMF_ZWRITE_ENABLE, false); + mat.setFlag(video::EMF_NORMALIZE_NORMALS, true); + mat.ColorMaterial = video::ECM_NONE; + mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID; + mat.MaterialTypeParam = 0.01f; + mat.DiffuseColor = color; + + wchar_t current_char = 0, previous_char = 0; + u32 n = 0; + + array glyph_indices; + + while (*text) + { + current_char = *text; + bool line_break=false; + if (current_char == L'\r') // Mac or Windows breaks + { + line_break = true; + if (*(text + 1) == L'\n') // Windows line breaks. + current_char = *(++text); + } + else if (current_char == L'\n') // Unix breaks + { + line_break = true; + } + + if (line_break) + { + previous_char = 0; + offset.Y -= tt_face->size->metrics.ascender / 64; + offset.X = start_point.X; + if (center) + offset.X += (text_size.Width - getDimensionUntilEndOfLine(text+1).Width) >> 1; + ++text; + } + else + { + n = getGlyphIndexByChar(current_char); + if (n > 0) + { + glyph_indices.push_back( n ); + + // Store glyph size and offset informations. + SGUITTGlyph const& glyph = Glyphs[n-1]; + u32 texw = glyph.source_rect.getWidth(); + u32 texh = glyph.source_rect.getHeight(); + s32 offx = glyph.offset.X; + s32 offy = (font_metrics.ascender / 64) - glyph.offset.Y; + + // Apply kerning. + vector2di k = getKerning(current_char, previous_char); + offset.X += k.X; + offset.Y += k.Y; + + vector3df current_pos(offset.X + offx, offset.Y - offy, 0); + dimension2d letter_size = dimension2d(texw, texh); + + // Now we copy planes corresponding to the letter size. + IMeshManipulator* mani = smgr->getMeshManipulator(); + IMesh* meshcopy = mani->createMeshCopy(shared_plane_ptr_); + #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8 + mani->scale(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1)); + #else + mani->scaleMesh(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1)); + #endif + + ISceneNode* current_node = smgr->addMeshSceneNode(meshcopy, parent, -1, current_pos); + meshcopy->drop(); + + current_node->getMaterial(0) = mat; + current_node->setAutomaticCulling(EAC_OFF); + current_node->setIsDebugObject(true); //so the picking won't have any effect on individual letter + //current_node->setDebugDataVisible(EDS_BBOX); //de-comment this when debugging + + container.push_back(current_node); + } + offset.X += getWidthFromCharacter(current_char); + previous_char = current_char; + ++text; + } + } + + update_glyph_pages(); + //only after we update the textures can we use the glyph page textures. + + for (u32 i = 0; i < glyph_indices.size(); ++i) + { + u32 n = glyph_indices[i]; + SGUITTGlyph const& glyph = Glyphs[n-1]; + ITexture* current_tex = Glyph_Pages[glyph.glyph_page]->texture; + f32 page_texture_size = (f32)current_tex->getSize().Width; + //Now we calculate the UV position according to the texture size and the source rect. + // + // 2___3 + // | /| + // | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1) + // |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1) + // 0---1 + // + f32 u1 = glyph.source_rect.UpperLeftCorner.X / page_texture_size; + f32 u2 = u1 + (glyph.source_rect.getWidth() / page_texture_size); + f32 v1 = glyph.source_rect.UpperLeftCorner.Y / page_texture_size; + f32 v2 = v1 + (glyph.source_rect.getHeight() / page_texture_size); + + //we can be quite sure that this is IMeshSceneNode, because we just added them in the above loop. + IMeshSceneNode* node = static_cast(container[i]); + + S3DVertex* pv = static_cast(node->getMesh()->getMeshBuffer(0)->getVertices()); + //pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / static_cast(letter_size.Height); + //pv[1].TCoords.X = pv[3].TCoords.X = (letter_size.Width - 1) / static_cast(letter_size.Width); + pv[0].TCoords = vector2df(u1, v2); + pv[1].TCoords = vector2df(u2, v2); + pv[2].TCoords = vector2df(u1, v1); + pv[3].TCoords = vector2df(u2, v1); + + container[i]->getMaterial(0).setTexture(0, current_tex); + } + + return container; +} + +} // end namespace gui +} // end namespace irr diff --git a/src/cguittfont/CGUITTFont.h b/src/cguittfont/CGUITTFont.h new file mode 100644 index 0000000..e24d8f1 --- /dev/null +++ b/src/cguittfont/CGUITTFont.h @@ -0,0 +1,379 @@ +/* + CGUITTFont FreeType class for Irrlicht + Copyright (c) 2009-2010 John Norman + + 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. + + The original version of this class can be located at: + http://irrlicht.suckerfreegames.com/ + + John Norman + john@suckerfreegames.com +*/ + +#ifndef __C_GUI_TTFONT_H_INCLUDED__ +#define __C_GUI_TTFONT_H_INCLUDED__ + +#include +#include +#include FT_FREETYPE_H + +namespace irr +{ +namespace gui +{ + struct SGUITTFace; + class CGUITTFont; + + //! Class to assist in deleting glyphs. + class CGUITTAssistDelete + { + public: + template + static void Delete(core::array& a) + { + TAlloc allocator; + allocator.deallocate(a.pointer()); + } + }; + + //! Structure representing a single TrueType glyph. + struct SGUITTGlyph + { + //! Constructor. + SGUITTGlyph() : isLoaded(false), glyph_page(0), surface(0), parent(0) {} + + //! Destructor. + ~SGUITTGlyph() { unload(); } + + //! Preload the glyph. + //! The preload process occurs when the program tries to cache the glyph from FT_Library. + //! However, it simply defines the SGUITTGlyph's properties and will only create the page + //! textures if necessary. The actual creation of the textures should only occur right + //! before the batch draw call. + void preload(u32 char_index, FT_Face face, video::IVideoDriver* driver, u32 font_size, const FT_Int32 loadFlags); + + //! Unloads the glyph. + void unload(); + + //! Creates the IImage object from the FT_Bitmap. + video::IImage* createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const; + + //! If true, the glyph has been loaded. + bool isLoaded; + + //! The page the glyph is on. + u32 glyph_page; + + //! The source rectangle for the glyph. + core::recti source_rect; + + //! The offset of glyph when drawn. + core::vector2di offset; + + //! Glyph advance information. + FT_Vector advance; + + //! This is just the temporary image holder. After this glyph is paged, + //! it will be dropped. + mutable video::IImage* surface; + + //! The pointer pointing to the parent (CGUITTFont) + CGUITTFont* parent; + }; + + //! Holds a sheet of glyphs. + class CGUITTGlyphPage + { + public: + CGUITTGlyphPage(video::IVideoDriver* Driver, const io::path& texture_name) :texture(0), available_slots(0), used_slots(0), dirty(false), driver(Driver), name(texture_name) {} + ~CGUITTGlyphPage() + { + if (texture) + { + if (driver) + driver->removeTexture(texture); + else texture->drop(); + } + } + + //! Create the actual page texture, + bool createPageTexture(const u8& pixel_mode, const core::dimension2du& texture_size) + { + if( texture ) + return false; + + bool flgmip = driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS); + driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false); + + // Set the texture color format. + switch (pixel_mode) + { + case FT_PIXEL_MODE_MONO: + texture = driver->addTexture(texture_size, name, video::ECF_A1R5G5B5); + break; + case FT_PIXEL_MODE_GRAY: + default: + texture = driver->addTexture(texture_size, name, video::ECF_A8R8G8B8); + break; + } + + // Restore our texture creation flags. + driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, flgmip); + return texture ? true : false; + } + + //! Add the glyph to a list of glyphs to be paged. + //! This collection will be cleared after updateTexture is called. + void pushGlyphToBePaged(const SGUITTGlyph* glyph) + { + glyph_to_be_paged.push_back(glyph); + } + + //! Updates the texture atlas with new glyphs. + void updateTexture() + { + if (!dirty) return; + + void* ptr = texture->lock(); + video::ECOLOR_FORMAT format = texture->getColorFormat(); + core::dimension2du size = texture->getOriginalSize(); + video::IImage* pageholder = driver->createImageFromData(format, size, ptr, true, false); + + for (u32 i = 0; i < glyph_to_be_paged.size(); ++i) + { + const SGUITTGlyph* glyph = glyph_to_be_paged[i]; + if (glyph && glyph->isLoaded) + { + if (glyph->surface) + { + glyph->surface->copyTo(pageholder, glyph->source_rect.UpperLeftCorner); + glyph->surface->drop(); + glyph->surface = 0; + } + else + { + ; // TODO: add error message? + //currently, if we failed to create the image, just ignore this operation. + } + } + } + + pageholder->drop(); + texture->unlock(); + glyph_to_be_paged.clear(); + dirty = false; + } + + video::ITexture* texture; + u32 available_slots; + u32 used_slots; + bool dirty; + + core::array render_positions; + core::array render_source_rects; + + private: + core::array glyph_to_be_paged; + video::IVideoDriver* driver; + io::path name; + }; + + //! Class representing a TrueType font. + class CGUITTFont : public IGUIFont + { + public: + //! Creates a new TrueType font and returns a pointer to it. The pointer must be drop()'ed when finished. + //! \param env The IGUIEnvironment the font loads out of. + //! \param filename The filename of the font. + //! \param size The size of the font glyphs in pixels. Since this is the size of the individual glyphs, the true height of the font may change depending on the characters used. + //! \param antialias set the use_monochrome (opposite to antialias) flag + //! \param transparency set the use_transparency flag + //! \return Returns a pointer to a CGUITTFont. Will return 0 if the font failed to load. + static CGUITTFont* createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true, const u32 shadow = 0, const u32 shadow_alpha = 255); + static CGUITTFont* createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); + static CGUITTFont* create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); + static CGUITTFont* create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); + + //! Destructor + virtual ~CGUITTFont(); + + //! Sets the amount of glyphs to batch load. + virtual void setBatchLoadSize(u32 batch_size) { batch_load_size = batch_size; } + + //! Sets the maximum texture size for a page of glyphs. + virtual void setMaxPageTextureSize(const core::dimension2du& texture_size) { max_page_texture_size = texture_size; } + + //! Get the font size. + virtual u32 getFontSize() const { return size; } + + //! Check the font's transparency. + virtual bool isTransparent() const { return use_transparency; } + + //! Check if the font auto-hinting is enabled. + //! Auto-hinting is FreeType's built-in font hinting engine. + virtual bool useAutoHinting() const { return use_auto_hinting; } + + //! Check if the font hinting is enabled. + virtual bool useHinting() const { return use_hinting; } + + //! Check if the font is being loaded as a monochrome font. + //! The font can either be a 256 color grayscale font, or a 2 color monochrome font. + virtual bool useMonochrome() const { return use_monochrome; } + + //! Tells the font to allow transparency when rendering. + //! Default: true. + //! \param flag If true, the font draws using transparency. + virtual void setTransparency(const bool flag); + + //! Tells the font to use monochrome rendering. + //! Default: false. + //! \param flag If true, the font draws using a monochrome image. If false, the font uses a grayscale image. + virtual void setMonochrome(const bool flag); + + //! Enables or disables font hinting. + //! Default: Hinting and auto-hinting true. + //! \param enable If false, font hinting is turned off. If true, font hinting is turned on. + //! \param enable_auto_hinting If true, FreeType uses its own auto-hinting algorithm. If false, it tries to use the algorithm specified by the font. + virtual void setFontHinting(const bool enable, const bool enable_auto_hinting = true); + + //! Draws some text and clips it to the specified rectangle if wanted. + virtual void draw(const core::stringw& text, const core::rect& position, + video::SColor color, bool hcenter=false, bool vcenter=false, + const core::rect* clip=0); + + //! Returns the dimension of a character produced by this font. + virtual core::dimension2d getCharDimension(const wchar_t ch) const; + + //! Returns the dimension of a text string. + virtual core::dimension2d getDimension(const wchar_t* text) const; + virtual core::dimension2d getDimension(const core::ustring& text) const; + + //! Calculates the index of the character in the text which is on a specific position. + virtual s32 getCharacterFromPos(const wchar_t* text, s32 pixel_x) const; + virtual s32 getCharacterFromPos(const core::ustring& text, s32 pixel_x) const; + + //! Sets global kerning width for the font. + virtual void setKerningWidth(s32 kerning); + + //! Sets global kerning height for the font. + virtual void setKerningHeight(s32 kerning); + + //! Gets kerning values (distance between letters) for the font. If no parameters are provided, + virtual s32 getKerningWidth(const wchar_t* thisLetter=0, const wchar_t* previousLetter=0) const; + virtual s32 getKerningWidth(const uchar32_t thisLetter=0, const uchar32_t previousLetter=0) const; + + //! Returns the distance between letters + virtual s32 getKerningHeight() const; + + //! Define which characters should not be drawn by the font. + virtual void setInvisibleCharacters(const wchar_t *s); + virtual void setInvisibleCharacters(const core::ustring& s); + + //! Get the last glyph page if there's still available slots. + //! If not, it will return zero. + CGUITTGlyphPage* getLastGlyphPage() const; + + //! Create a new glyph page texture. + //! \param pixel_mode the pixel mode defined by FT_Pixel_Mode + //should be better typed. fix later. + CGUITTGlyphPage* createGlyphPage(const u8& pixel_mode); + + //! Get the last glyph page's index. + u32 getLastGlyphPageIndex() const { return Glyph_Pages.size() - 1; } + + //! Create corresponding character's software image copy from the font, + //! so you can use this data just like any ordinary video::IImage. + //! \param ch The character you need + virtual video::IImage* createTextureFromChar(const uchar32_t& ch); + + //! This function is for debugging mostly. If the page doesn't exist it returns zero. + //! \param page_index Simply return the texture handle of a given page index. + virtual video::ITexture* getPageTextureByIndex(const u32& page_index) const; + + //! Add a list of scene nodes generated by putting font textures on the 3D planes. + virtual core::array addTextSceneNode + (const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent = 0, + const video::SColor& color = video::SColor(255, 0, 0, 0), bool center = false ); + + protected: + bool use_monochrome; + bool use_transparency; + bool use_hinting; + bool use_auto_hinting; + u32 size; + u32 batch_load_size; + core::dimension2du max_page_texture_size; + + private: + // Manages the FreeType library. + static FT_Library c_library; + static core::map c_faces; + static bool c_libraryLoaded; + static scene::IMesh* shared_plane_ptr_; + static scene::SMesh shared_plane_; + + CGUITTFont(IGUIEnvironment *env); + bool load(const io::path& filename, const u32 size, const bool antialias, const bool transparency); + void reset_images(); + void update_glyph_pages() const; + void update_load_flags() + { + // Set up our loading flags. + load_flags = FT_LOAD_DEFAULT | FT_LOAD_RENDER; + if (!useHinting()) load_flags |= FT_LOAD_NO_HINTING; + if (!useAutoHinting()) load_flags |= FT_LOAD_NO_AUTOHINT; + if (useMonochrome()) load_flags |= FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO | FT_RENDER_MODE_MONO; + else load_flags |= FT_LOAD_TARGET_NORMAL; + } + u32 getWidthFromCharacter(wchar_t c) const; + u32 getWidthFromCharacter(uchar32_t c) const; + u32 getHeightFromCharacter(wchar_t c) const; + u32 getHeightFromCharacter(uchar32_t c) const; + u32 getGlyphIndexByChar(wchar_t c) const; + u32 getGlyphIndexByChar(uchar32_t c) const; + core::vector2di getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const; + core::vector2di getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const; + core::dimension2d getDimensionUntilEndOfLine(const wchar_t* p) const; + + void createSharedPlane(); + + irr::IrrlichtDevice* Device; + gui::IGUIEnvironment* Environment; + video::IVideoDriver* Driver; + io::path filename; + FT_Face tt_face; + FT_Size_Metrics font_metrics; + FT_Int32 load_flags; + + mutable core::array Glyph_Pages; + mutable core::array Glyphs; + + s32 GlobalKerningWidth; + s32 GlobalKerningHeight; + core::ustring Invisible; + u32 shadow_offset; + u32 shadow_alpha; + }; + +} // end namespace gui +} // end namespace irr + +#endif // __C_GUI_TTFONT_H_INCLUDED__ diff --git a/src/cguittfont/CMakeLists.txt b/src/cguittfont/CMakeLists.txt new file mode 100644 index 0000000..21448ec --- /dev/null +++ b/src/cguittfont/CMakeLists.txt @@ -0,0 +1,29 @@ +# CGUITTFont authors, y u no include headers you use? +# Do not add CGUITTFont.cpp to the line below. +# xCGUITTFont.cpp is a wrapper file that includes +# additional required headers. +add_library(cguittfont xCGUITTFont.cpp) + +if(FREETYPE_PKGCONFIG_FOUND) + set_target_properties(cguittfont + PROPERTIES + COMPILE_FLAGS "${FREETYPE_CFLAGS_STR}" + LINK_FLAGS "${FREETYPE_LDFLAGS_STR}" + ) + + include_directories( + ${IRRLICHT_INCLUDE_DIR} + ) +else(FREETYPE_PKGCONFIG_FOUND) + include_directories( + ${IRRLICHT_INCLUDE_DIR} + ${FREETYPE_INCLUDE_DIRS} + ) +endif(FREETYPE_PKGCONFIG_FOUND) + +target_link_libraries( + cguittfont + ${IRRLICHT_LIBRARY} + ${FREETYPE_LIBRARY} + ${ZLIB_LIBRARIES} # needed by freetype, repeated here for safety + ) diff --git a/src/cguittfont/irrUString.h b/src/cguittfont/irrUString.h new file mode 100644 index 0000000..eb7abe5 --- /dev/null +++ b/src/cguittfont/irrUString.h @@ -0,0 +1,3892 @@ +/* + Basic Unicode string class for Irrlicht. + Copyright (c) 2009-2011 John Norman + + 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. + + The original version of this class can be located at: + http://irrlicht.suckerfreegames.com/ + + John Norman + john@suckerfreegames.com +*/ + +#ifndef __IRR_USTRING_H_INCLUDED__ +#define __IRR_USTRING_H_INCLUDED__ + +#if (__cplusplus > 199711L) || (_MSC_VER >= 1600) || defined(__GXX_EXPERIMENTAL_CXX0X__) +# define USTRING_CPP0X +# if defined(__GXX_EXPERIMENTAL_CXX0X__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 5))) +# define USTRING_CPP0X_NEWLITERALS +# endif +#endif + +#include +#include +#include +#ifdef _WIN32 +#define __BYTE_ORDER 0 +#define __LITTLE_ENDIAN 0 +#define __BIG_ENDIAN 1 +#elif defined(__MACH__) && defined(__APPLE__) +#include +#elif defined(__FreeBSD__) +#include +#else +#include +#endif + +#ifdef USTRING_CPP0X +# include +#endif + +#ifndef USTRING_NO_STL +# include +# include +# include +#endif + +#include "irrTypes.h" +#include "irrAllocator.h" +#include "irrArray.h" +#include "irrMath.h" +#include "irrString.h" +#include "path.h" + +//! UTF-16 surrogate start values. +static const irr::u16 UTF16_HI_SURROGATE = 0xD800; +static const irr::u16 UTF16_LO_SURROGATE = 0xDC00; + +//! Is a UTF-16 code point a surrogate? +#define UTF16_IS_SURROGATE(c) (((c) & 0xF800) == 0xD800) +#define UTF16_IS_SURROGATE_HI(c) (((c) & 0xFC00) == 0xD800) +#define UTF16_IS_SURROGATE_LO(c) (((c) & 0xFC00) == 0xDC00) + + +namespace irr +{ + + // Define our character types. +#ifdef USTRING_CPP0X_NEWLITERALS // C++0x + typedef char32_t uchar32_t; + typedef char16_t uchar16_t; + typedef char uchar8_t; +#else + typedef u32 uchar32_t; + typedef u16 uchar16_t; + typedef u8 uchar8_t; +#endif + +namespace core +{ + +namespace unicode +{ + +//! The unicode replacement character. Used to replace invalid characters. +const irr::u16 UTF_REPLACEMENT_CHARACTER = 0xFFFD; + +//! Convert a UTF-16 surrogate pair into a UTF-32 character. +//! \param high The high value of the pair. +//! \param low The low value of the pair. +//! \return The UTF-32 character expressed by the surrogate pair. +inline uchar32_t toUTF32(uchar16_t high, uchar16_t low) +{ + // Convert the surrogate pair into a single UTF-32 character. + uchar32_t x = ((high & ((1 << 6) -1)) << 10) | (low & ((1 << 10) -1)); + uchar32_t wu = ((high >> 6) & ((1 << 5) - 1)) + 1; + return (wu << 16) | x; +} + +//! Swaps the endianness of a 16-bit value. +//! \return The new value. +inline uchar16_t swapEndian16(const uchar16_t& c) +{ + return ((c >> 8) & 0x00FF) | ((c << 8) & 0xFF00); +} + +//! Swaps the endianness of a 32-bit value. +//! \return The new value. +inline uchar32_t swapEndian32(const uchar32_t& c) +{ + return ((c >> 24) & 0x000000FF) | + ((c >> 8) & 0x0000FF00) | + ((c << 8) & 0x00FF0000) | + ((c << 24) & 0xFF000000); +} + +//! The Unicode byte order mark. +const u16 BOM = 0xFEFF; + +//! The size of the Unicode byte order mark in terms of the Unicode character size. +const u8 BOM_UTF8_LEN = 3; +const u8 BOM_UTF16_LEN = 1; +const u8 BOM_UTF32_LEN = 1; + +//! Unicode byte order marks for file operations. +const u8 BOM_ENCODE_UTF8[3] = { 0xEF, 0xBB, 0xBF }; +const u8 BOM_ENCODE_UTF16_BE[2] = { 0xFE, 0xFF }; +const u8 BOM_ENCODE_UTF16_LE[2] = { 0xFF, 0xFE }; +const u8 BOM_ENCODE_UTF32_BE[4] = { 0x00, 0x00, 0xFE, 0xFF }; +const u8 BOM_ENCODE_UTF32_LE[4] = { 0xFF, 0xFE, 0x00, 0x00 }; + +//! The size in bytes of the Unicode byte marks for file operations. +const u8 BOM_ENCODE_UTF8_LEN = 3; +const u8 BOM_ENCODE_UTF16_LEN = 2; +const u8 BOM_ENCODE_UTF32_LEN = 4; + +//! Unicode encoding type. +enum EUTF_ENCODE +{ + EUTFE_NONE = 0, + EUTFE_UTF8, + EUTFE_UTF16, + EUTFE_UTF16_LE, + EUTFE_UTF16_BE, + EUTFE_UTF32, + EUTFE_UTF32_LE, + EUTFE_UTF32_BE +}; + +//! Unicode endianness. +enum EUTF_ENDIAN +{ + EUTFEE_NATIVE = 0, + EUTFEE_LITTLE, + EUTFEE_BIG +}; + +//! Returns the specified unicode byte order mark in a byte array. +//! The byte order mark is the first few bytes in a text file that signifies its encoding. +/** \param mode The Unicode encoding method that we want to get the byte order mark for. + If EUTFE_UTF16 or EUTFE_UTF32 is passed, it uses the native system endianness. **/ +//! \return An array that contains a byte order mark. +inline core::array getUnicodeBOM(EUTF_ENCODE mode) +{ +#define COPY_ARRAY(source, size) \ + memcpy(ret.pointer(), source, size); \ + ret.set_used(size) + + core::array ret(4); + switch (mode) + { + case EUTFE_UTF8: + COPY_ARRAY(BOM_ENCODE_UTF8, BOM_ENCODE_UTF8_LEN); + break; + case EUTFE_UTF16: + #ifdef __BIG_ENDIAN__ + COPY_ARRAY(BOM_ENCODE_UTF16_BE, BOM_ENCODE_UTF16_LEN); + #else + COPY_ARRAY(BOM_ENCODE_UTF16_LE, BOM_ENCODE_UTF16_LEN); + #endif + break; + case EUTFE_UTF16_BE: + COPY_ARRAY(BOM_ENCODE_UTF16_BE, BOM_ENCODE_UTF16_LEN); + break; + case EUTFE_UTF16_LE: + COPY_ARRAY(BOM_ENCODE_UTF16_LE, BOM_ENCODE_UTF16_LEN); + break; + case EUTFE_UTF32: + #ifdef __BIG_ENDIAN__ + COPY_ARRAY(BOM_ENCODE_UTF32_BE, BOM_ENCODE_UTF32_LEN); + #else + COPY_ARRAY(BOM_ENCODE_UTF32_LE, BOM_ENCODE_UTF32_LEN); + #endif + break; + case EUTFE_UTF32_BE: + COPY_ARRAY(BOM_ENCODE_UTF32_BE, BOM_ENCODE_UTF32_LEN); + break; + case EUTFE_UTF32_LE: + COPY_ARRAY(BOM_ENCODE_UTF32_LE, BOM_ENCODE_UTF32_LEN); + break; + case EUTFE_NONE: + // TODO sapier: fixed warning only, + // don't know if something needs to be done here + break; + } + return ret; + +#undef COPY_ARRAY +} + +//! Detects if the given data stream starts with a unicode BOM. +//! \param data The data stream to check. +//! \return The unicode BOM associated with the data stream, or EUTFE_NONE if none was found. +inline EUTF_ENCODE determineUnicodeBOM(const char* data) +{ + if (memcmp(data, BOM_ENCODE_UTF8, 3) == 0) return EUTFE_UTF8; + if (memcmp(data, BOM_ENCODE_UTF16_BE, 2) == 0) return EUTFE_UTF16_BE; + if (memcmp(data, BOM_ENCODE_UTF16_LE, 2) == 0) return EUTFE_UTF16_LE; + if (memcmp(data, BOM_ENCODE_UTF32_BE, 4) == 0) return EUTFE_UTF32_BE; + if (memcmp(data, BOM_ENCODE_UTF32_LE, 4) == 0) return EUTFE_UTF32_LE; + return EUTFE_NONE; +} + +} // end namespace unicode + + +//! UTF-16 string class. +template > +class ustring16 +{ +public: + + ///------------------/// + /// iterator classes /// + ///------------------/// + + //! Access an element in a unicode string, allowing one to change it. + class _ustring16_iterator_access + { + public: + _ustring16_iterator_access(const ustring16* s, u32 p) : ref(s), pos(p) {} + + //! Allow the class to be interpreted as a single UTF-32 character. + operator uchar32_t() const + { + return _get(); + } + + //! Allow one to change the character in the unicode string. + //! \param c The new character to use. + //! \return Myself. + _ustring16_iterator_access& operator=(const uchar32_t c) + { + _set(c); + return *this; + } + + //! Increments the value by 1. + //! \return Myself. + _ustring16_iterator_access& operator++() + { + _set(_get() + 1); + return *this; + } + + //! Increments the value by 1, returning the old value. + //! \return A unicode character. + uchar32_t operator++(int) + { + uchar32_t old = _get(); + _set(old + 1); + return old; + } + + //! Decrements the value by 1. + //! \return Myself. + _ustring16_iterator_access& operator--() + { + _set(_get() - 1); + return *this; + } + + //! Decrements the value by 1, returning the old value. + //! \return A unicode character. + uchar32_t operator--(int) + { + uchar32_t old = _get(); + _set(old - 1); + return old; + } + + //! Adds to the value by a specified amount. + //! \param val The amount to add to this character. + //! \return Myself. + _ustring16_iterator_access& operator+=(int val) + { + _set(_get() + val); + return *this; + } + + //! Subtracts from the value by a specified amount. + //! \param val The amount to subtract from this character. + //! \return Myself. + _ustring16_iterator_access& operator-=(int val) + { + _set(_get() - val); + return *this; + } + + //! Multiples the value by a specified amount. + //! \param val The amount to multiply this character by. + //! \return Myself. + _ustring16_iterator_access& operator*=(int val) + { + _set(_get() * val); + return *this; + } + + //! Divides the value by a specified amount. + //! \param val The amount to divide this character by. + //! \return Myself. + _ustring16_iterator_access& operator/=(int val) + { + _set(_get() / val); + return *this; + } + + //! Modulos the value by a specified amount. + //! \param val The amount to modulo this character by. + //! \return Myself. + _ustring16_iterator_access& operator%=(int val) + { + _set(_get() % val); + return *this; + } + + //! Adds to the value by a specified amount. + //! \param val The amount to add to this character. + //! \return A unicode character. + uchar32_t operator+(int val) const + { + return _get() + val; + } + + //! Subtracts from the value by a specified amount. + //! \param val The amount to subtract from this character. + //! \return A unicode character. + uchar32_t operator-(int val) const + { + return _get() - val; + } + + //! Multiplies the value by a specified amount. + //! \param val The amount to multiply this character by. + //! \return A unicode character. + uchar32_t operator*(int val) const + { + return _get() * val; + } + + //! Divides the value by a specified amount. + //! \param val The amount to divide this character by. + //! \return A unicode character. + uchar32_t operator/(int val) const + { + return _get() / val; + } + + //! Modulos the value by a specified amount. + //! \param val The amount to modulo this character by. + //! \return A unicode character. + uchar32_t operator%(int val) const + { + return _get() % val; + } + + private: + //! Gets a uchar32_t from our current position. + uchar32_t _get() const + { + const uchar16_t* a = ref->c_str(); + if (!UTF16_IS_SURROGATE(a[pos])) + return static_cast(a[pos]); + else + { + if (pos + 1 >= ref->size_raw()) + return 0; + + return unicode::toUTF32(a[pos], a[pos + 1]); + } + } + + //! Sets a uchar32_t at our current position. + void _set(uchar32_t c) + { + ustring16* ref2 = const_cast*>(ref); + const uchar16_t* a = ref2->c_str(); + if (c > 0xFFFF) + { + // c will be multibyte, so split it up into the high and low surrogate pairs. + uchar16_t x = static_cast(c); + uchar16_t vh = UTF16_HI_SURROGATE | ((((c >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); + + // If the previous position was a surrogate pair, just replace them. Else, insert the low pair. + if (UTF16_IS_SURROGATE_HI(a[pos]) && pos + 1 != ref2->size_raw()) + ref2->replace_raw(vl, static_cast(pos) + 1); + else ref2->insert_raw(vl, static_cast(pos) + 1); + + ref2->replace_raw(vh, static_cast(pos)); + } + else + { + // c will be a single byte. + uchar16_t vh = static_cast(c); + + // If the previous position was a surrogate pair, remove the extra byte. + if (UTF16_IS_SURROGATE_HI(a[pos])) + ref2->erase_raw(static_cast(pos) + 1); + + ref2->replace_raw(vh, static_cast(pos)); + } + } + + const ustring16* ref; + u32 pos; + }; + typedef typename ustring16::_ustring16_iterator_access access; + + + //! Iterator to iterate through a UTF-16 string. +#ifndef USTRING_NO_STL + class _ustring16_const_iterator : public std::iterator< + std::bidirectional_iterator_tag, // iterator_category + access, // value_type + ptrdiff_t, // difference_type + const access, // pointer + const access // reference + > +#else + class _ustring16_const_iterator +#endif + { + public: + typedef _ustring16_const_iterator _Iter; + typedef std::iterator _Base; + typedef const access const_pointer; + typedef const access const_reference; + +#ifndef USTRING_NO_STL + typedef typename _Base::value_type value_type; + typedef typename _Base::difference_type difference_type; + typedef typename _Base::difference_type distance_type; + typedef typename _Base::pointer pointer; + typedef const_reference reference; +#else + typedef access value_type; + typedef u32 difference_type; + typedef u32 distance_type; + typedef const_pointer pointer; + typedef const_reference reference; +#endif + + //! Constructors. + _ustring16_const_iterator(const _Iter& i) : ref(i.ref), pos(i.pos) {} + _ustring16_const_iterator(const ustring16& s) : ref(&s), pos(0) {} + _ustring16_const_iterator(const ustring16& s, const u32 p) : ref(&s), pos(0) + { + if (ref->size_raw() == 0 || p == 0) + return; + + // Go to the appropriate position. + u32 i = p; + u32 sr = ref->size_raw(); + const uchar16_t* a = ref->c_str(); + while (i != 0 && pos < sr) + { + if (UTF16_IS_SURROGATE_HI(a[pos])) + pos += 2; + else ++pos; + --i; + } + } + + //! Test for equalness. + bool operator==(const _Iter& iter) const + { + if (ref == iter.ref && pos == iter.pos) + return true; + return false; + } + + //! Test for unequalness. + bool operator!=(const _Iter& iter) const + { + if (ref != iter.ref || pos != iter.pos) + return true; + return false; + } + + //! Switch to the next full character in the string. + _Iter& operator++() + { // ++iterator + if (pos == ref->size_raw()) return *this; + const uchar16_t* a = ref->c_str(); + if (UTF16_IS_SURROGATE_HI(a[pos])) + pos += 2; // TODO: check for valid low surrogate? + else ++pos; + if (pos > ref->size_raw()) pos = ref->size_raw(); + return *this; + } + + //! Switch to the next full character in the string, returning the previous position. + _Iter operator++(int) + { // iterator++ + _Iter _tmp(*this); + ++*this; + return _tmp; + } + + //! Switch to the previous full character in the string. + _Iter& operator--() + { // --iterator + if (pos == 0) return *this; + const uchar16_t* a = ref->c_str(); + --pos; + if (UTF16_IS_SURROGATE_LO(a[pos]) && pos != 0) // low surrogate, go back one more. + --pos; + return *this; + } + + //! Switch to the previous full character in the string, returning the previous position. + _Iter operator--(int) + { // iterator-- + _Iter _tmp(*this); + --*this; + return _tmp; + } + + //! Advance a specified number of full characters in the string. + //! \return Myself. + _Iter& operator+=(const difference_type v) + { + if (v == 0) return *this; + if (v < 0) return operator-=(v * -1); + + if (pos >= ref->size_raw()) + return *this; + + // Go to the appropriate position. + // TODO: Don't force u32 on an x64 OS. Make it agnostic. + u32 i = (u32)v; + u32 sr = ref->size_raw(); + const uchar16_t* a = ref->c_str(); + while (i != 0 && pos < sr) + { + if (UTF16_IS_SURROGATE_HI(a[pos])) + pos += 2; + else ++pos; + --i; + } + if (pos > sr) + pos = sr; + + return *this; + } + + //! Go back a specified number of full characters in the string. + //! \return Myself. + _Iter& operator-=(const difference_type v) + { + if (v == 0) return *this; + if (v > 0) return operator+=(v * -1); + + if (pos == 0) + return *this; + + // Go to the appropriate position. + // TODO: Don't force u32 on an x64 OS. Make it agnostic. + u32 i = (u32)v; + const uchar16_t* a = ref->c_str(); + while (i != 0 && pos != 0) + { + --pos; + if (UTF16_IS_SURROGATE_LO(a[pos]) != 0 && pos != 0) + --pos; + --i; + } + + return *this; + } + + //! Return a new iterator that is a variable number of full characters forward from the current position. + _Iter operator+(const difference_type v) const + { + _Iter ret(*this); + ret += v; + return ret; + } + + //! Return a new iterator that is a variable number of full characters backward from the current position. + _Iter operator-(const difference_type v) const + { + _Iter ret(*this); + ret -= v; + return ret; + } + + //! Returns the distance between two iterators. + difference_type operator-(const _Iter& iter) const + { + // Make sure we reference the same object! + if (ref != iter.ref) + return difference_type(); + + _Iter i = iter; + difference_type ret; + + // Walk up. + if (pos > i.pos) + { + while (pos > i.pos) + { + ++i; + ++ret; + } + return ret; + } + + // Walk down. + while (pos < i.pos) + { + --i; + --ret; + } + return ret; + } + + //! Accesses the full character at the iterator's position. + const_reference operator*() const + { + if (pos >= ref->size_raw()) + { + const uchar16_t* a = ref->c_str(); + u32 p = ref->size_raw(); + if (UTF16_IS_SURROGATE_LO(a[p])) + --p; + reference ret(ref, p); + return ret; + } + const_reference ret(ref, pos); + return ret; + } + + //! Accesses the full character at the iterator's position. + reference operator*() + { + if (pos >= ref->size_raw()) + { + const uchar16_t* a = ref->c_str(); + u32 p = ref->size_raw(); + if (UTF16_IS_SURROGATE_LO(a[p])) + --p; + reference ret(ref, p); + return ret; + } + reference ret(ref, pos); + return ret; + } + + //! Accesses the full character at the iterator's position. + const_pointer operator->() const + { + return operator*(); + } + + //! Accesses the full character at the iterator's position. + pointer operator->() + { + return operator*(); + } + + //! Is the iterator at the start of the string? + bool atStart() const + { + return pos == 0; + } + + //! Is the iterator at the end of the string? + bool atEnd() const + { + const uchar16_t* a = ref->c_str(); + if (UTF16_IS_SURROGATE(a[pos])) + return (pos + 1) >= ref->size_raw(); + else return pos >= ref->size_raw(); + } + + //! Moves the iterator to the start of the string. + void toStart() + { + pos = 0; + } + + //! Moves the iterator to the end of the string. + void toEnd() + { + pos = ref->size_raw(); + } + + //! Returns the iterator's position. + //! \return The iterator's position. + u32 getPos() const + { + return pos; + } + + protected: + const ustring16* ref; + u32 pos; + }; + + //! Iterator to iterate through a UTF-16 string. + class _ustring16_iterator : public _ustring16_const_iterator + { + public: + typedef _ustring16_iterator _Iter; + typedef _ustring16_const_iterator _Base; + typedef typename _Base::const_pointer const_pointer; + typedef typename _Base::const_reference const_reference; + + + typedef typename _Base::value_type value_type; + typedef typename _Base::difference_type difference_type; + typedef typename _Base::distance_type distance_type; + typedef access pointer; + typedef access reference; + + using _Base::pos; + using _Base::ref; + + //! Constructors. + _ustring16_iterator(const _Iter& i) : _ustring16_const_iterator(i) {} + _ustring16_iterator(const ustring16& s) : _ustring16_const_iterator(s) {} + _ustring16_iterator(const ustring16& s, const u32 p) : _ustring16_const_iterator(s, p) {} + + //! Accesses the full character at the iterator's position. + reference operator*() const + { + if (pos >= ref->size_raw()) + { + const uchar16_t* a = ref->c_str(); + u32 p = ref->size_raw(); + if (UTF16_IS_SURROGATE_LO(a[p])) + --p; + reference ret(ref, p); + return ret; + } + reference ret(ref, pos); + return ret; + } + + //! Accesses the full character at the iterator's position. + reference operator*() + { + if (pos >= ref->size_raw()) + { + const uchar16_t* a = ref->c_str(); + u32 p = ref->size_raw(); + if (UTF16_IS_SURROGATE_LO(a[p])) + --p; + reference ret(ref, p); + return ret; + } + reference ret(ref, pos); + return ret; + } + + //! Accesses the full character at the iterator's position. + pointer operator->() const + { + return operator*(); + } + + //! Accesses the full character at the iterator's position. + pointer operator->() + { + return operator*(); + } + }; + + typedef typename ustring16::_ustring16_iterator iterator; + typedef typename ustring16::_ustring16_const_iterator const_iterator; + + ///----------------------/// + /// end iterator classes /// + ///----------------------/// + + //! Default constructor + ustring16() + : array(0), allocated(1), used(0) + { +#if __BYTE_ORDER == __BIG_ENDIAN + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + array = allocator.allocate(1); // new u16[1]; + array[0] = 0x0; + } + + + //! Constructor + ustring16(const ustring16& other) + : array(0), allocated(0), used(0) + { +#if __BYTE_ORDER == __BIG_ENDIAN + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + *this = other; + } + + + //! Constructor from other string types + template + ustring16(const string& other) + : array(0), allocated(0), used(0) + { +#if __BYTE_ORDER == __BIG_ENDIAN + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + *this = other; + } + + +#ifndef USTRING_NO_STL + //! Constructor from std::string + template + ustring16(const std::basic_string& other) + : array(0), allocated(0), used(0) + { +#if __BYTE_ORDER == __BIG_ENDIAN + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + *this = other.c_str(); + } + + + //! Constructor from iterator. + template + ustring16(Itr first, Itr last) + : array(0), allocated(0), used(0) + { +#if __BYTE_ORDER == __BIG_ENDIAN + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + reserve(std::distance(first, last)); + array[used] = 0; + + for (; first != last; ++first) + append((uchar32_t)*first); + } +#endif + + +#ifndef USTRING_CPP0X_NEWLITERALS + //! Constructor for copying a character string from a pointer. + ustring16(const char* const c) + : array(0), allocated(0), used(0) + { +#if __BYTE_ORDER == __BIG_ENDIAN + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + loadDataStream(c, strlen(c)); + //append((uchar8_t*)c); + } + + + //! Constructor for copying a character string from a pointer with a given length. + ustring16(const char* const c, u32 length) + : array(0), allocated(0), used(0) + { +#if __BYTE_ORDER == __BIG_ENDIAN + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + loadDataStream(c, length); + } +#endif + + + //! Constructor for copying a UTF-8 string from a pointer. + ustring16(const uchar8_t* const c) + : array(0), allocated(0), used(0) + { +#if __BYTE_ORDER == __BIG_ENDIAN + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c); + } + + + //! Constructor for copying a UTF-8 string from a single char. + ustring16(const char c) + : array(0), allocated(0), used(0) + { +#if __BYTE_ORDER == __BIG_ENDIAN + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append((uchar32_t)c); + } + + + //! Constructor for copying a UTF-8 string from a pointer with a given length. + ustring16(const uchar8_t* const c, u32 length) + : array(0), allocated(0), used(0) + { +#if __BYTE_ORDER == __BIG_ENDIAN + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c, length); + } + + + //! Constructor for copying a UTF-16 string from a pointer. + ustring16(const uchar16_t* const c) + : array(0), allocated(0), used(0) + { +#if __BYTE_ORDER == __BIG_ENDIAN + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c); + } + + + //! Constructor for copying a UTF-16 string from a pointer with a given length + ustring16(const uchar16_t* const c, u32 length) + : array(0), allocated(0), used(0) + { +#if __BYTE_ORDER == __BIG_ENDIAN + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c, length); + } + + + //! Constructor for copying a UTF-32 string from a pointer. + ustring16(const uchar32_t* const c) + : array(0), allocated(0), used(0) + { +#if __BYTE_ORDER == __BIG_ENDIAN + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c); + } + + + //! Constructor for copying a UTF-32 from a pointer with a given length. + ustring16(const uchar32_t* const c, u32 length) + : array(0), allocated(0), used(0) + { +#if __BYTE_ORDER == __BIG_ENDIAN + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c, length); + } + + + //! Constructor for copying a wchar_t string from a pointer. + ustring16(const wchar_t* const c) + : array(0), allocated(0), used(0) + { +#if __BYTE_ORDER == __BIG_ENDIAN + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + if (sizeof(wchar_t) == 4) + append(reinterpret_cast(c)); + else if (sizeof(wchar_t) == 2) + append(reinterpret_cast(c)); + else if (sizeof(wchar_t) == 1) + append(reinterpret_cast(c)); + } + + + //! Constructor for copying a wchar_t string from a pointer with a given length. + ustring16(const wchar_t* const c, u32 length) + : array(0), allocated(0), used(0) + { +#if __BYTE_ORDER == __BIG_ENDIAN + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + if (sizeof(wchar_t) == 4) + append(reinterpret_cast(c), length); + else if (sizeof(wchar_t) == 2) + append(reinterpret_cast(c), length); + else if (sizeof(wchar_t) == 1) + append(reinterpret_cast(c), length); + } + + +#ifdef USTRING_CPP0X + //! Constructor for moving a ustring16 + ustring16(ustring16&& other) + : array(other.array), encoding(other.encoding), allocated(other.allocated), used(other.used) + { + //std::cout << "MOVE constructor" << std::endl; + other.array = 0; + other.allocated = 0; + other.used = 0; + } +#endif + + + //! Destructor + ~ustring16() + { + allocator.deallocate(array); // delete [] array; + } + + + //! Assignment operator + ustring16& operator=(const ustring16& other) + { + if (this == &other) + return *this; + + used = other.size_raw(); + if (used >= allocated) + { + allocator.deallocate(array); // delete [] array; + allocated = used + 1; + array = allocator.allocate(used + 1); //new u16[used]; + } + + const uchar16_t* p = other.c_str(); + for (u32 i=0; i<=used; ++i, ++p) + array[i] = *p; + + array[used] = 0; + + // Validate our new UTF-16 string. + validate(); + + return *this; + } + + +#ifdef USTRING_CPP0X + //! Move assignment operator + ustring16& operator=(ustring16&& other) + { + if (this != &other) + { + //std::cout << "MOVE operator=" << std::endl; + allocator.deallocate(array); + + array = other.array; + allocated = other.allocated; + encoding = other.encoding; + used = other.used; + other.array = 0; + other.used = 0; + } + return *this; + } +#endif + + + //! Assignment operator for other string types + template + ustring16& operator=(const string& other) + { + *this = other.c_str(); + return *this; + } + + + //! Assignment operator for UTF-8 strings + ustring16& operator=(const uchar8_t* const c) + { + if (!array) + { + array = allocator.allocate(1); //new u16[1]; + allocated = 1; + } + used = 0; + array[used] = 0x0; + if (!c) return *this; + + //! Append our string now. + append(c); + return *this; + } + + + //! Assignment operator for UTF-16 strings + ustring16& operator=(const uchar16_t* const c) + { + if (!array) + { + array = allocator.allocate(1); //new u16[1]; + allocated = 1; + } + used = 0; + array[used] = 0x0; + if (!c) return *this; + + //! Append our string now. + append(c); + return *this; + } + + + //! Assignment operator for UTF-32 strings + ustring16& operator=(const uchar32_t* const c) + { + if (!array) + { + array = allocator.allocate(1); //new u16[1]; + allocated = 1; + } + used = 0; + array[used] = 0x0; + if (!c) return *this; + + //! Append our string now. + append(c); + return *this; + } + + + //! Assignment operator for wchar_t strings. + /** Note that this assumes that a correct unicode string is stored in the wchar_t string. + Since wchar_t changes depending on its platform, it could either be a UTF-8, -16, or -32 string. + This function assumes you are storing the correct unicode encoding inside the wchar_t string. **/ + ustring16& operator=(const wchar_t* const c) + { + if (sizeof(wchar_t) == 4) + *this = reinterpret_cast(c); + else if (sizeof(wchar_t) == 2) + *this = reinterpret_cast(c); + else if (sizeof(wchar_t) == 1) + *this = reinterpret_cast(c); + + return *this; + } + + + //! Assignment operator for other strings. + /** Note that this assumes that a correct unicode string is stored in the string. **/ + template + ustring16& operator=(const B* const c) + { + if (sizeof(B) == 4) + *this = reinterpret_cast(c); + else if (sizeof(B) == 2) + *this = reinterpret_cast(c); + else if (sizeof(B) == 1) + *this = reinterpret_cast(c); + + return *this; + } + + + //! Direct access operator + access operator [](const u32 index) + { + _IRR_DEBUG_BREAK_IF(index>=size()) // bad index + iterator iter(*this, index); + return iter.operator*(); + } + + + //! Direct access operator + const access operator [](const u32 index) const + { + _IRR_DEBUG_BREAK_IF(index>=size()) // bad index + const_iterator iter(*this, index); + return iter.operator*(); + } + + + //! Equality operator + bool operator ==(const uchar16_t* const str) const + { + if (!str) + return false; + + u32 i; + for(i=0; array[i] && str[i]; ++i) + if (array[i] != str[i]) + return false; + + return !array[i] && !str[i]; + } + + + //! Equality operator + bool operator ==(const ustring16& other) const + { + for(u32 i=0; array[i] && other.array[i]; ++i) + if (array[i] != other.array[i]) + return false; + + return used == other.used; + } + + + //! Is smaller comparator + bool operator <(const ustring16& other) const + { + for(u32 i=0; array[i] && other.array[i]; ++i) + { + s32 diff = array[i] - other.array[i]; + if ( diff ) + return diff < 0; + } + + return used < other.used; + } + + + //! Inequality operator + bool operator !=(const uchar16_t* const str) const + { + return !(*this == str); + } + + + //! Inequality operator + bool operator !=(const ustring16& other) const + { + return !(*this == other); + } + + + //! Returns the length of a ustring16 in full characters. + //! \return Length of a ustring16 in full characters. + u32 size() const + { + const_iterator i(*this, 0); + u32 pos = 0; + while (!i.atEnd()) + { + ++i; + ++pos; + } + return pos; + } + + + //! Informs if the ustring is empty or not. + //! \return True if the ustring is empty, false if not. + bool empty() const + { + return (size_raw() == 0); + } + + + //! Returns a pointer to the raw UTF-16 string data. + //! \return pointer to C-style NUL terminated array of UTF-16 code points. + const uchar16_t* c_str() const + { + return array; + } + + + //! Compares the first n characters of this string with another. + //! \param other Other string to compare to. + //! \param n Number of characters to compare. + //! \return True if the n first characters of both strings are equal. + bool equalsn(const ustring16& other, u32 n) const + { + u32 i; + const uchar16_t* oa = other.c_str(); + for(i=0; array[i] && oa[i] && i < n; ++i) + if (array[i] != oa[i]) + return false; + + // if one (or both) of the strings was smaller then they + // are only equal if they have the same length + return (i == n) || (used == other.used); + } + + + //! Compares the first n characters of this string with another. + //! \param str Other string to compare to. + //! \param n Number of characters to compare. + //! \return True if the n first characters of both strings are equal. + bool equalsn(const uchar16_t* const str, u32 n) const + { + if (!str) + return false; + u32 i; + for(i=0; array[i] && str[i] && i < n; ++i) + if (array[i] != str[i]) + return false; + + // if one (or both) of the strings was smaller then they + // are only equal if they have the same length + return (i == n) || (array[i] == 0 && str[i] == 0); + } + + + //! Appends a character to this ustring16 + //! \param character The character to append. + //! \return A reference to our current string. + ustring16& append(uchar32_t character) + { + if (used + 2 >= allocated) + reallocate(used + 2); + + if (character > 0xFFFF) + { + used += 2; + + // character will be multibyte, so split it up into a surrogate pair. + uchar16_t x = static_cast(character); + uchar16_t vh = UTF16_HI_SURROGATE | ((((character >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); + array[used-2] = vh; + array[used-1] = vl; + } + else + { + ++used; + array[used-1] = character; + } + array[used] = 0; + + return *this; + } + + + //! Appends a UTF-8 string to this ustring16 + //! \param other The UTF-8 string to append. + //! \param length The length of the string to append. + //! \return A reference to our current string. + ustring16& append(const uchar8_t* const other, u32 length=0xffffffff) + { + if (!other) + return *this; + + // Determine if the string is long enough for a BOM. + u32 len = 0; + const uchar8_t* p = other; + do + { + ++len; + } while (*p++ && len < unicode::BOM_ENCODE_UTF8_LEN); + + // Check for BOM. + unicode::EUTF_ENCODE c_bom = unicode::EUTFE_NONE; + if (len == unicode::BOM_ENCODE_UTF8_LEN) + { + if (memcmp(other, unicode::BOM_ENCODE_UTF8, unicode::BOM_ENCODE_UTF8_LEN) == 0) + c_bom = unicode::EUTFE_UTF8; + } + + // If a BOM was found, don't include it in the string. + const uchar8_t* c2 = other; + if (c_bom != unicode::EUTFE_NONE) + { + c2 = other + unicode::BOM_UTF8_LEN; + length -= unicode::BOM_UTF8_LEN; + } + + // Calculate the size of the string to read in. + len = 0; + p = c2; + do + { + ++len; + } while(*p++ && len < length); + if (len > length) + len = length; + + // If we need to grow the array, do it now. + if (used + len >= allocated) + reallocate(used + (len * 2)); + u32 start = used; + + // Convert UTF-8 to UTF-16. + u32 pos = start; + for (u32 l = 0; l> 6) & 0x03) == 0x02) + { // Invalid continuation byte. + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + ++l; + } + else if (c2[l] == 0xC0 || c2[l] == 0xC1) + { // Invalid byte - overlong encoding. + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + ++l; + } + else if ((c2[l] & 0xF8) == 0xF0) + { // 4 bytes UTF-8, 2 bytes UTF-16. + // Check for a full string. + if ((l + 3) >= len) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + l += 3; + break; + } + + // Validate. + bool valid = true; + u8 l2 = 0; + if (valid && (((c2[l+1] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; + if (valid && (((c2[l+2] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; + if (valid && (((c2[l+3] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; + if (!valid) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + l += l2; + continue; + } + + // Decode. + uchar8_t b1 = ((c2[l] & 0x7) << 2) | ((c2[l+1] >> 4) & 0x3); + uchar8_t b2 = ((c2[l+1] & 0xF) << 4) | ((c2[l+2] >> 2) & 0xF); + uchar8_t b3 = ((c2[l+2] & 0x3) << 6) | (c2[l+3] & 0x3F); + uchar32_t v = b3 | ((uchar32_t)b2 << 8) | ((uchar32_t)b1 << 16); + + // Split v up into a surrogate pair. + uchar16_t x = static_cast(v); + uchar16_t vh = UTF16_HI_SURROGATE | ((((v >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); + + array[pos++] = vh; + array[pos++] = vl; + l += 4; + ++used; // Using two shorts this time, so increase used by 1. + } + else if ((c2[l] & 0xF0) == 0xE0) + { // 3 bytes UTF-8, 1 byte UTF-16. + // Check for a full string. + if ((l + 2) >= len) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + l += 2; + break; + } + + // Validate. + bool valid = true; + u8 l2 = 0; + if (valid && (((c2[l+1] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; + if (valid && (((c2[l+2] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; + if (!valid) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + l += l2; + continue; + } + + // Decode. + uchar8_t b1 = ((c2[l] & 0xF) << 4) | ((c2[l+1] >> 2) & 0xF); + uchar8_t b2 = ((c2[l+1] & 0x3) << 6) | (c2[l+2] & 0x3F); + uchar16_t ch = b2 | ((uchar16_t)b1 << 8); + array[pos++] = ch; + l += 3; + } + else if ((c2[l] & 0xE0) == 0xC0) + { // 2 bytes UTF-8, 1 byte UTF-16. + // Check for a full string. + if ((l + 1) >= len) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + l += 1; + break; + } + + // Validate. + if (((c2[l+1] >> 6) & 0x03) != 0x02) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + ++l; + continue; + } + + // Decode. + uchar8_t b1 = (c2[l] >> 2) & 0x7; + uchar8_t b2 = ((c2[l] & 0x3) << 6) | (c2[l+1] & 0x3F); + uchar16_t ch = b2 | ((uchar16_t)b1 << 8); + array[pos++] = ch; + l += 2; + } + else + { // 1 byte UTF-8, 1 byte UTF-16. + // Validate. + if (c2[l] > 0x7F) + { // Values above 0xF4 are restricted and aren't used. By now, anything above 0x7F is invalid. + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + } + else array[pos++] = static_cast(c2[l]); + ++l; + } + } + array[used] = 0; + + // Validate our new UTF-16 string. + validate(); + + return *this; + } + + + //! Appends a UTF-16 string to this ustring16 + //! \param other The UTF-16 string to append. + //! \param length The length of the string to append. + //! \return A reference to our current string. + ustring16& append(const uchar16_t* const other, u32 length=0xffffffff) + { + if (!other) + return *this; + + // Determine if the string is long enough for a BOM. + u32 len = 0; + const uchar16_t* p = other; + do + { + ++len; + } while (*p++ && len < unicode::BOM_ENCODE_UTF16_LEN); + + // Check for the BOM to determine the string's endianness. + unicode::EUTF_ENDIAN c_end = unicode::EUTFEE_NATIVE; + if (memcmp(other, unicode::BOM_ENCODE_UTF16_LE, unicode::BOM_ENCODE_UTF16_LEN) == 0) + c_end = unicode::EUTFEE_LITTLE; + else if (memcmp(other, unicode::BOM_ENCODE_UTF16_BE, unicode::BOM_ENCODE_UTF16_LEN) == 0) + c_end = unicode::EUTFEE_BIG; + + // If a BOM was found, don't include it in the string. + const uchar16_t* c2 = other; + if (c_end != unicode::EUTFEE_NATIVE) + { + c2 = other + unicode::BOM_UTF16_LEN; + length -= unicode::BOM_UTF16_LEN; + } + + // Calculate the size of the string to read in. + len = 0; + p = c2; + do + { + ++len; + } while(*p++ && len < length); + if (len > length) + len = length; + + // If we need to grow the size of the array, do it now. + if (used + len >= allocated) + reallocate(used + (len * 2)); + u32 start = used; + used += len; + + // Copy the string now. + unicode::EUTF_ENDIAN m_end = getEndianness(); + for (u32 l = start; l < start + len; ++l) + { + array[l] = (uchar16_t)c2[l]; + if (c_end != unicode::EUTFEE_NATIVE && c_end != m_end) + array[l] = unicode::swapEndian16(array[l]); + } + + array[used] = 0; + + // Validate our new UTF-16 string. + validate(); + return *this; + } + + + //! Appends a UTF-32 string to this ustring16 + //! \param other The UTF-32 string to append. + //! \param length The length of the string to append. + //! \return A reference to our current string. + ustring16& append(const uchar32_t* const other, u32 length=0xffffffff) + { + if (!other) + return *this; + + // Check for the BOM to determine the string's endianness. + unicode::EUTF_ENDIAN c_end = unicode::EUTFEE_NATIVE; + if (memcmp(other, unicode::BOM_ENCODE_UTF32_LE, unicode::BOM_ENCODE_UTF32_LEN) == 0) + c_end = unicode::EUTFEE_LITTLE; + else if (memcmp(other, unicode::BOM_ENCODE_UTF32_BE, unicode::BOM_ENCODE_UTF32_LEN) == 0) + c_end = unicode::EUTFEE_BIG; + + // If a BOM was found, don't include it in the string. + const uchar32_t* c2 = other; + if (c_end != unicode::EUTFEE_NATIVE) + { + c2 = other + unicode::BOM_UTF32_LEN; + length -= unicode::BOM_UTF32_LEN; + } + + // Calculate the size of the string to read in. + u32 len = 0; + const uchar32_t* p = c2; + do + { + ++len; + } while(*p++ && len < length); + if (len > length) + len = length; + + // If we need to grow the size of the array, do it now. + // In case all of the UTF-32 string is split into surrogate pairs, do len * 2. + if (used + (len * 2) >= allocated) + reallocate(used + ((len * 2) * 2)); + u32 start = used; + + // Convert UTF-32 to UTF-16. + unicode::EUTF_ENDIAN m_end = getEndianness(); + u32 pos = start; + for (u32 l = 0; l 0xFFFF) + { + // Split ch up into a surrogate pair as it is over 16 bits long. + uchar16_t x = static_cast(ch); + uchar16_t vh = UTF16_HI_SURROGATE | ((((ch >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); + array[pos++] = vh; + array[pos++] = vl; + ++used; // Using two shorts, so increased used again. + } + else if (ch >= 0xD800 && ch <= 0xDFFF) + { + // Between possible UTF-16 surrogates (invalid!) + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + } + else array[pos++] = static_cast(ch); + } + array[used] = 0; + + // Validate our new UTF-16 string. + validate(); + + return *this; + } + + + //! Appends a ustring16 to this ustring16 + //! \param other The string to append to this one. + //! \return A reference to our current string. + ustring16& append(const ustring16& other) + { + const uchar16_t* oa = other.c_str(); + + u32 len = other.size_raw(); + + if (used + len >= allocated) + reallocate(used + len); + + for (u32 l=0; l& append(const ustring16& other, u32 length) + { + if (other.size() == 0) + return *this; + + if (other.size() < length) + { + append(other); + return *this; + } + + if (used + length * 2 >= allocated) + reallocate(used + length * 2); + + const_iterator iter(other, 0); + u32 l = length; + while (!iter.atEnd() && l) + { + uchar32_t c = *iter; + append(c); + ++iter; + --l; + } + + return *this; + } + + + //! Reserves some memory. + //! \param count The amount of characters to reserve. + void reserve(u32 count) + { + if (count < allocated) + return; + + reallocate(count); + } + + + //! Finds first occurrence of character. + //! \param c The character to search for. + //! \return Position where the character has been found, or -1 if not found. + s32 findFirst(uchar32_t c) const + { + const_iterator i(*this, 0); + + s32 pos = 0; + while (!i.atEnd()) + { + uchar32_t t = *i; + if (c == t) + return pos; + ++pos; + ++i; + } + + return -1; + } + + //! Finds first occurrence of a character of a list. + //! \param c A list of characters to find. For example if the method should find the first occurrence of 'a' or 'b', this parameter should be "ab". + //! \param count The amount of characters in the list. Usually, this should be strlen(c). + //! \return Position where one of the characters has been found, or -1 if not found. + s32 findFirstChar(const uchar32_t* const c, u32 count=1) const + { + if (!c || !count) + return -1; + + const_iterator i(*this, 0); + + s32 pos = 0; + while (!i.atEnd()) + { + uchar32_t t = *i; + for (u32 j=0; j& str, const u32 start = 0) const + { + u32 my_size = size(); + u32 their_size = str.size(); + + if (their_size == 0 || my_size - start < their_size) + return -1; + + const_iterator i(*this, start); + + s32 pos = start; + while (!i.atEnd()) + { + const_iterator i2(i); + const_iterator j(str, 0); + uchar32_t t1 = (uchar32_t)*i2; + uchar32_t t2 = (uchar32_t)*j; + while (t1 == t2) + { + ++i2; + ++j; + if (j.atEnd()) + return pos; + t1 = (uchar32_t)*i2; + t2 = (uchar32_t)*j; + } + ++i; + ++pos; + } + + return -1; + } + + + //! Finds another ustring16 in this ustring16. + //! \param str The string to find. + //! \param start The start position of the search. + //! \return Positions where the string has been found, or -1 if not found. + s32 find_raw(const ustring16& str, const u32 start = 0) const + { + const uchar16_t* data = str.c_str(); + if (data && *data) + { + u32 len = 0; + + while (data[len]) + ++len; + + if (len > used) + return -1; + + for (u32 i=start; i<=used-len; ++i) + { + u32 j=0; + + while(data[j] && array[i+j] == data[j]) + ++j; + + if (!data[j]) + return i; + } + } + + return -1; + } + + + //! Returns a substring. + //! \param begin: Start of substring. + //! \param length: Length of substring. + //! \return A reference to our current string. + ustring16 subString(u32 begin, s32 length) const + { + u32 len = size(); + // if start after ustring16 + // or no proper substring length + if ((length <= 0) || (begin>=len)) + return ustring16(""); + // clamp length to maximal value + if ((length+begin) > len) + length = len-begin; + + ustring16 o; + o.reserve((length+1) * 2); + + const_iterator i(*this, begin); + while (!i.atEnd() && length) + { + o.append(*i); + ++i; + --length; + } + + return o; + } + + + //! Appends a character to this ustring16. + //! \param c Character to append. + //! \return A reference to our current string. + ustring16& operator += (char c) + { + append((uchar32_t)c); + return *this; + } + + + //! Appends a character to this ustring16. + //! \param c Character to append. + //! \return A reference to our current string. + ustring16& operator += (uchar32_t c) + { + append(c); + return *this; + } + + + //! Appends a number to this ustring16. + //! \param c Number to append. + //! \return A reference to our current string. + ustring16& operator += (short c) + { + append(core::stringc(c)); + return *this; + } + + + //! Appends a number to this ustring16. + //! \param c Number to append. + //! \return A reference to our current string. + ustring16& operator += (unsigned short c) + { + append(core::stringc(c)); + return *this; + } + + +#ifdef USTRING_CPP0X_NEWLITERALS + //! Appends a number to this ustring16. + //! \param c Number to append. + //! \return A reference to our current string. + ustring16& operator += (int c) + { + append(core::stringc(c)); + return *this; + } + + + //! Appends a number to this ustring16. + //! \param c Number to append. + //! \return A reference to our current string. + ustring16& operator += (unsigned int c) + { + append(core::stringc(c)); + return *this; + } +#endif + + + //! Appends a number to this ustring16. + //! \param c Number to append. + //! \return A reference to our current string. + ustring16& operator += (long c) + { + append(core::stringc(c)); + return *this; + } + + + //! Appends a number to this ustring16. + //! \param c Number to append. + //! \return A reference to our current string. + ustring16& operator += (unsigned long c) + { + append(core::stringc(c)); + return *this; + } + + + //! Appends a number to this ustring16. + //! \param c Number to append. + //! \return A reference to our current string. + ustring16& operator += (double c) + { + append(core::stringc(c)); + return *this; + } + + + //! Appends a char ustring16 to this ustring16. + //! \param c Char ustring16 to append. + //! \return A reference to our current string. + ustring16& operator += (const uchar16_t* const c) + { + append(c); + return *this; + } + + + //! Appends a ustring16 to this ustring16. + //! \param other ustring16 to append. + //! \return A reference to our current string. + ustring16& operator += (const ustring16& other) + { + append(other); + return *this; + } + + + //! Replaces all characters of a given type with another one. + //! \param toReplace Character to replace. + //! \param replaceWith Character replacing the old one. + //! \return A reference to our current string. + ustring16& replace(uchar32_t toReplace, uchar32_t replaceWith) + { + iterator i(*this, 0); + while (!i.atEnd()) + { + typename ustring16::access a = *i; + if ((uchar32_t)a == toReplace) + a = replaceWith; + ++i; + } + return *this; + } + + + //! Replaces all instances of a string with another one. + //! \param toReplace The string to replace. + //! \param replaceWith The string replacing the old one. + //! \return A reference to our current string. + ustring16& replace(const ustring16& toReplace, const ustring16& replaceWith) + { + if (toReplace.size() == 0) + return *this; + + const uchar16_t* other = toReplace.c_str(); + const uchar16_t* replace = replaceWith.c_str(); + const u32 other_size = toReplace.size_raw(); + const u32 replace_size = replaceWith.size_raw(); + + // Determine the delta. The algorithm will change depending on the delta. + s32 delta = replace_size - other_size; + + // A character for character replace. The string will not shrink or grow. + if (delta == 0) + { + s32 pos = 0; + while ((pos = find_raw(other, pos)) != -1) + { + for (u32 i = 0; i < replace_size; ++i) + array[pos + i] = replace[i]; + ++pos; + } + return *this; + } + + // We are going to be removing some characters. The string will shrink. + if (delta < 0) + { + u32 i = 0; + for (u32 pos = 0; pos <= used; ++i, ++pos) + { + // Is this potentially a match? + if (array[pos] == *other) + { + // Check to see if we have a match. + u32 j; + for (j = 0; j < other_size; ++j) + { + if (array[pos + j] != other[j]) + break; + } + + // If we have a match, replace characters. + if (j == other_size) + { + for (j = 0; j < replace_size; ++j) + array[i + j] = replace[j]; + i += replace_size - 1; + pos += other_size - 1; + continue; + } + } + + // No match found, just copy characters. + array[i - 1] = array[pos]; + } + array[i] = 0; + used = i; + + return *this; + } + + // We are going to be adding characters, so the string size will increase. + // Count the number of times toReplace exists in the string so we can allocate the new size. + u32 find_count = 0; + s32 pos = 0; + while ((pos = find_raw(other, pos)) != -1) + { + ++find_count; + ++pos; + } + + // Re-allocate the string now, if needed. + u32 len = delta * find_count; + if (used + len >= allocated) + reallocate(used + len); + + // Start replacing. + pos = 0; + while ((pos = find_raw(other, pos)) != -1) + { + uchar16_t* start = array + pos + other_size - 1; + uchar16_t* ptr = array + used; + uchar16_t* end = array + used + delta; + + // Shift characters to make room for the string. + while (ptr != start) + { + *end = *ptr; + --ptr; + --end; + } + + // Add the new string now. + for (u32 i = 0; i < replace_size; ++i) + array[pos + i] = replace[i]; + + pos += replace_size; + used += delta; + } + + // Terminate the string and return ourself. + array[used] = 0; + return *this; + } + + + //! Removes characters from a ustring16.. + //! \param c The character to remove. + //! \return A reference to our current string. + ustring16& remove(uchar32_t c) + { + u32 pos = 0; + u32 found = 0; + u32 len = (c > 0xFFFF ? 2 : 1); // Remove characters equal to the size of c as a UTF-16 character. + for (u32 i=0; i<=used; ++i) + { + uchar32_t uc32 = 0; + if (!UTF16_IS_SURROGATE_HI(array[i])) + uc32 |= array[i]; + else if (i + 1 <= used) + { + // Convert the surrogate pair into a single UTF-32 character. + uc32 = unicode::toUTF32(array[i], array[i + 1]); + } + u32 len2 = (uc32 > 0xFFFF ? 2 : 1); + + if (uc32 == c) + { + found += len; + continue; + } + + array[pos++] = array[i]; + if (len2 == 2) + array[pos++] = array[++i]; + } + used -= found; + array[used] = 0; + return *this; + } + + + //! Removes a ustring16 from the ustring16. + //! \param toRemove The string to remove. + //! \return A reference to our current string. + ustring16& remove(const ustring16& toRemove) + { + u32 size = toRemove.size_raw(); + if (size == 0) return *this; + + const uchar16_t* tra = toRemove.c_str(); + u32 pos = 0; + u32 found = 0; + for (u32 i=0; i<=used; ++i) + { + u32 j = 0; + while (j < size) + { + if (array[i + j] != tra[j]) + break; + ++j; + } + if (j == size) + { + found += size; + i += size - 1; + continue; + } + + array[pos++] = array[i]; + } + used -= found; + array[used] = 0; + return *this; + } + + + //! Removes characters from the ustring16. + //! \param characters The characters to remove. + //! \return A reference to our current string. + ustring16& removeChars(const ustring16& characters) + { + if (characters.size_raw() == 0) + return *this; + + u32 pos = 0; + u32 found = 0; + const_iterator iter(characters); + for (u32 i=0; i<=used; ++i) + { + uchar32_t uc32 = 0; + if (!UTF16_IS_SURROGATE_HI(array[i])) + uc32 |= array[i]; + else if (i + 1 <= used) + { + // Convert the surrogate pair into a single UTF-32 character. + uc32 = unicode::toUTF32(array[i], array[i+1]); + } + u32 len2 = (uc32 > 0xFFFF ? 2 : 1); + + bool cont = false; + iter.toStart(); + while (!iter.atEnd()) + { + uchar32_t c = *iter; + if (uc32 == c) + { + found += (c > 0xFFFF ? 2 : 1); // Remove characters equal to the size of c as a UTF-16 character. + ++i; + cont = true; + break; + } + ++iter; + } + if (cont) continue; + + array[pos++] = array[i]; + if (len2 == 2) + array[pos++] = array[++i]; + } + used -= found; + array[used] = 0; + return *this; + } + + + //! Trims the ustring16. + //! Removes the specified characters (by default, Latin-1 whitespace) from the begining and the end of the ustring16. + //! \param whitespace The characters that are to be considered as whitespace. + //! \return A reference to our current string. + ustring16& trim(const ustring16& whitespace = " \t\n\r") + { + core::array utf32white = whitespace.toUTF32(); + + // find start and end of the substring without the specified characters + const s32 begin = findFirstCharNotInList(utf32white.const_pointer(), whitespace.used + 1); + if (begin == -1) + return (*this=""); + + const s32 end = findLastCharNotInList(utf32white.const_pointer(), whitespace.used + 1); + + return (*this = subString(begin, (end +1) - begin)); + } + + + //! Erases a character from the ustring16. + //! May be slow, because all elements following after the erased element have to be copied. + //! \param index Index of element to be erased. + //! \return A reference to our current string. + ustring16& erase(u32 index) + { + _IRR_DEBUG_BREAK_IF(index>used) // access violation + + iterator i(*this, index); + + uchar32_t t = *i; + u32 len = (t > 0xFFFF ? 2 : 1); + + for (u32 j = static_cast(i.getPos()) + len; j <= used; ++j) + array[j - len] = array[j]; + + used -= len; + array[used] = 0; + + return *this; + } + + + //! Validate the existing ustring16, checking for valid surrogate pairs and checking for proper termination. + //! \return A reference to our current string. + ustring16& validate() + { + // Validate all unicode characters. + for (u32 i=0; i= allocated) || UTF16_IS_SURROGATE_LO(array[i])) + array[i] = unicode::UTF_REPLACEMENT_CHARACTER; + else if (UTF16_IS_SURROGATE_HI(array[i]) && !UTF16_IS_SURROGATE_LO(array[i+1])) + array[i] = unicode::UTF_REPLACEMENT_CHARACTER; + ++i; + } + if (array[i] >= 0xFDD0 && array[i] <= 0xFDEF) + array[i] = unicode::UTF_REPLACEMENT_CHARACTER; + } + + // terminate + used = 0; + if (allocated > 0) + { + used = allocated - 1; + array[used] = 0; + } + return *this; + } + + + //! Gets the last char of the ustring16, or 0. + //! \return The last char of the ustring16, or 0. + uchar32_t lastChar() const + { + if (used < 1) + return 0; + + if (UTF16_IS_SURROGATE_LO(array[used-1])) + { + // Make sure we have a paired surrogate. + if (used < 2) + return 0; + + // Check for an invalid surrogate. + if (!UTF16_IS_SURROGATE_HI(array[used-2])) + return 0; + + // Convert the surrogate pair into a single UTF-32 character. + return unicode::toUTF32(array[used-2], array[used-1]); + } + else + { + return array[used-1]; + } + } + + + //! Split the ustring16 into parts. + /** This method will split a ustring16 at certain delimiter characters + into the container passed in as reference. The type of the container + has to be given as template parameter. It must provide a push_back and + a size method. + \param ret The result container + \param c C-style ustring16 of delimiter characters + \param count Number of delimiter characters + \param ignoreEmptyTokens Flag to avoid empty substrings in the result + container. If two delimiters occur without a character in between, an + empty substring would be placed in the result. If this flag is set, + only non-empty strings are stored. + \param keepSeparators Flag which allows to add the separator to the + result ustring16. If this flag is true, the concatenation of the + substrings results in the original ustring16. Otherwise, only the + characters between the delimiters are returned. + \return The number of resulting substrings + */ + template + u32 split(container& ret, const uchar32_t* const c, u32 count=1, bool ignoreEmptyTokens=true, bool keepSeparators=false) const + { + if (!c) + return 0; + + const_iterator i(*this); + const u32 oldSize=ret.size(); + u32 pos = 0; + u32 lastpos = 0; + u32 lastpospos = 0; + bool lastWasSeparator = false; + while (!i.atEnd()) + { + uchar32_t ch = *i; + bool foundSeparator = false; + for (u32 j=0; j(&array[lastpospos], pos - lastpos)); + foundSeparator = true; + lastpos = (keepSeparators ? pos : pos + 1); + lastpospos = (keepSeparators ? i.getPos() : i.getPos() + 1); + break; + } + } + lastWasSeparator = foundSeparator; + ++pos; + ++i; + } + u32 s = size() + 1; + if (s > lastpos) + ret.push_back(ustring16(&array[lastpospos], s - lastpos)); + return ret.size()-oldSize; + } + + + //! Split the ustring16 into parts. + /** This method will split a ustring16 at certain delimiter characters + into the container passed in as reference. The type of the container + has to be given as template parameter. It must provide a push_back and + a size method. + \param ret The result container + \param c A unicode string of delimiter characters + \param ignoreEmptyTokens Flag to avoid empty substrings in the result + container. If two delimiters occur without a character in between, an + empty substring would be placed in the result. If this flag is set, + only non-empty strings are stored. + \param keepSeparators Flag which allows to add the separator to the + result ustring16. If this flag is true, the concatenation of the + substrings results in the original ustring16. Otherwise, only the + characters between the delimiters are returned. + \return The number of resulting substrings + */ + template + u32 split(container& ret, const ustring16& c, bool ignoreEmptyTokens=true, bool keepSeparators=false) const + { + core::array v = c.toUTF32(); + return split(ret, v.pointer(), v.size(), ignoreEmptyTokens, keepSeparators); + } + + + //! Gets the size of the allocated memory buffer for the string. + //! \return The size of the allocated memory buffer. + u32 capacity() const + { + return allocated; + } + + + //! Returns the raw number of UTF-16 code points in the string which includes the individual surrogates. + //! \return The raw number of UTF-16 code points, excluding the trialing NUL. + u32 size_raw() const + { + return used; + } + + + //! Inserts a character into the string. + //! \param c The character to insert. + //! \param pos The position to insert the character. + //! \return A reference to our current string. + ustring16& insert(uchar32_t c, u32 pos) + { + u8 len = (c > 0xFFFF ? 2 : 1); + + if (used + len >= allocated) + reallocate(used + len); + + used += len; + + iterator iter(*this, pos); + for (u32 i = used - 2; i > iter.getPos(); --i) + array[i] = array[i - len]; + + if (c > 0xFFFF) + { + // c will be multibyte, so split it up into a surrogate pair. + uchar16_t x = static_cast(c); + uchar16_t vh = UTF16_HI_SURROGATE | ((((c >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); + array[iter.getPos()] = vh; + array[iter.getPos()+1] = vl; + } + else + { + array[iter.getPos()] = static_cast(c); + } + array[used] = 0; + return *this; + } + + + //! Inserts a string into the string. + //! \param c The string to insert. + //! \param pos The position to insert the string. + //! \return A reference to our current string. + ustring16& insert(const ustring16& c, u32 pos) + { + u32 len = c.size_raw(); + if (len == 0) return *this; + + if (used + len >= allocated) + reallocate(used + len); + + used += len; + + iterator iter(*this, pos); + for (u32 i = used - 2; i > iter.getPos() + len; --i) + array[i] = array[i - len]; + + const uchar16_t* s = c.c_str(); + for (u32 i = 0; i < len; ++i) + { + array[pos++] = *s; + ++s; + } + + array[used] = 0; + return *this; + } + + + //! Inserts a character into the string. + //! \param c The character to insert. + //! \param pos The position to insert the character. + //! \return A reference to our current string. + ustring16& insert_raw(uchar16_t c, u32 pos) + { + if (used + 1 >= allocated) + reallocate(used + 1); + + ++used; + + for (u32 i = used - 1; i > pos; --i) + array[i] = array[i - 1]; + + array[pos] = c; + array[used] = 0; + return *this; + } + + + //! Removes a character from string. + //! \param pos Position of the character to remove. + //! \return A reference to our current string. + ustring16& erase_raw(u32 pos) + { + for (u32 i=pos; i<=used; ++i) + { + array[i] = array[i + 1]; + } + --used; + array[used] = 0; + return *this; + } + + + //! Replaces a character in the string. + //! \param c The new character. + //! \param pos The position of the character to replace. + //! \return A reference to our current string. + ustring16& replace_raw(uchar16_t c, u32 pos) + { + array[pos] = c; + return *this; + } + + + //! Returns an iterator to the beginning of the string. + //! \return An iterator to the beginning of the string. + iterator begin() + { + iterator i(*this, 0); + return i; + } + + + //! Returns an iterator to the beginning of the string. + //! \return An iterator to the beginning of the string. + const_iterator begin() const + { + const_iterator i(*this, 0); + return i; + } + + + //! Returns an iterator to the beginning of the string. + //! \return An iterator to the beginning of the string. + const_iterator cbegin() const + { + const_iterator i(*this, 0); + return i; + } + + + //! Returns an iterator to the end of the string. + //! \return An iterator to the end of the string. + iterator end() + { + iterator i(*this, 0); + i.toEnd(); + return i; + } + + + //! Returns an iterator to the end of the string. + //! \return An iterator to the end of the string. + const_iterator end() const + { + const_iterator i(*this, 0); + i.toEnd(); + return i; + } + + + //! Returns an iterator to the end of the string. + //! \return An iterator to the end of the string. + const_iterator cend() const + { + const_iterator i(*this, 0); + i.toEnd(); + return i; + } + + + //! Converts the string to a UTF-8 encoded string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return A string containing the UTF-8 encoded string. + core::string toUTF8_s(const bool addBOM = false) const + { + core::string ret; + ret.reserve(used * 4 + (addBOM ? unicode::BOM_UTF8_LEN : 0) + 1); + const_iterator iter(*this, 0); + + // Add the byte order mark if the user wants it. + if (addBOM) + { + ret.append(unicode::BOM_ENCODE_UTF8[0]); + ret.append(unicode::BOM_ENCODE_UTF8[1]); + ret.append(unicode::BOM_ENCODE_UTF8[2]); + } + + while (!iter.atEnd()) + { + uchar32_t c = *iter; + if (c > 0xFFFF) + { // 4 bytes + uchar8_t b1 = (0x1E << 3) | ((c >> 18) & 0x7); + uchar8_t b2 = (0x2 << 6) | ((c >> 12) & 0x3F); + uchar8_t b3 = (0x2 << 6) | ((c >> 6) & 0x3F); + uchar8_t b4 = (0x2 << 6) | (c & 0x3F); + ret.append(b1); + ret.append(b2); + ret.append(b3); + ret.append(b4); + } + else if (c > 0x7FF) + { // 3 bytes + uchar8_t b1 = (0xE << 4) | ((c >> 12) & 0xF); + uchar8_t b2 = (0x2 << 6) | ((c >> 6) & 0x3F); + uchar8_t b3 = (0x2 << 6) | (c & 0x3F); + ret.append(b1); + ret.append(b2); + ret.append(b3); + } + else if (c > 0x7F) + { // 2 bytes + uchar8_t b1 = (0x6 << 5) | ((c >> 6) & 0x1F); + uchar8_t b2 = (0x2 << 6) | (c & 0x3F); + ret.append(b1); + ret.append(b2); + } + else + { // 1 byte + ret.append(static_cast(c)); + } + ++iter; + } + return ret; + } + + + //! Converts the string to a UTF-8 encoded string array. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return An array containing the UTF-8 encoded string. + core::array toUTF8(const bool addBOM = false) const + { + core::array ret(used * 4 + (addBOM ? unicode::BOM_UTF8_LEN : 0) + 1); + const_iterator iter(*this, 0); + + // Add the byte order mark if the user wants it. + if (addBOM) + { + ret.push_back(unicode::BOM_ENCODE_UTF8[0]); + ret.push_back(unicode::BOM_ENCODE_UTF8[1]); + ret.push_back(unicode::BOM_ENCODE_UTF8[2]); + } + + while (!iter.atEnd()) + { + uchar32_t c = *iter; + if (c > 0xFFFF) + { // 4 bytes + uchar8_t b1 = (0x1E << 3) | ((c >> 18) & 0x7); + uchar8_t b2 = (0x2 << 6) | ((c >> 12) & 0x3F); + uchar8_t b3 = (0x2 << 6) | ((c >> 6) & 0x3F); + uchar8_t b4 = (0x2 << 6) | (c & 0x3F); + ret.push_back(b1); + ret.push_back(b2); + ret.push_back(b3); + ret.push_back(b4); + } + else if (c > 0x7FF) + { // 3 bytes + uchar8_t b1 = (0xE << 4) | ((c >> 12) & 0xF); + uchar8_t b2 = (0x2 << 6) | ((c >> 6) & 0x3F); + uchar8_t b3 = (0x2 << 6) | (c & 0x3F); + ret.push_back(b1); + ret.push_back(b2); + ret.push_back(b3); + } + else if (c > 0x7F) + { // 2 bytes + uchar8_t b1 = (0x6 << 5) | ((c >> 6) & 0x1F); + uchar8_t b2 = (0x2 << 6) | (c & 0x3F); + ret.push_back(b1); + ret.push_back(b2); + } + else + { // 1 byte + ret.push_back(static_cast(c)); + } + ++iter; + } + ret.push_back(0); + return ret; + } + + +#ifdef USTRING_CPP0X_NEWLITERALS // C++0x + //! Converts the string to a UTF-16 encoded string. + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return A string containing the UTF-16 encoded string. + core::string toUTF16_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + core::string ret; + ret.reserve(used + (addBOM ? unicode::BOM_UTF16_LEN : 0) + 1); + + // Add the BOM if specified. + if (addBOM) + { + if (endian == unicode::EUTFEE_NATIVE) + ret[0] = unicode::BOM; + else if (endian == unicode::EUTFEE_LITTLE) + { + uchar8_t* ptr8 = reinterpret_cast(ret.c_str()); + *ptr8++ = unicode::BOM_ENCODE_UTF16_LE[0]; + *ptr8 = unicode::BOM_ENCODE_UTF16_LE[1]; + } + else + { + uchar8_t* ptr8 = reinterpret_cast(ret.c_str()); + *ptr8++ = unicode::BOM_ENCODE_UTF16_BE[0]; + *ptr8 = unicode::BOM_ENCODE_UTF16_BE[1]; + } + } + + ret.append(array); + if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) + { + char16_t* ptr = ret.c_str(); + for (u32 i = 0; i < ret.size(); ++i) + *ptr++ = unicode::swapEndian16(*ptr); + } + return ret; + } +#endif + + + //! Converts the string to a UTF-16 encoded string array. + //! Unfortunately, no toUTF16_s() version exists due to limitations with Irrlicht's string class. + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return An array containing the UTF-16 encoded string. + core::array toUTF16(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + core::array ret(used + (addBOM ? unicode::BOM_UTF16_LEN : 0) + 1); + uchar16_t* ptr = ret.pointer(); + + // Add the BOM if specified. + if (addBOM) + { + if (endian == unicode::EUTFEE_NATIVE) + *ptr = unicode::BOM; + else if (endian == unicode::EUTFEE_LITTLE) + { + uchar8_t* ptr8 = reinterpret_cast(ptr); + *ptr8++ = unicode::BOM_ENCODE_UTF16_LE[0]; + *ptr8 = unicode::BOM_ENCODE_UTF16_LE[1]; + } + else + { + uchar8_t* ptr8 = reinterpret_cast(ptr); + *ptr8++ = unicode::BOM_ENCODE_UTF16_BE[0]; + *ptr8 = unicode::BOM_ENCODE_UTF16_BE[1]; + } + ++ptr; + } + + memcpy((void*)ptr, (void*)array, used * sizeof(uchar16_t)); + if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) + { + for (u32 i = 0; i <= used; ++i) + ptr[i] = unicode::swapEndian16(ptr[i]); + } + ret.set_used(used + (addBOM ? unicode::BOM_UTF16_LEN : 0)); + ret.push_back(0); + return ret; + } + + +#ifdef USTRING_CPP0X_NEWLITERALS // C++0x + //! Converts the string to a UTF-32 encoded string. + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return A string containing the UTF-32 encoded string. + core::string toUTF32_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + core::string ret; + ret.reserve(size() + 1 + (addBOM ? unicode::BOM_UTF32_LEN : 0)); + const_iterator iter(*this, 0); + + // Add the BOM if specified. + if (addBOM) + { + if (endian == unicode::EUTFEE_NATIVE) + ret.append(unicode::BOM); + else + { + union + { + uchar32_t full; + u8 chunk[4]; + } t; + + if (endian == unicode::EUTFEE_LITTLE) + { + t.chunk[0] = unicode::BOM_ENCODE_UTF32_LE[0]; + t.chunk[1] = unicode::BOM_ENCODE_UTF32_LE[1]; + t.chunk[2] = unicode::BOM_ENCODE_UTF32_LE[2]; + t.chunk[3] = unicode::BOM_ENCODE_UTF32_LE[3]; + } + else + { + t.chunk[0] = unicode::BOM_ENCODE_UTF32_BE[0]; + t.chunk[1] = unicode::BOM_ENCODE_UTF32_BE[1]; + t.chunk[2] = unicode::BOM_ENCODE_UTF32_BE[2]; + t.chunk[3] = unicode::BOM_ENCODE_UTF32_BE[3]; + } + ret.append(t.full); + } + } + + while (!iter.atEnd()) + { + uchar32_t c = *iter; + if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) + c = unicode::swapEndian32(c); + ret.append(c); + ++iter; + } + return ret; + } +#endif + + + //! Converts the string to a UTF-32 encoded string array. + //! Unfortunately, no toUTF32_s() version exists due to limitations with Irrlicht's string class. + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return An array containing the UTF-32 encoded string. + core::array toUTF32(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + core::array ret(size() + (addBOM ? unicode::BOM_UTF32_LEN : 0) + 1); + const_iterator iter(*this, 0); + + // Add the BOM if specified. + if (addBOM) + { + if (endian == unicode::EUTFEE_NATIVE) + ret.push_back(unicode::BOM); + else + { + union + { + uchar32_t full; + u8 chunk[4]; + } t; + + if (endian == unicode::EUTFEE_LITTLE) + { + t.chunk[0] = unicode::BOM_ENCODE_UTF32_LE[0]; + t.chunk[1] = unicode::BOM_ENCODE_UTF32_LE[1]; + t.chunk[2] = unicode::BOM_ENCODE_UTF32_LE[2]; + t.chunk[3] = unicode::BOM_ENCODE_UTF32_LE[3]; + } + else + { + t.chunk[0] = unicode::BOM_ENCODE_UTF32_BE[0]; + t.chunk[1] = unicode::BOM_ENCODE_UTF32_BE[1]; + t.chunk[2] = unicode::BOM_ENCODE_UTF32_BE[2]; + t.chunk[3] = unicode::BOM_ENCODE_UTF32_BE[3]; + } + ret.push_back(t.full); + } + } + ret.push_back(0); + + while (!iter.atEnd()) + { + uchar32_t c = *iter; + if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) + c = unicode::swapEndian32(c); + ret.push_back(c); + ++iter; + } + return ret; + } + + + //! Converts the string to a wchar_t encoded string. + /** The size of a wchar_t changes depending on the platform. This function will store a + correct UTF-8, -16, or -32 encoded string depending on the size of a wchar_t. **/ + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return A string containing the wchar_t encoded string. + core::string toWCHAR_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + if (sizeof(wchar_t) == 4) + { + core::array a(toUTF32(endian, addBOM)); + core::stringw ret(a.pointer()); + return ret; + } + else if (sizeof(wchar_t) == 2) + { + if (endian == unicode::EUTFEE_NATIVE && addBOM == false) + { + core::stringw ret(array); + return ret; + } + else + { + core::array a(toUTF16(endian, addBOM)); + core::stringw ret(a.pointer()); + return ret; + } + } + else if (sizeof(wchar_t) == 1) + { + core::array a(toUTF8(addBOM)); + core::stringw ret(a.pointer()); + return ret; + } + + // Shouldn't happen. + return core::stringw(); + } + + + //! Converts the string to a wchar_t encoded string array. + /** The size of a wchar_t changes depending on the platform. This function will store a + correct UTF-8, -16, or -32 encoded string depending on the size of a wchar_t. **/ + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return An array containing the wchar_t encoded string. + core::array toWCHAR(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + if (sizeof(wchar_t) == 4) + { + core::array a(toUTF32(endian, addBOM)); + core::array ret(a.size()); + ret.set_used(a.size()); + memcpy((void*)ret.pointer(), (void*)a.pointer(), a.size() * sizeof(uchar32_t)); + return ret; + } + if (sizeof(wchar_t) == 2) + { + if (endian == unicode::EUTFEE_NATIVE && addBOM == false) + { + core::array ret(used); + ret.set_used(used); + memcpy((void*)ret.pointer(), (void*)array, used * sizeof(uchar16_t)); + return ret; + } + else + { + core::array a(toUTF16(endian, addBOM)); + core::array ret(a.size()); + ret.set_used(a.size()); + memcpy((void*)ret.pointer(), (void*)a.pointer(), a.size() * sizeof(uchar16_t)); + return ret; + } + } + if (sizeof(wchar_t) == 1) + { + core::array a(toUTF8(addBOM)); + core::array ret(a.size()); + ret.set_used(a.size()); + memcpy((void*)ret.pointer(), (void*)a.pointer(), a.size() * sizeof(uchar8_t)); + return ret; + } + + // Shouldn't happen. + return core::array(); + } + + //! Converts the string to a properly encoded io::path string. + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return An io::path string containing the properly encoded string. + io::path toPATH_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { +#if defined(_IRR_WCHAR_FILESYSTEM) + return toWCHAR_s(endian, addBOM); +#else + return toUTF8_s(addBOM); +#endif + } + + //! Loads an unknown stream of data. + //! Will attempt to determine if the stream is unicode data. Useful for loading from files. + //! \param data The data stream to load from. + //! \param data_size The length of the data string. + //! \return A reference to our current string. + ustring16& loadDataStream(const char* data, size_t data_size) + { + // Clear our string. + *this = ""; + if (!data) + return *this; + + unicode::EUTF_ENCODE e = unicode::determineUnicodeBOM(data); + switch (e) + { + default: + case unicode::EUTFE_UTF8: + append((uchar8_t*)data, data_size); + break; + + case unicode::EUTFE_UTF16: + case unicode::EUTFE_UTF16_BE: + case unicode::EUTFE_UTF16_LE: + append((uchar16_t*)data, data_size / 2); + break; + + case unicode::EUTFE_UTF32: + case unicode::EUTFE_UTF32_BE: + case unicode::EUTFE_UTF32_LE: + append((uchar32_t*)data, data_size / 4); + break; + } + + return *this; + } + + //! Gets the encoding of the Unicode string this class contains. + //! \return An enum describing the current encoding of this string. + const unicode::EUTF_ENCODE getEncoding() const + { + return encoding; + } + + //! Gets the endianness of the Unicode string this class contains. + //! \return An enum describing the endianness of this string. + const unicode::EUTF_ENDIAN getEndianness() const + { + if (encoding == unicode::EUTFE_UTF16_LE || + encoding == unicode::EUTFE_UTF32_LE) + return unicode::EUTFEE_LITTLE; + else return unicode::EUTFEE_BIG; + } + +private: + + //! Reallocate the string, making it bigger or smaller. + //! \param new_size The new size of the string. + void reallocate(u32 new_size) + { + uchar16_t* old_array = array; + + array = allocator.allocate(new_size + 1); //new u16[new_size]; + allocated = new_size + 1; + if (old_array == 0) return; + + u32 amount = used < new_size ? used : new_size; + for (u32 i=0; i<=amount; ++i) + array[i] = old_array[i]; + + if (allocated <= used) + used = allocated - 1; + + array[used] = 0; + + allocator.deallocate(old_array); // delete [] old_array; + } + + //--- member variables + + uchar16_t* array; + unicode::EUTF_ENCODE encoding; + u32 allocated; + u32 used; + TAlloc allocator; + //irrAllocator allocator; +}; + +typedef ustring16 > ustring; + + +//! Appends two ustring16s. +template +inline ustring16 operator+(const ustring16& left, const ustring16& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a null-terminated unicode string. +template +inline ustring16 operator+(const ustring16& left, const B* const right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a null-terminated unicode string. +template +inline ustring16 operator+(const B* const left, const ustring16& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and an Irrlicht string. +template +inline ustring16 operator+(const ustring16& left, const string& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and an Irrlicht string. +template +inline ustring16 operator+(const string& left, const ustring16& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a std::basic_string. +template +inline ustring16 operator+(const ustring16& left, const std::basic_string& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a std::basic_string. +template +inline ustring16 operator+(const std::basic_string& left, const ustring16& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a char. +template +inline ustring16 operator+(const ustring16& left, const char right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a char. +template +inline ustring16 operator+(const char left, const ustring16& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +#ifdef USTRING_CPP0X_NEWLITERALS +//! Appends a ustring16 and a uchar32_t. +template +inline ustring16 operator+(const ustring16& left, const uchar32_t right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a uchar32_t. +template +inline ustring16 operator+(const uchar32_t left, const ustring16& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} +#endif + + +//! Appends a ustring16 and a short. +template +inline ustring16 operator+(const ustring16& left, const short right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} + + +//! Appends a ustring16 and a short. +template +inline ustring16 operator+(const short left, const ustring16& right) +{ + ustring16 ret((core::stringc(left))); + ret += right; + return ret; +} + + +//! Appends a ustring16 and an unsigned short. +template +inline ustring16 operator+(const ustring16& left, const unsigned short right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} + + +//! Appends a ustring16 and an unsigned short. +template +inline ustring16 operator+(const unsigned short left, const ustring16& right) +{ + ustring16 ret((core::stringc(left))); + ret += right; + return ret; +} + + +//! Appends a ustring16 and an int. +template +inline ustring16 operator+(const ustring16& left, const int right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} + + +//! Appends a ustring16 and an int. +template +inline ustring16 operator+(const int left, const ustring16& right) +{ + ustring16 ret((core::stringc(left))); + ret += right; + return ret; +} + + +//! Appends a ustring16 and an unsigned int. +template +inline ustring16 operator+(const ustring16& left, const unsigned int right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} + + +//! Appends a ustring16 and an unsigned int. +template +inline ustring16 operator+(const unsigned int left, const ustring16& right) +{ + ustring16 ret((core::stringc(left))); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a long. +template +inline ustring16 operator+(const ustring16& left, const long right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} + + +//! Appends a ustring16 and a long. +template +inline ustring16 operator+(const long left, const ustring16& right) +{ + ustring16 ret((core::stringc(left))); + ret += right; + return ret; +} + + +//! Appends a ustring16 and an unsigned long. +template +inline ustring16 operator+(const ustring16& left, const unsigned long right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} + + +//! Appends a ustring16 and an unsigned long. +template +inline ustring16 operator+(const unsigned long left, const ustring16& right) +{ + ustring16 ret((core::stringc(left))); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a float. +template +inline ustring16 operator+(const ustring16& left, const float right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} + + +//! Appends a ustring16 and a float. +template +inline ustring16 operator+(const float left, const ustring16& right) +{ + ustring16 ret((core::stringc(left))); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a double. +template +inline ustring16 operator+(const ustring16& left, const double right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} + + +//! Appends a ustring16 and a double. +template +inline ustring16 operator+(const double left, const ustring16& right) +{ + ustring16 ret((core::stringc(left))); + ret += right; + return ret; +} + + +#ifdef USTRING_CPP0X +//! Appends two ustring16s. +template +inline ustring16&& operator+(const ustring16& left, ustring16&& right) +{ + //std::cout << "MOVE operator+(&, &&)" << std::endl; + right.insert(left, 0); + return std::move(right); +} + + +//! Appends two ustring16s. +template +inline ustring16&& operator+(ustring16&& left, const ustring16& right) +{ + //std::cout << "MOVE operator+(&&, &)" << std::endl; + left.append(right); + return std::move(left); +} + + +//! Appends two ustring16s. +template +inline ustring16&& operator+(ustring16&& left, ustring16&& right) +{ + //std::cout << "MOVE operator+(&&, &&)" << std::endl; + if ((right.size_raw() <= left.capacity() - left.size_raw()) || + (right.capacity() - right.size_raw() < left.size_raw())) + { + left.append(right); + return std::move(left); + } + else + { + right.insert(left, 0); + return std::move(right); + } +} + + +//! Appends a ustring16 and a null-terminated unicode string. +template +inline ustring16&& operator+(ustring16&& left, const B* const right) +{ + //std::cout << "MOVE operator+(&&, B*)" << std::endl; + left.append(right); + return std::move(left); +} + + +//! Appends a ustring16 and a null-terminated unicode string. +template +inline ustring16&& operator+(const B* const left, ustring16&& right) +{ + //std::cout << "MOVE operator+(B*, &&)" << std::endl; + right.insert(left, 0); + return std::move(right); +} + + +//! Appends a ustring16 and an Irrlicht string. +template +inline ustring16&& operator+(const string& left, ustring16&& right) +{ + //std::cout << "MOVE operator+(&, &&)" << std::endl; + right.insert(left, 0); + return std::move(right); +} + + +//! Appends a ustring16 and an Irrlicht string. +template +inline ustring16&& operator+(ustring16&& left, const string& right) +{ + //std::cout << "MOVE operator+(&&, &)" << std::endl; + left.append(right); + return std::move(left); +} + + +//! Appends a ustring16 and a std::basic_string. +template +inline ustring16&& operator+(const std::basic_string& left, ustring16&& right) +{ + //std::cout << "MOVE operator+(&, &&)" << std::endl; + right.insert(core::ustring16(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and a std::basic_string. +template +inline ustring16&& operator+(ustring16&& left, const std::basic_string& right) +{ + //std::cout << "MOVE operator+(&&, &)" << std::endl; + left.append(right); + return std::move(left); +} + + +//! Appends a ustring16 and a char. +template +inline ustring16 operator+(ustring16&& left, const char right) +{ + left.append((uchar32_t)right); + return std::move(left); +} + + +//! Appends a ustring16 and a char. +template +inline ustring16 operator+(const char left, ustring16&& right) +{ + right.insert((uchar32_t)left, 0); + return std::move(right); +} + + +#ifdef USTRING_CPP0X_NEWLITERALS +//! Appends a ustring16 and a uchar32_t. +template +inline ustring16 operator+(ustring16&& left, const uchar32_t right) +{ + left.append(right); + return std::move(left); +} + + +//! Appends a ustring16 and a uchar32_t. +template +inline ustring16 operator+(const uchar32_t left, ustring16&& right) +{ + right.insert(left, 0); + return std::move(right); +} +#endif + + +//! Appends a ustring16 and a short. +template +inline ustring16 operator+(ustring16&& left, const short right) +{ + left.append(core::stringc(right)); + return std::move(left); +} + + +//! Appends a ustring16 and a short. +template +inline ustring16 operator+(const short left, ustring16&& right) +{ + right.insert(core::stringc(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and an unsigned short. +template +inline ustring16 operator+(ustring16&& left, const unsigned short right) +{ + left.append(core::stringc(right)); + return std::move(left); +} + + +//! Appends a ustring16 and an unsigned short. +template +inline ustring16 operator+(const unsigned short left, ustring16&& right) +{ + right.insert(core::stringc(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and an int. +template +inline ustring16 operator+(ustring16&& left, const int right) +{ + left.append(core::stringc(right)); + return std::move(left); +} + + +//! Appends a ustring16 and an int. +template +inline ustring16 operator+(const int left, ustring16&& right) +{ + right.insert(core::stringc(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and an unsigned int. +template +inline ustring16 operator+(ustring16&& left, const unsigned int right) +{ + left.append(core::stringc(right)); + return std::move(left); +} + + +//! Appends a ustring16 and an unsigned int. +template +inline ustring16 operator+(const unsigned int left, ustring16&& right) +{ + right.insert(core::stringc(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and a long. +template +inline ustring16 operator+(ustring16&& left, const long right) +{ + left.append(core::stringc(right)); + return std::move(left); +} + + +//! Appends a ustring16 and a long. +template +inline ustring16 operator+(const long left, ustring16&& right) +{ + right.insert(core::stringc(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and an unsigned long. +template +inline ustring16 operator+(ustring16&& left, const unsigned long right) +{ + left.append(core::stringc(right)); + return std::move(left); +} + + +//! Appends a ustring16 and an unsigned long. +template +inline ustring16 operator+(const unsigned long left, ustring16&& right) +{ + right.insert(core::stringc(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and a float. +template +inline ustring16 operator+(ustring16&& left, const float right) +{ + left.append(core::stringc(right)); + return std::move(left); +} + + +//! Appends a ustring16 and a float. +template +inline ustring16 operator+(const float left, ustring16&& right) +{ + right.insert(core::stringc(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and a double. +template +inline ustring16 operator+(ustring16&& left, const double right) +{ + left.append(core::stringc(right)); + return std::move(left); +} + + +//! Appends a ustring16 and a double. +template +inline ustring16 operator+(const double left, ustring16&& right) +{ + right.insert(core::stringc(left), 0); + return std::move(right); +} +#endif + + +#ifndef USTRING_NO_STL +//! Writes a ustring16 to an ostream. +template +inline std::ostream& operator<<(std::ostream& out, const ustring16& in) +{ + out << in.toUTF8_s().c_str(); + return out; +} + +//! Writes a ustring16 to a wostream. +template +inline std::wostream& operator<<(std::wostream& out, const ustring16& in) +{ + out << in.toWCHAR_s().c_str(); + return out; +} +#endif + + +#ifndef USTRING_NO_STL + +namespace unicode +{ + +//! Hashing algorithm for hashing a ustring. Used for things like unordered_maps. +//! Algorithm taken from std::hash. +class hash : public std::unary_function +{ + public: + size_t operator()(const core::ustring& s) const + { + size_t ret = 2166136261U; + size_t index = 0; + size_t stride = 1 + s.size_raw() / 10; + + core::ustring::const_iterator i = s.begin(); + while (i != s.end()) + { + // TODO: Don't force u32 on an x64 OS. Make it agnostic. + ret = 16777619U * ret ^ (size_t)s[(u32)index]; + index += stride; + i += stride; + } + return (ret); + } +}; + +} // end namespace unicode + +#endif + +} // end namespace core +} // end namespace irr + +#endif diff --git a/src/cguittfont/xCGUITTFont.cpp b/src/cguittfont/xCGUITTFont.cpp new file mode 100644 index 0000000..c51922e --- /dev/null +++ b/src/cguittfont/xCGUITTFont.cpp @@ -0,0 +1,5 @@ +// A wrapper source file to avoid hack with gcc and modifying +// the CGUITTFont files. + +#include "xCGUITTFont.h" +#include "CGUITTFont.cpp" diff --git a/src/cguittfont/xCGUITTFont.h b/src/cguittfont/xCGUITTFont.h new file mode 100644 index 0000000..c3efe7f --- /dev/null +++ b/src/cguittfont/xCGUITTFont.h @@ -0,0 +1,7 @@ +// A wrapper header to avoid hack with gcc and modifying +// the CGUITTFont files. + +#include +#include +#include "irrUString.h" +#include "CGUITTFont.h" diff --git a/src/chat.cpp b/src/chat.cpp new file mode 100644 index 0000000..2d29d39 --- /dev/null +++ b/src/chat.cpp @@ -0,0 +1,779 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#include "chat.h" +#include "debug.h" +#include "strfnd.h" +#include +#include +#include "util/string.h" +#include "util/numeric.h" + +ChatBuffer::ChatBuffer(u32 scrollback): + m_scrollback(scrollback), + m_unformatted(), + m_cols(0), + m_rows(0), + m_scroll(0), + m_formatted(), + m_empty_formatted_line() +{ + if (m_scrollback == 0) + m_scrollback = 1; + m_empty_formatted_line.first = true; +} + +ChatBuffer::~ChatBuffer() +{ +} + +void ChatBuffer::addLine(std::wstring name, std::wstring text) +{ + ChatLine line(name, text); + m_unformatted.push_back(line); + + if (m_rows > 0) + { + // m_formatted is valid and must be kept valid + bool scrolled_at_bottom = (m_scroll == getBottomScrollPos()); + u32 num_added = formatChatLine(line, m_cols, m_formatted); + if (scrolled_at_bottom) + m_scroll += num_added; + } + + // Limit number of lines by m_scrollback + if (m_unformatted.size() > m_scrollback) + { + deleteOldest(m_unformatted.size() - m_scrollback); + } +} + +void ChatBuffer::clear() +{ + m_unformatted.clear(); + m_formatted.clear(); + m_scroll = 0; +} + +u32 ChatBuffer::getLineCount() const +{ + return m_unformatted.size(); +} + +u32 ChatBuffer::getScrollback() const +{ + return m_scrollback; +} + +const ChatLine& ChatBuffer::getLine(u32 index) const +{ + assert(index < getLineCount()); // pre-condition + return m_unformatted[index]; +} + +void ChatBuffer::step(f32 dtime) +{ + for (u32 i = 0; i < m_unformatted.size(); ++i) + { + m_unformatted[i].age += dtime; + } +} + +void ChatBuffer::deleteOldest(u32 count) +{ + u32 del_unformatted = 0; + u32 del_formatted = 0; + + while (count > 0 && del_unformatted < m_unformatted.size()) + { + ++del_unformatted; + + // keep m_formatted in sync + if (del_formatted < m_formatted.size()) + { + + sanity_check(m_formatted[del_formatted].first); + ++del_formatted; + while (del_formatted < m_formatted.size() && + !m_formatted[del_formatted].first) + ++del_formatted; + } + + --count; + } + + m_unformatted.erase(m_unformatted.begin(), m_unformatted.begin() + del_unformatted); + m_formatted.erase(m_formatted.begin(), m_formatted.begin() + del_formatted); +} + +void ChatBuffer::deleteByAge(f32 maxAge) +{ + u32 count = 0; + while (count < m_unformatted.size() && m_unformatted[count].age > maxAge) + ++count; + deleteOldest(count); +} + +u32 ChatBuffer::getColumns() const +{ + return m_cols; +} + +u32 ChatBuffer::getRows() const +{ + return m_rows; +} + +void ChatBuffer::reformat(u32 cols, u32 rows) +{ + if (cols == 0 || rows == 0) + { + // Clear formatted buffer + m_cols = 0; + m_rows = 0; + m_scroll = 0; + m_formatted.clear(); + } + else if (cols != m_cols || rows != m_rows) + { + // TODO: Avoid reformatting ALL lines (even invisible ones) + // each time the console size changes. + + // Find out the scroll position in *unformatted* lines + u32 restore_scroll_unformatted = 0; + u32 restore_scroll_formatted = 0; + bool at_bottom = (m_scroll == getBottomScrollPos()); + if (!at_bottom) + { + for (s32 i = 0; i < m_scroll; ++i) + { + if (m_formatted[i].first) + ++restore_scroll_unformatted; + } + } + + // If number of columns change, reformat everything + if (cols != m_cols) + { + m_formatted.clear(); + for (u32 i = 0; i < m_unformatted.size(); ++i) + { + if (i == restore_scroll_unformatted) + restore_scroll_formatted = m_formatted.size(); + formatChatLine(m_unformatted[i], cols, m_formatted); + } + } + + // Update the console size + m_cols = cols; + m_rows = rows; + + // Restore the scroll position + if (at_bottom) + { + scrollBottom(); + } + else + { + scrollAbsolute(restore_scroll_formatted); + } + } +} + +const ChatFormattedLine& ChatBuffer::getFormattedLine(u32 row) const +{ + s32 index = m_scroll + (s32) row; + if (index >= 0 && index < (s32) m_formatted.size()) + return m_formatted[index]; + else + return m_empty_formatted_line; +} + +void ChatBuffer::scroll(s32 rows) +{ + scrollAbsolute(m_scroll + rows); +} + +void ChatBuffer::scrollAbsolute(s32 scroll) +{ + s32 top = getTopScrollPos(); + s32 bottom = getBottomScrollPos(); + + m_scroll = scroll; + if (m_scroll < top) + m_scroll = top; + if (m_scroll > bottom) + m_scroll = bottom; +} + +void ChatBuffer::scrollBottom() +{ + m_scroll = getBottomScrollPos(); +} + +void ChatBuffer::scrollTop() +{ + m_scroll = getTopScrollPos(); +} + +u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, + std::vector& destination) const +{ + u32 num_added = 0; + std::vector next_frags; + ChatFormattedLine next_line; + ChatFormattedFragment temp_frag; + u32 out_column = 0; + u32 in_pos = 0; + u32 hanging_indentation = 0; + + // Format the sender name and produce fragments + if (!line.name.empty()) + { + temp_frag.text = L"<"; + temp_frag.column = 0; + //temp_frag.bold = 0; + next_frags.push_back(temp_frag); + temp_frag.text = line.name; + temp_frag.column = 0; + //temp_frag.bold = 1; + next_frags.push_back(temp_frag); + temp_frag.text = L"> "; + temp_frag.column = 0; + //temp_frag.bold = 0; + next_frags.push_back(temp_frag); + } + + // Choose an indentation level + if (line.name.empty()) + { + // Server messages + hanging_indentation = 0; + } + else if (line.name.size() + 3 <= cols/2) + { + // Names shorter than about half the console width + hanging_indentation = line.name.size() + 3; + } + else + { + // Very long names + hanging_indentation = 2; + } + + next_line.first = true; + bool text_processing = false; + + // Produce fragments and layout them into lines + while (!next_frags.empty() || in_pos < line.text.size()) + { + // Layout fragments into lines + while (!next_frags.empty()) + { + ChatFormattedFragment& frag = next_frags[0]; + if (frag.text.size() <= cols - out_column) + { + // Fragment fits into current line + frag.column = out_column; + next_line.fragments.push_back(frag); + out_column += frag.text.size(); + next_frags.erase(next_frags.begin()); + } + else + { + // Fragment does not fit into current line + // So split it up + temp_frag.text = frag.text.substr(0, cols - out_column); + temp_frag.column = out_column; + //temp_frag.bold = frag.bold; + next_line.fragments.push_back(temp_frag); + frag.text = frag.text.substr(cols - out_column); + out_column = cols; + } + if (out_column == cols || text_processing) + { + // End the current line + destination.push_back(next_line); + num_added++; + next_line.fragments.clear(); + next_line.first = false; + + out_column = text_processing ? hanging_indentation : 0; + } + } + + // Produce fragment + if (in_pos < line.text.size()) + { + u32 remaining_in_input = line.text.size() - in_pos; + u32 remaining_in_output = cols - out_column; + + // Determine a fragment length <= the minimum of + // remaining_in_{in,out}put. Try to end the fragment + // on a word boundary. + u32 frag_length = 1, space_pos = 0; + while (frag_length < remaining_in_input && + frag_length < remaining_in_output) + { + if (isspace(line.text[in_pos + frag_length])) + space_pos = frag_length; + ++frag_length; + } + if (space_pos != 0 && frag_length < remaining_in_input) + frag_length = space_pos + 1; + + temp_frag.text = line.text.substr(in_pos, frag_length); + temp_frag.column = 0; + //temp_frag.bold = 0; + next_frags.push_back(temp_frag); + in_pos += frag_length; + text_processing = true; + } + } + + // End the last line + if (num_added == 0 || !next_line.fragments.empty()) + { + destination.push_back(next_line); + num_added++; + } + + return num_added; +} + +s32 ChatBuffer::getTopScrollPos() const +{ + s32 formatted_count = (s32) m_formatted.size(); + s32 rows = (s32) m_rows; + if (rows == 0) + return 0; + else if (formatted_count <= rows) + return formatted_count - rows; + else + return 0; +} + +s32 ChatBuffer::getBottomScrollPos() const +{ + s32 formatted_count = (s32) m_formatted.size(); + s32 rows = (s32) m_rows; + if (rows == 0) + return 0; + else + return formatted_count - rows; +} + + + +ChatPrompt::ChatPrompt(std::wstring prompt, u32 history_limit): + m_prompt(prompt), + m_line(L""), + m_history(), + m_history_index(0), + m_history_limit(history_limit), + m_cols(0), + m_view(0), + m_cursor(0), + m_nick_completion_start(0), + m_nick_completion_end(0) +{ +} + +ChatPrompt::~ChatPrompt() +{ +} + +void ChatPrompt::input(wchar_t ch) +{ + m_line.insert(m_cursor, 1, ch); + m_cursor++; + clampView(); + m_nick_completion_start = 0; + m_nick_completion_end = 0; +} + +void ChatPrompt::input(const std::wstring &str) +{ + m_line.insert(m_cursor, str); + m_cursor += str.size(); + clampView(); + m_nick_completion_start = 0; + m_nick_completion_end = 0; +} + +std::wstring ChatPrompt::submit() +{ + std::wstring line = m_line; + m_line.clear(); + if (!line.empty()) + m_history.push_back(line); + if (m_history.size() > m_history_limit) + m_history.erase(m_history.begin()); + m_history_index = m_history.size(); + m_view = 0; + m_cursor = 0; + m_nick_completion_start = 0; + m_nick_completion_end = 0; + return line; +} + +void ChatPrompt::clear() +{ + m_line.clear(); + m_view = 0; + m_cursor = 0; + m_nick_completion_start = 0; + m_nick_completion_end = 0; +} + +void ChatPrompt::replace(std::wstring line) +{ + m_line = line; + m_view = m_cursor = line.size(); + clampView(); + m_nick_completion_start = 0; + m_nick_completion_end = 0; +} + +void ChatPrompt::historyPrev() +{ + if (m_history_index != 0) + { + --m_history_index; + replace(m_history[m_history_index]); + } +} + +void ChatPrompt::historyNext() +{ + if (m_history_index + 1 >= m_history.size()) + { + m_history_index = m_history.size(); + replace(L""); + } + else + { + ++m_history_index; + replace(m_history[m_history_index]); + } +} + +void ChatPrompt::nickCompletion(const std::list& names, bool backwards) +{ + // Two cases: + // (a) m_nick_completion_start == m_nick_completion_end == 0 + // Then no previous nick completion is active. + // Get the word around the cursor and replace with any nick + // that has that word as a prefix. + // (b) else, continue a previous nick completion. + // m_nick_completion_start..m_nick_completion_end are the + // interval where the originally used prefix was. Cycle + // through the list of completions of that prefix. + u32 prefix_start = m_nick_completion_start; + u32 prefix_end = m_nick_completion_end; + bool initial = (prefix_end == 0); + if (initial) + { + // no previous nick completion is active + prefix_start = prefix_end = m_cursor; + while (prefix_start > 0 && !isspace(m_line[prefix_start-1])) + --prefix_start; + while (prefix_end < m_line.size() && !isspace(m_line[prefix_end])) + ++prefix_end; + if (prefix_start == prefix_end) + return; + } + std::wstring prefix = m_line.substr(prefix_start, prefix_end - prefix_start); + + // find all names that start with the selected prefix + std::vector completions; + for (std::list::const_iterator + i = names.begin(); + i != names.end(); ++i) + { + if (str_starts_with(narrow_to_wide(*i), prefix, true)) + { + std::wstring completion = narrow_to_wide(*i); + if (prefix_start == 0) + completion += L":"; + completions.push_back(completion); + } + } + if (completions.empty()) + return; + + // find a replacement string and the word that will be replaced + u32 word_end = prefix_end; + u32 replacement_index = 0; + if (!initial) + { + while (word_end < m_line.size() && !isspace(m_line[word_end])) + ++word_end; + std::wstring word = m_line.substr(prefix_start, word_end - prefix_start); + + // cycle through completions + for (u32 i = 0; i < completions.size(); ++i) + { + if (str_equal(word, completions[i], true)) + { + if (backwards) + replacement_index = i + completions.size() - 1; + else + replacement_index = i + 1; + replacement_index %= completions.size(); + break; + } + } + } + std::wstring replacement = completions[replacement_index] + L" "; + if (word_end < m_line.size() && isspace(word_end)) + ++word_end; + + // replace existing word with replacement word, + // place the cursor at the end and record the completion prefix + m_line.replace(prefix_start, word_end - prefix_start, replacement); + m_cursor = prefix_start + replacement.size(); + clampView(); + m_nick_completion_start = prefix_start; + m_nick_completion_end = prefix_end; +} + +void ChatPrompt::reformat(u32 cols) +{ + if (cols <= m_prompt.size()) + { + m_cols = 0; + m_view = m_cursor; + } + else + { + s32 length = m_line.size(); + bool was_at_end = (m_view + m_cols >= length + 1); + m_cols = cols - m_prompt.size(); + if (was_at_end) + m_view = length; + clampView(); + } +} + +std::wstring ChatPrompt::getVisiblePortion() const +{ + return m_prompt + m_line.substr(m_view, m_cols); +} + +s32 ChatPrompt::getVisibleCursorPosition() const +{ + return m_cursor - m_view + m_prompt.size(); +} + +void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope) +{ + s32 old_cursor = m_cursor; + s32 new_cursor = m_cursor; + + s32 length = m_line.size(); + s32 increment = (dir == CURSOROP_DIR_RIGHT) ? 1 : -1; + + if (scope == CURSOROP_SCOPE_CHARACTER) + { + new_cursor += increment; + } + else if (scope == CURSOROP_SCOPE_WORD) + { + if (increment > 0) + { + // skip one word to the right + while (new_cursor < length && isspace(m_line[new_cursor])) + new_cursor++; + while (new_cursor < length && !isspace(m_line[new_cursor])) + new_cursor++; + while (new_cursor < length && isspace(m_line[new_cursor])) + new_cursor++; + } + else + { + // skip one word to the left + while (new_cursor >= 1 && isspace(m_line[new_cursor - 1])) + new_cursor--; + while (new_cursor >= 1 && !isspace(m_line[new_cursor - 1])) + new_cursor--; + } + } + else if (scope == CURSOROP_SCOPE_LINE) + { + new_cursor += increment * length; + } + + new_cursor = MYMAX(MYMIN(new_cursor, length), 0); + + if (op == CURSOROP_MOVE) + { + m_cursor = new_cursor; + } + else if (op == CURSOROP_DELETE) + { + if (new_cursor < old_cursor) + { + m_line.erase(new_cursor, old_cursor - new_cursor); + m_cursor = new_cursor; + } + else if (new_cursor > old_cursor) + { + m_line.erase(old_cursor, new_cursor - old_cursor); + m_cursor = old_cursor; + } + } + + clampView(); + + m_nick_completion_start = 0; + m_nick_completion_end = 0; +} + +void ChatPrompt::clampView() +{ + s32 length = m_line.size(); + if (length + 1 <= m_cols) + { + m_view = 0; + } + else + { + m_view = MYMIN(m_view, length + 1 - m_cols); + m_view = MYMIN(m_view, m_cursor); + m_view = MYMAX(m_view, m_cursor - m_cols + 1); + m_view = MYMAX(m_view, 0); + } +} + + + +ChatBackend::ChatBackend(): + m_console_buffer(500), + m_recent_buffer(6), + m_prompt(L"]", 500) +{ +} + +ChatBackend::~ChatBackend() +{ +} + +void ChatBackend::addMessage(std::wstring name, std::wstring text) +{ + // Note: A message may consist of multiple lines, for example the MOTD. + WStrfnd fnd(text); + while (!fnd.atend()) + { + std::wstring line = fnd.next(L"\n"); + m_console_buffer.addLine(name, line); + m_recent_buffer.addLine(name, line); + } +} + +void ChatBackend::addUnparsedMessage(std::wstring message) +{ + // TODO: Remove the need to parse chat messages client-side, by sending + // separate name and text fields in TOCLIENT_CHAT_MESSAGE. + + if (message.size() >= 2 && message[0] == L'<') + { + std::size_t closing = message.find_first_of(L'>', 1); + if (closing != std::wstring::npos && + closing + 2 <= message.size() && + message[closing+1] == L' ') + { + std::wstring name = message.substr(1, closing - 1); + std::wstring text = message.substr(closing + 2); + addMessage(name, text); + return; + } + } + + // Unable to parse, probably a server message. + addMessage(L"", message); +} + +ChatBuffer& ChatBackend::getConsoleBuffer() +{ + return m_console_buffer; +} + +ChatBuffer& ChatBackend::getRecentBuffer() +{ + return m_recent_buffer; +} + +std::wstring ChatBackend::getRecentChat() +{ + std::wostringstream stream; + for (u32 i = 0; i < m_recent_buffer.getLineCount(); ++i) + { + const ChatLine& line = m_recent_buffer.getLine(i); + if (i != 0) + stream << L"\n"; + if (!line.name.empty()) + stream << L"<" << line.name << L"> "; + stream << line.text; + } + return stream.str(); +} + +ChatPrompt& ChatBackend::getPrompt() +{ + return m_prompt; +} + +void ChatBackend::reformat(u32 cols, u32 rows) +{ + m_console_buffer.reformat(cols, rows); + + // no need to reformat m_recent_buffer, its formatted lines + // are not used + + m_prompt.reformat(cols); +} + +void ChatBackend::clearRecentChat() +{ + m_recent_buffer.clear(); +} + +void ChatBackend::step(float dtime) +{ + m_recent_buffer.step(dtime); + m_recent_buffer.deleteByAge(60.0); + + // no need to age messages in anything but m_recent_buffer +} + +void ChatBackend::scroll(s32 rows) +{ + m_console_buffer.scroll(rows); +} + +void ChatBackend::scrollPageDown() +{ + m_console_buffer.scroll(m_console_buffer.getRows()); +} + +void ChatBackend::scrollPageUp() +{ + m_console_buffer.scroll(-(s32)m_console_buffer.getRows()); +} diff --git a/src/chat.h b/src/chat.h new file mode 100644 index 0000000..82ce808 --- /dev/null +++ b/src/chat.h @@ -0,0 +1,275 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef CHAT_HEADER +#define CHAT_HEADER + +#include "irrlichttypes.h" +#include +#include +#include + +// Chat console related classes, only used by the client + +struct ChatLine +{ + // age in seconds + f32 age; + // name of sending player, or empty if sent by server + std::wstring name; + // message text + std::wstring text; + + ChatLine(std::wstring a_name, std::wstring a_text): + age(0.0), + name(a_name), + text(a_text) + { + } +}; + +struct ChatFormattedFragment +{ + // text string + std::wstring text; + // starting column + u32 column; + // formatting + //u8 bold:1; +}; + +struct ChatFormattedLine +{ + // Array of text fragments + std::vector fragments; + // true if first line of one formatted ChatLine + bool first; +}; + +class ChatBuffer +{ +public: + ChatBuffer(u32 scrollback); + ~ChatBuffer(); + + // Append chat line + // Removes oldest chat line if scrollback size is reached + void addLine(std::wstring name, std::wstring text); + + // Remove all chat lines + void clear(); + + // Get number of lines currently in buffer. + u32 getLineCount() const; + // Get scrollback size, maximum number of lines in buffer. + u32 getScrollback() const; + // Get reference to i-th chat line. + const ChatLine& getLine(u32 index) const; + + // Increase each chat line's age by dtime. + void step(f32 dtime); + // Delete oldest N chat lines. + void deleteOldest(u32 count); + // Delete lines older than maxAge. + void deleteByAge(f32 maxAge); + + // Get number of columns, 0 if reformat has not been called yet. + u32 getColumns() const; + // Get number of rows, 0 if reformat has not been called yet. + u32 getRows() const; + // Update console size and reformat all formatted lines. + void reformat(u32 cols, u32 rows); + // Get formatted line for a given row (0 is top of screen). + // Only valid after reformat has been called at least once + const ChatFormattedLine& getFormattedLine(u32 row) const; + // Scrolling in formatted buffer (relative) + // positive rows == scroll up, negative rows == scroll down + void scroll(s32 rows); + // Scrolling in formatted buffer (absolute) + void scrollAbsolute(s32 scroll); + // Scroll to bottom of buffer (newest) + void scrollBottom(); + // Scroll to top of buffer (oldest) + void scrollTop(); + + // Format a chat line for the given number of columns. + // Appends the formatted lines to the destination array and + // returns the number of formatted lines. + u32 formatChatLine(const ChatLine& line, u32 cols, + std::vector& destination) const; + +protected: + s32 getTopScrollPos() const; + s32 getBottomScrollPos() const; + +private: + // Scrollback size + u32 m_scrollback; + // Array of unformatted chat lines + std::vector m_unformatted; + + // Number of character columns in console + u32 m_cols; + // Number of character rows in console + u32 m_rows; + // Scroll position (console's top line index into m_formatted) + s32 m_scroll; + // Array of formatted lines + std::vector m_formatted; + // Empty formatted line, for error returns + ChatFormattedLine m_empty_formatted_line; +}; + +class ChatPrompt +{ +public: + ChatPrompt(std::wstring prompt, u32 history_limit); + ~ChatPrompt(); + + // Input character or string + void input(wchar_t ch); + void input(const std::wstring &str); + + // Submit, clear and return current line + std::wstring submit(); + + // Clear the current line + void clear(); + + // Replace the current line with the given text + void replace(std::wstring line); + + // Select previous command from history + void historyPrev(); + // Select next command from history + void historyNext(); + + // Nick completion + void nickCompletion(const std::list& names, bool backwards); + + // Update console size and reformat the visible portion of the prompt + void reformat(u32 cols); + // Get visible portion of the prompt. + std::wstring getVisiblePortion() const; + // Get cursor position (relative to visible portion). -1 if invalid + s32 getVisibleCursorPosition() const; + + // Cursor operations + enum CursorOp { + CURSOROP_MOVE, + CURSOROP_DELETE + }; + + // Cursor operation direction + enum CursorOpDir { + CURSOROP_DIR_LEFT, + CURSOROP_DIR_RIGHT + }; + + // Cursor operation scope + enum CursorOpScope { + CURSOROP_SCOPE_CHARACTER, + CURSOROP_SCOPE_WORD, + CURSOROP_SCOPE_LINE + }; + + // Cursor operation + // op specifies whether it's a move or delete operation + // dir specifies whether the operation goes left or right + // scope specifies how far the operation will reach (char/word/line) + // Examples: + // cursorOperation(CURSOROP_MOVE, CURSOROP_DIR_RIGHT, CURSOROP_SCOPE_LINE) + // moves the cursor to the end of the line. + // cursorOperation(CURSOROP_DELETE, CURSOROP_DIR_LEFT, CURSOROP_SCOPE_WORD) + // deletes the word to the left of the cursor. + void cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope); + +protected: + // set m_view to ensure that 0 <= m_view <= m_cursor < m_view + m_cols + // if line can be fully shown, set m_view to zero + // else, also ensure m_view <= m_line.size() + 1 - m_cols + void clampView(); + +private: + // Prompt prefix + std::wstring m_prompt; + // Currently edited line + std::wstring m_line; + // History buffer + std::vector m_history; + // History index (0 <= m_history_index <= m_history.size()) + u32 m_history_index; + // Maximum number of history entries + u32 m_history_limit; + + // Number of columns excluding columns reserved for the prompt + s32 m_cols; + // Start of visible portion (index into m_line) + s32 m_view; + // Cursor (index into m_line) + s32 m_cursor; + + // Last nick completion start (index into m_line) + s32 m_nick_completion_start; + // Last nick completion start (index into m_line) + s32 m_nick_completion_end; +}; + +class ChatBackend +{ +public: + ChatBackend(); + ~ChatBackend(); + + // Add chat message + void addMessage(std::wstring name, std::wstring text); + // Parse and add unparsed chat message + void addUnparsedMessage(std::wstring line); + + // Get the console buffer + ChatBuffer& getConsoleBuffer(); + // Get the recent messages buffer + ChatBuffer& getRecentBuffer(); + // Concatenate all recent messages + std::wstring getRecentChat(); + // Get the console prompt + ChatPrompt& getPrompt(); + + // Reformat all buffers + void reformat(u32 cols, u32 rows); + + // Clear all recent messages + void clearRecentChat(); + + // Age recent messages + void step(float dtime); + + // Scrolling + void scroll(s32 rows); + void scrollPageDown(); + void scrollPageUp(); + +private: + ChatBuffer m_console_buffer; + ChatBuffer m_recent_buffer; + ChatPrompt m_prompt; +}; + +#endif + diff --git a/src/client.cpp b/src/client.cpp new file mode 100644 index 0000000..7d2fab1 --- /dev/null +++ b/src/client.cpp @@ -0,0 +1,1748 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#include +#include +#include +#include +#include "jthread/jmutexautolock.h" +#include "util/directiontables.h" +#include "util/pointedthing.h" +#include "util/serialize.h" +#include "util/string.h" +#include "client.h" +#include "network/clientopcodes.h" +#include "main.h" +#include "filesys.h" +#include "porting.h" +#include "mapblock_mesh.h" +#include "mapblock.h" +#include "settings.h" +#include "profiler.h" +#include "gettext.h" +#include "log.h" +#include "nodemetadata.h" +#include "itemdef.h" +#include "shader.h" +#include "clientmap.h" +#include "clientmedia.h" +#include "sound.h" +#include "IMeshCache.h" +#include "config.h" +#include "version.h" +#include "drawscene.h" +#include "database-sqlite3.h" +#include "serialization.h" + +extern gui::IGUIEnvironment* guienv; + +/* + QueuedMeshUpdate +*/ + +QueuedMeshUpdate::QueuedMeshUpdate(): + p(-1337,-1337,-1337), + data(NULL), + ack_block_to_server(false) +{ +} + +QueuedMeshUpdate::~QueuedMeshUpdate() +{ + if(data) + delete data; +} + +/* + MeshUpdateQueue +*/ + +MeshUpdateQueue::MeshUpdateQueue() +{ +} + +MeshUpdateQueue::~MeshUpdateQueue() +{ + JMutexAutoLock lock(m_mutex); + + for(std::vector::iterator + i = m_queue.begin(); + i != m_queue.end(); i++) + { + QueuedMeshUpdate *q = *i; + delete q; + } +} + +/* + peer_id=0 adds with nobody to send to +*/ +void MeshUpdateQueue::addBlock(v3s16 p, MeshMakeData *data, bool ack_block_to_server, bool urgent) +{ + DSTACK(__FUNCTION_NAME); + + assert(data); // pre-condition + + JMutexAutoLock lock(m_mutex); + + if(urgent) + m_urgents.insert(p); + + /* + Find if block is already in queue. + If it is, update the data and quit. + */ + for(std::vector::iterator + i = m_queue.begin(); + i != m_queue.end(); i++) + { + QueuedMeshUpdate *q = *i; + if(q->p == p) + { + if(q->data) + delete q->data; + q->data = data; + if(ack_block_to_server) + q->ack_block_to_server = true; + return; + } + } + + /* + Add the block + */ + QueuedMeshUpdate *q = new QueuedMeshUpdate; + q->p = p; + q->data = data; + q->ack_block_to_server = ack_block_to_server; + m_queue.push_back(q); +} + +// Returned pointer must be deleted +// Returns NULL if queue is empty +QueuedMeshUpdate * MeshUpdateQueue::pop() +{ + JMutexAutoLock lock(m_mutex); + + bool must_be_urgent = !m_urgents.empty(); + for(std::vector::iterator + i = m_queue.begin(); + i != m_queue.end(); i++) + { + QueuedMeshUpdate *q = *i; + if(must_be_urgent && m_urgents.count(q->p) == 0) + continue; + m_queue.erase(i); + m_urgents.erase(q->p); + return q; + } + return NULL; +} + +/* + MeshUpdateThread +*/ + +void * MeshUpdateThread::Thread() +{ + ThreadStarted(); + + log_register_thread("MeshUpdateThread"); + + DSTACK(__FUNCTION_NAME); + + BEGIN_DEBUG_EXCEPTION_HANDLER + + porting::setThreadName("MeshUpdateThread"); + + while(!StopRequested()) + { + QueuedMeshUpdate *q = m_queue_in.pop(); + if(q == NULL) + { + sleep_ms(3); + continue; + } + + ScopeProfiler sp(g_profiler, "Client: Mesh making"); + + MapBlockMesh *mesh_new = new MapBlockMesh(q->data, m_camera_offset); + if(mesh_new->getMesh()->getMeshBufferCount() == 0) + { + delete mesh_new; + mesh_new = NULL; + } + + MeshUpdateResult r; + r.p = q->p; + r.mesh = mesh_new; + r.ack_block_to_server = q->ack_block_to_server; + + m_queue_out.push_back(r); + + delete q; + } + + END_DEBUG_EXCEPTION_HANDLER(errorstream) + + return NULL; +} + +/* + Client +*/ + +Client::Client( + IrrlichtDevice *device, + const char *playername, + std::string password, + MapDrawControl &control, + IWritableTextureSource *tsrc, + IWritableShaderSource *shsrc, + IWritableItemDefManager *itemdef, + IWritableNodeDefManager *nodedef, + ISoundManager *sound, + MtEventManager *event, + bool ipv6 +): + m_packetcounter_timer(0.0), + m_connection_reinit_timer(0.1), + m_avg_rtt_timer(0.0), + m_playerpos_send_timer(0.0), + m_ignore_damage_timer(0.0), + m_tsrc(tsrc), + m_shsrc(shsrc), + m_itemdef(itemdef), + m_nodedef(nodedef), + m_sound(sound), + m_event(event), + m_mesh_update_thread(this), + m_env( + new ClientMap(this, this, control, + device->getSceneManager()->getRootSceneNode(), + device->getSceneManager(), 666), + device->getSceneManager(), + tsrc, this, device + ), + m_particle_manager(&m_env), + m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, ipv6, this), + m_device(device), + m_server_ser_ver(SER_FMT_VER_INVALID), + m_playeritem(0), + m_inventory_updated(false), + m_inventory_from_server(NULL), + m_inventory_from_server_age(0.0), + m_show_highlighted(false), + m_animation_time(0), + m_crack_level(-1), + m_crack_pos(0,0,0), + m_highlighted_pos(0,0,0), + m_map_seed(0), + m_password(password), + m_access_denied(false), + m_itemdef_received(false), + m_nodedef_received(false), + m_media_downloader(new ClientMediaDownloader()), + m_time_of_day_set(false), + m_last_time_of_day_f(-1), + m_time_of_day_update_timer(0), + m_recommended_send_interval(0.1), + m_removed_sounds_check_timer(0), + m_state(LC_Created), + m_localdb(NULL) +{ + // Add local player + m_env.addPlayer(new LocalPlayer(this, playername)); + + m_cache_save_interval = g_settings->getU16("server_map_save_interval"); + + m_cache_smooth_lighting = g_settings->getBool("smooth_lighting"); + m_cache_enable_shaders = g_settings->getBool("enable_shaders"); +} + +void Client::Stop() +{ + //request all client managed threads to stop + m_mesh_update_thread.Stop(); + // Save local server map + if (m_localdb) { + infostream << "Local map saving ended." << std::endl; + m_localdb->endSave(); + } +} + +bool Client::isShutdown() +{ + + if (!m_mesh_update_thread.IsRunning()) return true; + + return false; +} + +Client::~Client() +{ + m_con.Disconnect(); + + m_mesh_update_thread.Stop(); + m_mesh_update_thread.Wait(); + while(!m_mesh_update_thread.m_queue_out.empty()) { + MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx(); + delete r.mesh; + } + + + delete m_inventory_from_server; + + // Delete detached inventories + for(std::map::iterator + i = m_detached_inventories.begin(); + i != m_detached_inventories.end(); i++){ + delete i->second; + } + + // cleanup 3d model meshes on client shutdown + while (m_device->getSceneManager()->getMeshCache()->getMeshCount() != 0) { + scene::IAnimatedMesh * mesh = + m_device->getSceneManager()->getMeshCache()->getMeshByIndex(0); + + if (mesh != NULL) + m_device->getSceneManager()->getMeshCache()->removeMesh(mesh); + } +} + +void Client::connect(Address address, + const std::string &address_name, + bool is_local_server) +{ + DSTACK(__FUNCTION_NAME); + + initLocalMapSaving(address, address_name, is_local_server); + + m_con.SetTimeoutMs(0); + m_con.Connect(address); +} + +void Client::step(float dtime) +{ + DSTACK(__FUNCTION_NAME); + + // Limit a bit + if(dtime > 2.0) + dtime = 2.0; + + if(m_ignore_damage_timer > dtime) + m_ignore_damage_timer -= dtime; + else + m_ignore_damage_timer = 0.0; + + m_animation_time += dtime; + if(m_animation_time > 60.0) + m_animation_time -= 60.0; + + m_time_of_day_update_timer += dtime; + + ReceiveAll(); + + /* + Packet counter + */ + { + float &counter = m_packetcounter_timer; + counter -= dtime; + if(counter <= 0.0) + { + counter = 20.0; + + infostream << "Client packetcounter (" << m_packetcounter_timer + << "):"<getName()); + snprintf(pPassword, PASSWORD_SIZE, "%s", m_password.c_str()); + + NetworkPacket pkt(TOSERVER_INIT_LEGACY, + 1 + PLAYERNAME_SIZE + PASSWORD_SIZE + 2 + 2); + + pkt << (u8) SER_FMT_VER_HIGHEST_READ; + pkt.putRawString(pName,PLAYERNAME_SIZE); + pkt.putRawString(pPassword, PASSWORD_SIZE); + pkt << (u16) CLIENT_PROTOCOL_VERSION_MIN << (u16) CLIENT_PROTOCOL_VERSION_MAX; + + Send(&pkt); + } + + // Not connected, return + return; + } + + /* + Do stuff if connected + */ + + /* + Run Map's timers and unload unused data + */ + const float map_timer_and_unload_dtime = 5.25; + if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) { + ScopeProfiler sp(g_profiler, "Client: map timer and unload"); + std::vector deleted_blocks; + m_env.getMap().timerUpdate(map_timer_and_unload_dtime, + g_settings->getFloat("client_unload_unused_data_timeout"), + &deleted_blocks); + + /* + Send info to server + NOTE: This loop is intentionally iterated the way it is. + */ + + std::vector::iterator i = deleted_blocks.begin(); + std::vector sendlist; + for(;;) { + if(sendlist.size() == 255 || i == deleted_blocks.end()) { + if(sendlist.empty()) + break; + /* + [0] u16 command + [2] u8 count + [3] v3s16 pos_0 + [3+6] v3s16 pos_1 + ... + */ + NetworkPacket pkt(TOSERVER_DELETEDBLOCKS, 1 + sizeof(v3s16) * sendlist.size()); + + pkt << (u8) sendlist.size(); + + u32 k = 0; + for(std::vector::iterator + j = sendlist.begin(); + j != sendlist.end(); ++j) { + pkt << *j; + k++; + } + + Send(&pkt); + + if(i == deleted_blocks.end()) + break; + + sendlist.clear(); + } + + sendlist.push_back(*i); + ++i; + } + } + + /* + Handle environment + */ + // Control local player (0ms) + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player != NULL); + player->applyControl(dtime); + + // Step environment + m_env.step(dtime); + + /* + Get events + */ + for(;;) { + ClientEnvEvent event = m_env.getClientEvent(); + if(event.type == CEE_NONE) { + break; + } + else if(event.type == CEE_PLAYER_DAMAGE) { + if(m_ignore_damage_timer <= 0) { + u8 damage = event.player_damage.amount; + + if(event.player_damage.send_to_server) + sendDamage(damage); + + // Add to ClientEvent queue + ClientEvent event; + event.type = CE_PLAYER_DAMAGE; + event.player_damage.amount = damage; + m_client_event_queue.push(event); + } + } + else if(event.type == CEE_PLAYER_BREATH) { + u16 breath = event.player_breath.amount; + sendBreath(breath); + } + } + + /* + Print some info + */ + float &counter = m_avg_rtt_timer; + counter += dtime; + if(counter >= 10) { + counter = 0.0; + // connectedAndInitialized() is true, peer exists. + float avg_rtt = getRTT(); + infostream << "Client: avg_rtt=" << avg_rtt << std::endl; + } + + /* + Send player position to server + */ + { + float &counter = m_playerpos_send_timer; + counter += dtime; + if((m_state == LC_Ready) && (counter >= m_recommended_send_interval)) + { + counter = 0.0; + sendPlayerPos(); + } + } + + /* + Replace updated meshes + */ + { + int num_processed_meshes = 0; + while(!m_mesh_update_thread.m_queue_out.empty()) + { + num_processed_meshes++; + MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx(); + MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(r.p); + if(block) { + // Delete the old mesh + if(block->mesh != NULL) + { + // TODO: Remove hardware buffers of meshbuffers of block->mesh + delete block->mesh; + block->mesh = NULL; + } + + // Replace with the new mesh + block->mesh = r.mesh; + } else { + delete r.mesh; + } + + if(r.ack_block_to_server) { + /* + Acknowledge block + [0] u8 count + [1] v3s16 pos_0 + */ + NetworkPacket pkt(TOSERVER_GOTBLOCKS, 1 + 6); + pkt << (u8) 1 << r.p; + Send(&pkt); + } + } + + if(num_processed_meshes > 0) + g_profiler->graphAdd("num_processed_meshes", num_processed_meshes); + } + + /* + Load fetched media + */ + if (m_media_downloader && m_media_downloader->isStarted()) { + m_media_downloader->step(this); + if (m_media_downloader->isDone()) { + received_media(); + delete m_media_downloader; + m_media_downloader = NULL; + } + } + + /* + If the server didn't update the inventory in a while, revert + the local inventory (so the player notices the lag problem + and knows something is wrong). + */ + if(m_inventory_from_server) + { + float interval = 10.0; + float count_before = floor(m_inventory_from_server_age / interval); + + m_inventory_from_server_age += dtime; + + float count_after = floor(m_inventory_from_server_age / interval); + + if(count_after != count_before) + { + // Do this every seconds after TOCLIENT_INVENTORY + // Reset the locally changed inventory to the authoritative inventory + Player *player = m_env.getLocalPlayer(); + player->inventory = *m_inventory_from_server; + m_inventory_updated = true; + } + } + + /* + Update positions of sounds attached to objects + */ + { + for(std::map::iterator + i = m_sounds_to_objects.begin(); + i != m_sounds_to_objects.end(); i++) + { + int client_id = i->first; + u16 object_id = i->second; + ClientActiveObject *cao = m_env.getActiveObject(object_id); + if(!cao) + continue; + v3f pos = cao->getPosition(); + m_sound->updateSoundPosition(client_id, pos); + } + } + + /* + Handle removed remotely initiated sounds + */ + m_removed_sounds_check_timer += dtime; + if(m_removed_sounds_check_timer >= 2.32) { + m_removed_sounds_check_timer = 0; + // Find removed sounds and clear references to them + std::set removed_server_ids; + for(std::map::iterator + i = m_sounds_server_to_client.begin(); + i != m_sounds_server_to_client.end();) { + s32 server_id = i->first; + int client_id = i->second; + i++; + if(!m_sound->soundExists(client_id)) { + m_sounds_server_to_client.erase(server_id); + m_sounds_client_to_server.erase(client_id); + m_sounds_to_objects.erase(client_id); + removed_server_ids.insert(server_id); + } + } + + // Sync to server + if(!removed_server_ids.empty()) { + size_t server_ids = removed_server_ids.size(); + assert(server_ids <= 0xFFFF); + + NetworkPacket pkt(TOSERVER_REMOVED_SOUNDS, 2 + server_ids * 4); + + pkt << (u16) (server_ids & 0xFFFF); + + for(std::set::iterator i = removed_server_ids.begin(); + i != removed_server_ids.end(); i++) + pkt << *i; + + Send(&pkt); + } + } + + // Write server map + if (m_localdb && m_localdb_save_interval.step(dtime, + m_cache_save_interval)) { + m_localdb->endSave(); + m_localdb->beginSave(); + } +} + +bool Client::loadMedia(const std::string &data, const std::string &filename) +{ + // Silly irrlicht's const-incorrectness + Buffer data_rw(data.c_str(), data.size()); + + std::string name; + + const char *image_ext[] = { + ".png", ".jpg", ".bmp", ".tga", + ".pcx", ".ppm", ".psd", ".wal", ".rgb", + NULL + }; + name = removeStringEnd(filename, image_ext); + if(name != "") + { + verbosestream<<"Client: Attempting to load image " + <<"file \""<getFileSystem(); + video::IVideoDriver *vdrv = m_device->getVideoDriver(); + + // Create an irrlicht memory file + io::IReadFile *rfile = irrfs->createMemoryReadFile( + *data_rw, data_rw.getSize(), "_tempreadfile"); + + FATAL_ERROR_IF(!rfile, "Could not create irrlicht memory file."); + + // Read image + video::IImage *img = vdrv->createImageFromFile(rfile); + if(!img){ + errorstream<<"Client: Cannot create image from data of " + <<"file \""<drop(); + return false; + } + else { + m_tsrc->insertSourceImage(filename, img); + img->drop(); + rfile->drop(); + return true; + } + } + + const char *sound_ext[] = { + ".0.ogg", ".1.ogg", ".2.ogg", ".3.ogg", ".4.ogg", + ".5.ogg", ".6.ogg", ".7.ogg", ".8.ogg", ".9.ogg", + ".ogg", NULL + }; + name = removeStringEnd(filename, sound_ext); + if(name != "") + { + verbosestream<<"Client: Attempting to load sound " + <<"file \""<loadSoundData(name, data); + return true; + } + + const char *model_ext[] = { + ".x", ".b3d", ".md2", ".obj", + NULL + }; + name = removeStringEnd(filename, model_ext); + if(name != "") + { + verbosestream<<"Client: Storing model into memory: " + <<"\""<id=" + <id< &file_requests) +{ + std::ostringstream os(std::ios_base::binary); + writeU16(os, TOSERVER_REQUEST_MEDIA); + size_t file_requests_size = file_requests.size(); + + FATAL_ERROR_IF(file_requests_size > 0xFFFF, "Unsupported number of file requests"); + + // Packet dynamicly resized + NetworkPacket pkt(TOSERVER_REQUEST_MEDIA, 2 + 0); + + pkt << (u16) (file_requests_size & 0xFFFF); + + for(std::vector::const_iterator i = file_requests.begin(); + i != file_requests.end(); ++i) { + pkt << (*i); + } + + Send(&pkt); + + infostream << "Client: Sending media request list to server (" + << file_requests.size() << " files. packet size)" << std::endl; +} + +void Client::received_media() +{ + NetworkPacket pkt(TOSERVER_RECEIVED_MEDIA, 0); + Send(&pkt); + infostream << "Client: Notifying server that we received all media" + << std::endl; +} + +void Client::initLocalMapSaving(const Address &address, + const std::string &hostname, + bool is_local_server) +{ + if (!g_settings->getBool("enable_local_map_saving") || is_local_server) { + return; + } + + const std::string world_path = porting::path_user + + DIR_DELIM + "worlds" + + DIR_DELIM + "server_" + + hostname + "_" + to_string(address.getPort()); + + fs::CreateAllDirs(world_path); + + m_localdb = new Database_SQLite3(world_path); + m_localdb->beginSave(); + actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl; +} + +void Client::ReceiveAll() +{ + DSTACK(__FUNCTION_NAME); + u32 start_ms = porting::getTimeMs(); + for(;;) + { + // Limit time even if there would be huge amounts of data to + // process + if(porting::getTimeMs() > start_ms + 100) + break; + + try { + Receive(); + g_profiler->graphAdd("client_received_packets", 1); + } + catch(con::NoIncomingDataException &e) { + break; + } + catch(con::InvalidIncomingDataException &e) { + infostream<<"Client::ReceiveAll(): " + "InvalidIncomingDataException: what()=" + < data; + u16 sender_peer_id; + u32 datasize = m_con.Receive(sender_peer_id, data); + ProcessData(*data, datasize, sender_peer_id); +} + +inline void Client::handleCommand(NetworkPacket* pkt) +{ + const ToClientCommandHandler& opHandle = toClientCommandTable[pkt->getCommand()]; + (this->*opHandle.handler)(pkt); +} + +/* + sender_peer_id given to this shall be quaranteed to be a valid peer +*/ +void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id) +{ + DSTACK(__FUNCTION_NAME); + + // Ignore packets that don't even fit a command + if(datasize < 2) { + m_packetcounter.add(60000); + return; + } + + NetworkPacket pkt(data, datasize, sender_peer_id); + + ToClientCommand command = (ToClientCommand) pkt.getCommand(); + + //infostream<<"Client: received command="< &fields) +{ + size_t fields_size = fields.size(); + + FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of nodemeta fields"); + + NetworkPacket pkt(TOSERVER_NODEMETA_FIELDS, 0); + + pkt << p << formname << (u16) (fields_size & 0xFFFF); + + for(std::map::const_iterator + i = fields.begin(); i != fields.end(); i++) { + const std::string &name = i->first; + const std::string &value = i->second; + pkt << name; + pkt.putLongString(value); + } + + Send(&pkt); +} + +void Client::sendInventoryFields(const std::string &formname, + const std::map &fields) +{ + size_t fields_size = fields.size(); + FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of inventory fields"); + + NetworkPacket pkt(TOSERVER_INVENTORY_FIELDS, 0); + pkt << formname << (u16) (fields_size & 0xFFFF); + + for(std::map::const_iterator + i = fields.begin(); i != fields.end(); i++) { + const std::string &name = i->first; + const std::string &value = i->second; + pkt << name; + pkt.putLongString(value); + } + + Send(&pkt); +} + +void Client::sendInventoryAction(InventoryAction *a) +{ + std::ostringstream os(std::ios_base::binary); + + a->serialize(os); + + // Make data buffer + std::string s = os.str(); + + NetworkPacket pkt(TOSERVER_INVENTORY_ACTION, s.size()); + pkt.putRawString(s.c_str(),s.size()); + + Send(&pkt); +} + +void Client::sendChatMessage(const std::wstring &message) +{ + NetworkPacket pkt(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16)); + + pkt << message; + + Send(&pkt); +} + +void Client::sendChangePassword(const std::wstring &oldpassword, + const std::wstring &newpassword) +{ + Player *player = m_env.getLocalPlayer(); + if(player == NULL) + return; + + std::string playername = player->getName(); + std::string oldpwd = translatePassword(playername, oldpassword); + std::string newpwd = translatePassword(playername, newpassword); + + NetworkPacket pkt(TOSERVER_PASSWORD_LEGACY, 2 * PASSWORD_SIZE); + + for(u8 i = 0; i < PASSWORD_SIZE; i++) { + pkt << (u8) (i < oldpwd.length() ? oldpwd[i] : 0); + } + + for(u8 i = 0; i < PASSWORD_SIZE; i++) { + pkt << (u8) (i < newpwd.length() ? newpwd[i] : 0); + } + + Send(&pkt); +} + + +void Client::sendDamage(u8 damage) +{ + DSTACK(__FUNCTION_NAME); + + NetworkPacket pkt(TOSERVER_DAMAGE, sizeof(u8)); + pkt << damage; + Send(&pkt); +} + +void Client::sendBreath(u16 breath) +{ + DSTACK(__FUNCTION_NAME); + + NetworkPacket pkt(TOSERVER_BREATH, sizeof(u16)); + pkt << breath; + Send(&pkt); +} + +void Client::sendRespawn() +{ + DSTACK(__FUNCTION_NAME); + + NetworkPacket pkt(TOSERVER_RESPAWN, 0); + Send(&pkt); +} + +void Client::sendReady() +{ + DSTACK(__FUNCTION_NAME); + + NetworkPacket pkt(TOSERVER_CLIENT_READY, + 1 + 1 + 1 + 1 + 2 + sizeof(char) * strlen(minetest_version_hash)); + + pkt << (u8) VERSION_MAJOR << (u8) VERSION_MINOR << (u8) VERSION_PATCH_ORIG + << (u8) 0 << (u16) strlen(minetest_version_hash); + + pkt.putRawString(minetest_version_hash, (u16) strlen(minetest_version_hash)); + Send(&pkt); +} + +void Client::sendPlayerPos() +{ + LocalPlayer *myplayer = m_env.getLocalPlayer(); + if(myplayer == NULL) + return; + + // Save bandwidth by only updating position when something changed + if(myplayer->last_position == myplayer->getPosition() && + myplayer->last_speed == myplayer->getSpeed() && + myplayer->last_pitch == myplayer->getPitch() && + myplayer->last_yaw == myplayer->getYaw() && + myplayer->last_keyPressed == myplayer->keyPressed) + return; + + myplayer->last_position = myplayer->getPosition(); + myplayer->last_speed = myplayer->getSpeed(); + myplayer->last_pitch = myplayer->getPitch(); + myplayer->last_yaw = myplayer->getYaw(); + myplayer->last_keyPressed = myplayer->keyPressed; + + u16 our_peer_id; + { + //JMutexAutoLock lock(m_con_mutex); //bulk comment-out + our_peer_id = m_con.GetPeerID(); + } + + // Set peer id if not set already + if(myplayer->peer_id == PEER_ID_INEXISTENT) + myplayer->peer_id = our_peer_id; + + assert(myplayer->peer_id == our_peer_id); + + v3f pf = myplayer->getPosition(); + v3f sf = myplayer->getSpeed(); + s32 pitch = myplayer->getPitch() * 100; + s32 yaw = myplayer->getYaw() * 100; + u32 keyPressed = myplayer->keyPressed; + + v3s32 position(pf.X*100, pf.Y*100, pf.Z*100); + v3s32 speed(sf.X*100, sf.Y*100, sf.Z*100); + /* + Format: + [0] v3s32 position*100 + [12] v3s32 speed*100 + [12+12] s32 pitch*100 + [12+12+4] s32 yaw*100 + [12+12+4+4] u32 keyPressed + */ + + NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4); + + pkt << position << speed << pitch << yaw << keyPressed; + + Send(&pkt); +} + +void Client::sendPlayerItem(u16 item) +{ + Player *myplayer = m_env.getLocalPlayer(); + if(myplayer == NULL) + return; + + u16 our_peer_id = m_con.GetPeerID(); + + // Set peer id if not set already + if(myplayer->peer_id == PEER_ID_INEXISTENT) + myplayer->peer_id = our_peer_id; + assert(myplayer->peer_id == our_peer_id); + + NetworkPacket pkt(TOSERVER_PLAYERITEM, 2); + + pkt << item; + + Send(&pkt); +} + +void Client::removeNode(v3s16 p) +{ + std::map modified_blocks; + + try { + m_env.getMap().removeNodeAndUpdate(p, modified_blocks); + } + catch(InvalidPositionException &e) { + } + + for(std::map::iterator + i = modified_blocks.begin(); + i != modified_blocks.end(); ++i) { + addUpdateMeshTaskWithEdge(i->first, false, true); + } +} + +void Client::addNode(v3s16 p, MapNode n, bool remove_metadata) +{ + //TimeTaker timer1("Client::addNode()"); + + std::map modified_blocks; + + try { + //TimeTaker timer3("Client::addNode(): addNodeAndUpdate"); + m_env.getMap().addNodeAndUpdate(p, n, modified_blocks, remove_metadata); + } + catch(InvalidPositionException &e) { + } + + for(std::map::iterator + i = modified_blocks.begin(); + i != modified_blocks.end(); ++i) { + addUpdateMeshTaskWithEdge(i->first, false, true); + } +} + +void Client::setPlayerControl(PlayerControl &control) +{ + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player != NULL); + player->control = control; +} + +void Client::selectPlayerItem(u16 item) +{ + m_playeritem = item; + m_inventory_updated = true; + sendPlayerItem(item); +} + +// Returns true if the inventory of the local player has been +// updated from the server. If it is true, it is set to false. +bool Client::getLocalInventoryUpdated() +{ + bool updated = m_inventory_updated; + m_inventory_updated = false; + return updated; +} + +// Copies the inventory of the local player to parameter +void Client::getLocalInventory(Inventory &dst) +{ + Player *player = m_env.getLocalPlayer(); + assert(player != NULL); + dst = player->inventory; +} + +Inventory* Client::getInventory(const InventoryLocation &loc) +{ + switch(loc.type){ + case InventoryLocation::UNDEFINED: + {} + break; + case InventoryLocation::CURRENT_PLAYER: + { + Player *player = m_env.getLocalPlayer(); + assert(player != NULL); + return &player->inventory; + } + break; + case InventoryLocation::PLAYER: + { + Player *player = m_env.getPlayer(loc.name.c_str()); + if(!player) + return NULL; + return &player->inventory; + } + break; + case InventoryLocation::NODEMETA: + { + NodeMetadata *meta = m_env.getMap().getNodeMetadata(loc.p); + if(!meta) + return NULL; + return meta->getInventory(); + } + break; + case InventoryLocation::DETACHED: + { + if(m_detached_inventories.count(loc.name) == 0) + return NULL; + return m_detached_inventories[loc.name]; + } + break; + default: + FATAL_ERROR("Invalid inventory location type."); + break; + } + return NULL; +} + +void Client::inventoryAction(InventoryAction *a) +{ + /* + Send it to the server + */ + sendInventoryAction(a); + + /* + Predict some local inventory changes + */ + a->clientApply(this, this); + + // Remove it + delete a; +} + +ClientActiveObject * Client::getSelectedActiveObject( + f32 max_d, + v3f from_pos_f_on_map, + core::line3d shootline_on_map + ) +{ + std::vector objects; + + m_env.getActiveObjects(from_pos_f_on_map, max_d, objects); + + // Sort them. + // After this, the closest object is the first in the array. + std::sort(objects.begin(), objects.end()); + + for(unsigned int i=0; i *selection_box = obj->getSelectionBox(); + if(selection_box == NULL) + continue; + + v3f pos = obj->getPosition(); + + core::aabbox3d offsetted_box( + selection_box->MinEdge + pos, + selection_box->MaxEdge + pos + ); + + if(offsetted_box.intersectsWithLine(shootline_on_map)) + { + return obj; + } + } + + return NULL; +} + +std::list Client::getConnectedPlayerNames() +{ + return m_env.getPlayerNames(); +} + +float Client::getAnimationTime() +{ + return m_animation_time; +} + +int Client::getCrackLevel() +{ + return m_crack_level; +} + +void Client::setHighlighted(v3s16 pos, bool show_highlighted) +{ + m_show_highlighted = show_highlighted; + v3s16 old_highlighted_pos = m_highlighted_pos; + m_highlighted_pos = pos; + addUpdateMeshTaskForNode(old_highlighted_pos, false, true); + addUpdateMeshTaskForNode(m_highlighted_pos, false, true); +} + +void Client::setCrack(int level, v3s16 pos) +{ + int old_crack_level = m_crack_level; + v3s16 old_crack_pos = m_crack_pos; + + m_crack_level = level; + m_crack_pos = pos; + + if(old_crack_level >= 0 && (level < 0 || pos != old_crack_pos)) + { + // remove old crack + addUpdateMeshTaskForNode(old_crack_pos, false, true); + } + if(level >= 0 && (old_crack_level < 0 || pos != old_crack_pos)) + { + // add new crack + addUpdateMeshTaskForNode(pos, false, true); + } +} + +u16 Client::getHP() +{ + Player *player = m_env.getLocalPlayer(); + assert(player != NULL); + return player->hp; +} + +u16 Client::getBreath() +{ + Player *player = m_env.getLocalPlayer(); + assert(player != NULL); + return player->getBreath(); +} + +bool Client::getChatMessage(std::wstring &message) +{ + if(m_chat_queue.size() == 0) + return false; + message = m_chat_queue.front(); + m_chat_queue.pop(); + return true; +} + +void Client::typeChatMessage(const std::wstring &message) +{ + // Discard empty line + if(message == L"") + return; + + // Send to others + sendChatMessage(message); + + // Show locally + if (message[0] == L'/') + { + m_chat_queue.push((std::wstring)L"issued command: " + message); + } + else + { + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player != NULL); + std::wstring name = narrow_to_wide(player->getName()); + m_chat_queue.push((std::wstring)L"<" + name + L"> " + message); + } +} + +void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server, bool urgent) +{ + MapBlock *b = m_env.getMap().getBlockNoCreateNoEx(p); + if(b == NULL) + return; + + /* + Create a task to update the mesh of the block + */ + + MeshMakeData *data = new MeshMakeData(this, m_cache_enable_shaders); + + { + //TimeTaker timer("data fill"); + // Release: ~0ms + // Debug: 1-6ms, avg=2ms + data->fill(b); + data->setCrack(m_crack_level, m_crack_pos); + data->setHighlighted(m_highlighted_pos, m_show_highlighted); + data->setSmoothLighting(m_cache_smooth_lighting); + } + + // Add task to queue + m_mesh_update_thread.m_queue_in.addBlock(p, data, ack_to_server, urgent); +} + +void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent) +{ + try{ + addUpdateMeshTask(blockpos, ack_to_server, urgent); + } + catch(InvalidPositionException &e){} + + // Leading edge + for (int i=0;i<6;i++) + { + try{ + v3s16 p = blockpos + g_6dirs[i]; + addUpdateMeshTask(p, false, urgent); + } + catch(InvalidPositionException &e){} + } +} + +void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool urgent) +{ + { + v3s16 p = nodepos; + infostream<<"Client::addUpdateMeshTaskForNode(): " + <<"("<getProgress(); + else + return 1.0; // downloader only exists when not yet done +} + +void Client::afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font) +{ + infostream<<"Client::afterContentReceived() started"<rebuildImagesAndTextures(); + delete[] text; + + // Rebuild shaders + infostream<<"- Rebuilding shaders"<rebuildShaders(); + delete[] text; + + // Update node aliases + infostream<<"- Updating node aliases"<updateAliases(m_itemdef); + m_nodedef->setNodeRegistrationStatus(true); + m_nodedef->runNodeResolverCallbacks(); + delete[] text; + + // Update node textures and assign shaders to each tile + infostream<<"- Updating node textures"<updateTextures(this); + + // Preload item textures and meshes if configured to + if(g_settings->getBool("preload_item_visuals")) + { + verbosestream<<"Updating item textures and meshes"< names = m_itemdef->getAll(); + size_t size = names.size(); + size_t count = 0; + int percent = 0; + for(std::set::const_iterator + i = names.begin(); i != names.end(); ++i) + { + // Asking for these caches the result + m_itemdef->getInventoryTexture(*i, this); + m_itemdef->getWieldMesh(*i, this); + count++; + percent = (count * 100 / size * 0.2) + 80; + draw_load_screen(text, device, guienv, 0, percent); + } + delete[] text; + } + + // Start mesh update thread after setting up content definitions + infostream<<"- Starting mesh update thread"<getVideoDriver(); + irr::video::IImage* const raw_image = driver->createScreenShot(); + if (raw_image) { + irr::video::IImage* const image = driver->createImage(video::ECF_R8G8B8, + raw_image->getDimension()); + + if (image) { + raw_image->copyTo(image); + irr::c8 filename[256]; + snprintf(filename, sizeof(filename), + (std::string("%s") + DIR_DELIM + "screenshot_%u.png").c_str(), + g_settings->get("screenshot_path").c_str(), + device->getTimer()->getRealTime()); + std::ostringstream sstr; + if (driver->writeImageToFile(image, filename)) { + sstr << "Saved screenshot to '" << filename << "'"; + } else { + sstr << "Failed to save screenshot '" << filename << "'"; + } + m_chat_queue.push(narrow_to_wide(sstr.str())); + infostream << sstr.str() << std::endl; + image->drop(); + } + raw_image->drop(); + } +} + +// IGameDef interface +// Under envlock +IItemDefManager* Client::getItemDefManager() +{ + return m_itemdef; +} +INodeDefManager* Client::getNodeDefManager() +{ + return m_nodedef; +} +ICraftDefManager* Client::getCraftDefManager() +{ + return NULL; + //return m_craftdef; +} +ITextureSource* Client::getTextureSource() +{ + return m_tsrc; +} +IShaderSource* Client::getShaderSource() +{ + return m_shsrc; +} +scene::ISceneManager* Client::getSceneManager() +{ + return m_device->getSceneManager(); +} +u16 Client::allocateUnknownNodeId(const std::string &name) +{ + errorstream << "Client::allocateUnknownNodeId(): " + << "Client cannot allocate node IDs" << std::endl; + FATAL_ERROR("Client allocated unknown node"); + + return CONTENT_IGNORE; +} +ISoundManager* Client::getSoundManager() +{ + return m_sound; +} +MtEventManager* Client::getEventManager() +{ + return m_event; +} + +ParticleManager* Client::getParticleManager() +{ + return &m_particle_manager; +} + +scene::IAnimatedMesh* Client::getMesh(const std::string &filename) +{ + std::map::const_iterator i = + m_mesh_data.find(filename); + if(i == m_mesh_data.end()){ + errorstream<<"Client::getMesh(): Mesh not found: \""<second; + scene::ISceneManager *smgr = m_device->getSceneManager(); + + // Create the mesh, remove it from cache and return it + // This allows unique vertex colors and other properties for each instance + Buffer data_rw(data.c_str(), data.size()); // Const-incorrect Irrlicht + io::IFileSystem *irrfs = m_device->getFileSystem(); + io::IReadFile *rfile = irrfs->createMemoryReadFile( + *data_rw, data_rw.getSize(), filename.c_str()); + FATAL_ERROR_IF(!rfile, "Could not create/open RAM file"); + + scene::IAnimatedMesh *mesh = smgr->getMesh(rfile); + rfile->drop(); + // NOTE: By playing with Irrlicht refcounts, maybe we could cache a bunch + // of uniquely named instances and re-use them + mesh->grab(); + smgr->getMeshCache()->removeMesh(mesh); + return mesh; +} diff --git a/src/client.h b/src/client.h new file mode 100644 index 0000000..9baa034 --- /dev/null +++ b/src/client.h @@ -0,0 +1,633 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef CLIENT_HEADER +#define CLIENT_HEADER + +#include "network/connection.h" +#include "environment.h" +#include "irrlichttypes_extrabloated.h" +#include "jthread/jmutex.h" +#include +#include +#include +#include +#include "clientobject.h" +#include "gamedef.h" +#include "inventorymanager.h" +#include "localplayer.h" +#include "hud.h" +#include "particles.h" +#include "network/networkpacket.h" + +struct MeshMakeData; +class MapBlockMesh; +class IWritableTextureSource; +class IWritableShaderSource; +class IWritableItemDefManager; +class IWritableNodeDefManager; +//class IWritableCraftDefManager; +class ClientMediaDownloader; +struct MapDrawControl; +class MtEventManager; +struct PointedThing; +class Database; + +struct QueuedMeshUpdate +{ + v3s16 p; + MeshMakeData *data; + bool ack_block_to_server; + + QueuedMeshUpdate(); + ~QueuedMeshUpdate(); +}; + +enum LocalClientState { + LC_Created, + LC_Init, + LC_Ready +}; + +/* + A thread-safe queue of mesh update tasks +*/ +class MeshUpdateQueue +{ +public: + MeshUpdateQueue(); + + ~MeshUpdateQueue(); + + /* + peer_id=0 adds with nobody to send to + */ + void addBlock(v3s16 p, MeshMakeData *data, + bool ack_block_to_server, bool urgent); + + // Returned pointer must be deleted + // Returns NULL if queue is empty + QueuedMeshUpdate * pop(); + + u32 size() + { + JMutexAutoLock lock(m_mutex); + return m_queue.size(); + } + +private: + std::vector m_queue; + std::set m_urgents; + JMutex m_mutex; +}; + +struct MeshUpdateResult +{ + v3s16 p; + MapBlockMesh *mesh; + bool ack_block_to_server; + + MeshUpdateResult(): + p(-1338,-1338,-1338), + mesh(NULL), + ack_block_to_server(false) + { + } +}; + +class MeshUpdateThread : public JThread +{ +public: + + MeshUpdateThread(IGameDef *gamedef): + m_gamedef(gamedef) + { + } + + void * Thread(); + + MeshUpdateQueue m_queue_in; + + MutexedQueue m_queue_out; + + IGameDef *m_gamedef; + + v3s16 m_camera_offset; +}; + +enum ClientEventType +{ + CE_NONE, + CE_PLAYER_DAMAGE, + CE_PLAYER_FORCE_MOVE, + CE_DEATHSCREEN, + CE_SHOW_FORMSPEC, + CE_SPAWN_PARTICLE, + CE_ADD_PARTICLESPAWNER, + CE_DELETE_PARTICLESPAWNER, + CE_HUDADD, + CE_HUDRM, + CE_HUDCHANGE, + CE_SET_SKY, + CE_OVERRIDE_DAY_NIGHT_RATIO, +}; + +struct ClientEvent +{ + ClientEventType type; + union{ + //struct{ + //} none; + struct{ + u8 amount; + } player_damage; + struct{ + f32 pitch; + f32 yaw; + } player_force_move; + struct{ + bool set_camera_point_target; + f32 camera_point_target_x; + f32 camera_point_target_y; + f32 camera_point_target_z; + } deathscreen; + struct{ + std::string *formspec; + std::string *formname; + } show_formspec; + //struct{ + //} textures_updated; + struct{ + v3f *pos; + v3f *vel; + v3f *acc; + f32 expirationtime; + f32 size; + bool collisiondetection; + bool vertical; + std::string *texture; + } spawn_particle; + struct{ + u16 amount; + f32 spawntime; + v3f *minpos; + v3f *maxpos; + v3f *minvel; + v3f *maxvel; + v3f *minacc; + v3f *maxacc; + f32 minexptime; + f32 maxexptime; + f32 minsize; + f32 maxsize; + bool collisiondetection; + bool vertical; + std::string *texture; + u32 id; + } add_particlespawner; + struct{ + u32 id; + } delete_particlespawner; + struct{ + u32 id; + u8 type; + v2f *pos; + std::string *name; + v2f *scale; + std::string *text; + u32 number; + u32 item; + u32 dir; + v2f *align; + v2f *offset; + v3f *world_pos; + v2s32 * size; + } hudadd; + struct{ + u32 id; + } hudrm; + struct{ + u32 id; + HudElementStat stat; + v2f *v2fdata; + std::string *sdata; + u32 data; + v3f *v3fdata; + v2s32 * v2s32data; + } hudchange; + struct{ + video::SColor *bgcolor; + std::string *type; + std::vector *params; + } set_sky; + struct{ + bool do_override; + float ratio_f; + } override_day_night_ratio; + }; +}; + +/* + Packet counter +*/ + +class PacketCounter +{ +public: + PacketCounter() + { + } + + void add(u16 command) + { + std::map::iterator n = m_packets.find(command); + if(n == m_packets.end()) + { + m_packets[command] = 1; + } + else + { + n->second++; + } + } + + void clear() + { + for(std::map::iterator + i = m_packets.begin(); + i != m_packets.end(); ++i) + { + i->second = 0; + } + } + + void print(std::ostream &o) + { + for(std::map::iterator + i = m_packets.begin(); + i != m_packets.end(); ++i) + { + o<<"cmd "<first + <<" count "<second + < m_packets; +}; + +class Client : public con::PeerHandler, public InventoryManager, public IGameDef +{ +public: + /* + NOTE: Nothing is thread-safe here. + */ + + Client( + IrrlichtDevice *device, + const char *playername, + std::string password, + MapDrawControl &control, + IWritableTextureSource *tsrc, + IWritableShaderSource *shsrc, + IWritableItemDefManager *itemdef, + IWritableNodeDefManager *nodedef, + ISoundManager *sound, + MtEventManager *event, + bool ipv6 + ); + + ~Client(); + + /* + request all threads managed by client to be stopped + */ + void Stop(); + + + bool isShutdown(); + + /* + The name of the local player should already be set when + calling this, as it is sent in the initialization. + */ + void connect(Address address, + const std::string &address_name, + bool is_local_server); + + /* + Stuff that references the environment is valid only as + long as this is not called. (eg. Players) + If this throws a PeerNotFoundException, the connection has + timed out. + */ + void step(float dtime); + + /* + * Command Handlers + */ + + void handleCommand(NetworkPacket* pkt); + + void handleCommand_Null(NetworkPacket* pkt) {}; + void handleCommand_Deprecated(NetworkPacket* pkt); + void handleCommand_Hello(NetworkPacket* pkt); + void handleCommand_AuthAccept(NetworkPacket* pkt); + void handleCommand_InitLegacy(NetworkPacket* pkt); + void handleCommand_AccessDenied(NetworkPacket* pkt); + void handleCommand_RemoveNode(NetworkPacket* pkt); + void handleCommand_AddNode(NetworkPacket* pkt); + void handleCommand_BlockData(NetworkPacket* pkt); + void handleCommand_Inventory(NetworkPacket* pkt); + void handleCommand_TimeOfDay(NetworkPacket* pkt); + void handleCommand_ChatMessage(NetworkPacket* pkt); + void handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt); + void handleCommand_ActiveObjectMessages(NetworkPacket* pkt); + void handleCommand_Movement(NetworkPacket* pkt); + void handleCommand_HP(NetworkPacket* pkt); + void handleCommand_Breath(NetworkPacket* pkt); + void handleCommand_MovePlayer(NetworkPacket* pkt); + void handleCommand_PlayerItem(NetworkPacket* pkt); + void handleCommand_DeathScreen(NetworkPacket* pkt); + void handleCommand_AnnounceMedia(NetworkPacket* pkt); + void handleCommand_Media(NetworkPacket* pkt); + void handleCommand_ToolDef(NetworkPacket* pkt); + void handleCommand_NodeDef(NetworkPacket* pkt); + void handleCommand_CraftItemDef(NetworkPacket* pkt); + void handleCommand_ItemDef(NetworkPacket* pkt); + void handleCommand_PlaySound(NetworkPacket* pkt); + void handleCommand_StopSound(NetworkPacket* pkt); + void handleCommand_Privileges(NetworkPacket* pkt); + void handleCommand_InventoryFormSpec(NetworkPacket* pkt); + void handleCommand_DetachedInventory(NetworkPacket* pkt); + void handleCommand_ShowFormSpec(NetworkPacket* pkt); + void handleCommand_SpawnParticle(NetworkPacket* pkt); + void handleCommand_AddParticleSpawner(NetworkPacket* pkt); + void handleCommand_DeleteParticleSpawner(NetworkPacket* pkt); + void handleCommand_HudAdd(NetworkPacket* pkt); + void handleCommand_HudRemove(NetworkPacket* pkt); + void handleCommand_HudChange(NetworkPacket* pkt); + void handleCommand_HudSetFlags(NetworkPacket* pkt); + void handleCommand_HudSetParam(NetworkPacket* pkt); + void handleCommand_HudSetSky(NetworkPacket* pkt); + void handleCommand_OverrideDayNightRatio(NetworkPacket* pkt); + void handleCommand_LocalPlayerAnimations(NetworkPacket* pkt); + void handleCommand_EyeOffset(NetworkPacket* pkt); + + void ProcessData(u8 *data, u32 datasize, u16 sender_peer_id); + + // Returns true if something was received + bool AsyncProcessPacket(); + bool AsyncProcessData(); + void Send(NetworkPacket* pkt); + + void interact(u8 action, const PointedThing& pointed); + + void sendNodemetaFields(v3s16 p, const std::string &formname, + const std::map &fields); + void sendInventoryFields(const std::string &formname, + const std::map &fields); + void sendInventoryAction(InventoryAction *a); + void sendChatMessage(const std::wstring &message); + void sendChangePassword(const std::wstring &oldpassword, + const std::wstring &newpassword); + void sendDamage(u8 damage); + void sendBreath(u16 breath); + void sendRespawn(); + void sendReady(); + + ClientEnvironment& getEnv() + { return m_env; } + + // Causes urgent mesh updates (unlike Map::add/removeNodeWithEvent) + void removeNode(v3s16 p); + void addNode(v3s16 p, MapNode n, bool remove_metadata = true); + + void setPlayerControl(PlayerControl &control); + + void selectPlayerItem(u16 item); + u16 getPlayerItem() const + { return m_playeritem; } + + // Returns true if the inventory of the local player has been + // updated from the server. If it is true, it is set to false. + bool getLocalInventoryUpdated(); + // Copies the inventory of the local player to parameter + void getLocalInventory(Inventory &dst); + + /* InventoryManager interface */ + Inventory* getInventory(const InventoryLocation &loc); + void inventoryAction(InventoryAction *a); + + // Gets closest object pointed by the shootline + // Returns NULL if not found + ClientActiveObject * getSelectedActiveObject( + f32 max_d, + v3f from_pos_f_on_map, + core::line3d shootline_on_map + ); + + std::list getConnectedPlayerNames(); + + float getAnimationTime(); + + int getCrackLevel(); + void setCrack(int level, v3s16 pos); + + void setHighlighted(v3s16 pos, bool show_higlighted); + v3s16 getHighlighted(){ return m_highlighted_pos; } + + u16 getHP(); + u16 getBreath(); + + bool checkPrivilege(const std::string &priv) + { return (m_privileges.count(priv) != 0); } + + bool getChatMessage(std::wstring &message); + void typeChatMessage(const std::wstring& message); + + u64 getMapSeed(){ return m_map_seed; } + + void addUpdateMeshTask(v3s16 blockpos, bool ack_to_server=false, bool urgent=false); + // Including blocks at appropriate edges + void addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server=false, bool urgent=false); + void addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server=false, bool urgent=false); + + void updateCameraOffset(v3s16 camera_offset) + { m_mesh_update_thread.m_camera_offset = camera_offset; } + + // Get event from queue. CE_NONE is returned if queue is empty. + ClientEvent getClientEvent(); + + bool accessDenied() + { return m_access_denied; } + + std::wstring accessDeniedReason() + { return m_access_denied_reason; } + + bool itemdefReceived() + { return m_itemdef_received; } + bool nodedefReceived() + { return m_nodedef_received; } + bool mediaReceived() + { return m_media_downloader == NULL; } + + float mediaReceiveProgress(); + + void afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font); + + float getRTT(void); + float getCurRate(void); + float getAvgRate(void); + + // IGameDef interface + virtual IItemDefManager* getItemDefManager(); + virtual INodeDefManager* getNodeDefManager(); + virtual ICraftDefManager* getCraftDefManager(); + virtual ITextureSource* getTextureSource(); + virtual IShaderSource* getShaderSource(); + virtual scene::ISceneManager* getSceneManager(); + virtual u16 allocateUnknownNodeId(const std::string &name); + virtual ISoundManager* getSoundManager(); + virtual MtEventManager* getEventManager(); + virtual ParticleManager* getParticleManager(); + virtual bool checkLocalPrivilege(const std::string &priv) + { return checkPrivilege(priv); } + virtual scene::IAnimatedMesh* getMesh(const std::string &filename); + + // The following set of functions is used by ClientMediaDownloader + // Insert a media file appropriately into the appropriate manager + bool loadMedia(const std::string &data, const std::string &filename); + // Send a request for conventional media transfer + void request_media(const std::vector &file_requests); + // Send a notification that no conventional media transfer is needed + void received_media(); + + LocalClientState getState() { return m_state; } + + void makeScreenshot(IrrlichtDevice *device); + +private: + + // Virtual methods from con::PeerHandler + void peerAdded(con::Peer *peer); + void deletingPeer(con::Peer *peer, bool timeout); + + void initLocalMapSaving(const Address &address, + const std::string &hostname, + bool is_local_server); + + void ReceiveAll(); + void Receive(); + + void sendPlayerPos(); + // Send the item number 'item' as player item to the server + void sendPlayerItem(u16 item); + + float m_packetcounter_timer; + float m_connection_reinit_timer; + float m_avg_rtt_timer; + float m_playerpos_send_timer; + float m_ignore_damage_timer; // Used after server moves player + IntervalLimiter m_map_timer_and_unload_interval; + + IWritableTextureSource *m_tsrc; + IWritableShaderSource *m_shsrc; + IWritableItemDefManager *m_itemdef; + IWritableNodeDefManager *m_nodedef; + ISoundManager *m_sound; + MtEventManager *m_event; + + + MeshUpdateThread m_mesh_update_thread; + ClientEnvironment m_env; + ParticleManager m_particle_manager; + con::Connection m_con; + IrrlichtDevice *m_device; + // Server serialization version + u8 m_server_ser_ver; + u16 m_playeritem; + bool m_inventory_updated; + Inventory *m_inventory_from_server; + float m_inventory_from_server_age; + std::set m_active_blocks; + PacketCounter m_packetcounter; + bool m_show_highlighted; + // Block mesh animation parameters + float m_animation_time; + int m_crack_level; + v3s16 m_crack_pos; + v3s16 m_highlighted_pos; + // 0 <= m_daynight_i < DAYNIGHT_CACHE_COUNT + //s32 m_daynight_i; + //u32 m_daynight_ratio; + std::queue m_chat_queue; + // The seed returned by the server in TOCLIENT_INIT is stored here + u64 m_map_seed; + std::string m_password; + bool m_access_denied; + std::wstring m_access_denied_reason; + std::queue m_client_event_queue; + bool m_itemdef_received; + bool m_nodedef_received; + ClientMediaDownloader *m_media_downloader; + + // time_of_day speed approximation for old protocol + bool m_time_of_day_set; + float m_last_time_of_day_f; + float m_time_of_day_update_timer; + + // An interval for generally sending object positions and stuff + float m_recommended_send_interval; + + // Sounds + float m_removed_sounds_check_timer; + // Mapping from server sound ids to our sound ids + std::map m_sounds_server_to_client; + // And the other way! + std::map m_sounds_client_to_server; + // And relations to objects + std::map m_sounds_to_objects; + + // Privileges + std::set m_privileges; + + // Detached inventories + // key = name + std::map m_detached_inventories; + + // Storage for mesh data for creating multiple instances of the same mesh + std::map m_mesh_data; + + // own state + LocalClientState m_state; + + // Used for saving server map to disk client-side + Database *m_localdb; + IntervalLimiter m_localdb_save_interval; + u16 m_cache_save_interval; + + // TODO: Add callback to update these when g_settings changes + bool m_cache_smooth_lighting; + bool m_cache_enable_shaders; +}; + +#endif // !CLIENT_HEADER diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt new file mode 100644 index 0000000..288acf1 --- /dev/null +++ b/src/client/CMakeLists.txt @@ -0,0 +1,5 @@ +set(client_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/clientlauncher.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp + PARENT_SCOPE +) diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp new file mode 100644 index 0000000..a4c087d --- /dev/null +++ b/src/client/clientlauncher.cpp @@ -0,0 +1,709 @@ +/* +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. +*/ + +#include "main.h" +#include "mainmenumanager.h" +#include "debug.h" +#include "clouds.h" +#include "server.h" +#include "filesys.h" +#include "guiMainMenu.h" +#include "game.h" +#include "chat.h" +#include "gettext.h" +#include "profiler.h" +#include "log.h" +#include "serverlist.h" +#include "guiEngine.h" +#include "player.h" +#include "fontengine.h" +#include "clientlauncher.h" + +// A pointer to a global instance of the time getter +// TODO: why? +TimeGetter *g_timegetter = NULL; + +u32 getTimeMs() +{ + if (g_timegetter == NULL) + return 0; + return g_timegetter->getTime(PRECISION_MILLI); +} + +u32 getTime(TimePrecision prec) { + if (g_timegetter == NULL) + return 0; + return g_timegetter->getTime(prec); +} + +ClientLauncher::~ClientLauncher() +{ + if (receiver) + delete receiver; + + if (input) + delete input; + + if (g_fontengine) + delete g_fontengine; + + if (device) + device->drop(); +} + + +bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args) +{ + init_args(game_params, cmd_args); + + // List video modes if requested + if (list_video_modes) + return print_video_modes(); + + if (!init_engine(game_params.log_level)) { + errorstream << "Could not initialize game engine." << std::endl; + return false; + } + + // Speed tests (done after irrlicht is loaded to get timer) + if (cmd_args.getFlag("speedtests")) { + dstream << "Running speed tests" << std::endl; + speed_tests(); + return true; + } + + video::IVideoDriver *video_driver = device->getVideoDriver(); + if (video_driver == NULL) { + errorstream << "Could not initialize video driver." << std::endl; + return false; + } + + porting::setXorgClassHint(video_driver->getExposedVideoData(), "Blokel"); + + /* + This changes the minimum allowed number of vertices in a VBO. + Default is 500. + */ + //driver->setMinHardwareBufferVertexCount(50); + + // Create time getter + g_timegetter = new IrrlichtTimeGetter(device); + + // Create game callback for menus + g_gamecallback = new MainGameCallback(device); + + device->setResizable(true); + + if (random_input) + input = new RandomInputHandler(); + else + input = new RealInputHandler(device, receiver); + + smgr = device->getSceneManager(); + smgr->getParameters()->setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true); + + guienv = device->getGUIEnvironment(); + skin = guienv->getSkin(); + skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255, 255, 255, 255)); + skin->setColor(gui::EGDC_3D_LIGHT, video::SColor(0, 0, 0, 0)); + skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255, 30, 30, 30)); + skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255, 0, 0, 0)); + skin->setColor(gui::EGDC_HIGH_LIGHT, video::SColor(255, 70, 120, 50)); + skin->setColor(gui::EGDC_HIGH_LIGHT_TEXT, video::SColor(255, 255, 255, 255)); + + g_fontengine = new FontEngine(g_settings, guienv); + FATAL_ERROR_IF(g_fontengine == NULL, "Font engine creation failed."); + +#if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2 + // Irrlicht 1.8 input colours + skin->setColor(gui::EGDC_EDITABLE, video::SColor(255, 128, 128, 128)); + skin->setColor(gui::EGDC_FOCUSED_EDITABLE, video::SColor(255, 96, 134, 49)); +#endif + + // Create the menu clouds + if (!g_menucloudsmgr) + g_menucloudsmgr = smgr->createNewSceneManager(); + if (!g_menuclouds) + g_menuclouds = new Clouds(g_menucloudsmgr->getRootSceneNode(), + g_menucloudsmgr, -1, rand(), 100); + g_menuclouds->update(v2f(0, 0), video::SColor(255, 200, 200, 255)); + scene::ICameraSceneNode* camera; + camera = g_menucloudsmgr->addCameraSceneNode(0, + v3f(0, 0, 0), v3f(0, 60, 100)); + camera->setFarValue(10000); + + /* + GUI stuff + */ + + ChatBackend chat_backend; + + // If an error occurs, this is set to something by menu(). + // It is then displayed before the menu shows on the next call to menu() + std::wstring error_message = L""; + + bool first_loop = true; + + /* + Menu-game loop + */ + bool retval = true; + bool *kill = porting::signal_handler_killstatus(); + + while (device->run() && !*kill && !g_gamecallback->shutdown_requested) + { + // Set the window caption + const wchar_t *text = wgettext("Launcher"); + device->setWindowCaption((std::wstring(L"Blokel [") + text + L"]").c_str()); + delete[] text; + + try { // This is used for catching disconnects + + guienv->clear(); + + /* + We need some kind of a root node to be able to add + custom gui elements directly on the screen. + Otherwise they won't be automatically drawn. + */ + guiroot = guienv->addStaticText(L"", core::rect(0, 0, 10000, 10000)); + + bool game_has_run = launch_game(&error_message, game_params, cmd_args); + + // If skip_main_menu, we only want to startup once + if (skip_main_menu && !first_loop) + break; + + first_loop = false; + + if (!game_has_run) { + if (skip_main_menu) + break; + else + continue; + } + + // Break out of menu-game loop to shut down cleanly + if (!device->run() || *kill) { + if (g_settings_path != "") + g_settings->updateConfigFile(g_settings_path.c_str()); + break; + } + + if (current_playername.length() > PLAYERNAME_SIZE-1) { + error_message = wgettext("Player name too long."); + playername = current_playername.substr(0, PLAYERNAME_SIZE-1); + g_settings->set("name", playername); + continue; + } + + device->getVideoDriver()->setTextureCreationFlag( + video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map")); + +#ifdef HAVE_TOUCHSCREENGUI + receiver->m_touchscreengui = new TouchScreenGUI(device, receiver); + g_touchscreengui = receiver->m_touchscreengui; +#endif + the_game( + kill, + random_input, + input, + device, + worldspec.path, + current_playername, + current_password, + current_address, + current_port, + error_message, + chat_backend, + gamespec, + simple_singleplayer_mode + ); + smgr->clear(); + +#ifdef HAVE_TOUCHSCREENGUI + delete g_touchscreengui; + g_touchscreengui = NULL; + receiver->m_touchscreengui = NULL; +#endif + + } //try + catch (con::PeerNotFoundException &e) { + error_message = wgettext("Connection error (timed out?)"); + errorstream << wide_to_narrow(error_message) << std::endl; + } + +#ifdef NDEBUG + catch (std::exception &e) { + std::string narrow_message = "Some exception: \""; + narrow_message += e.what(); + narrow_message += "\""; + errorstream << narrow_message << std::endl; + error_message = narrow_to_wide(narrow_message); + } +#endif + + // If no main menu, show error and exit + if (skip_main_menu) { + if (error_message != L"") { + verbosestream << "error_message = " + << wide_to_narrow(error_message) << std::endl; + retval = false; + } + break; + } + } // Menu-game loop + + g_menuclouds->drop(); + g_menucloudsmgr->drop(); + + return retval; +} + +void ClientLauncher::init_args(GameParams &game_params, const Settings &cmd_args) +{ + + skip_main_menu = cmd_args.getFlag("go"); + + // FIXME: This is confusing (but correct) + + /* If world_path is set then override it unless skipping the main menu using + * the --go command line param. Else, give preference to the address + * supplied on the command line + */ + address = g_settings->get("address"); + if (game_params.world_path != "" && !skip_main_menu) + address = ""; + else if (cmd_args.exists("address")) + address = cmd_args.get("address"); + + playername = g_settings->get("name"); + if (cmd_args.exists("name")) + playername = cmd_args.get("name"); + + list_video_modes = cmd_args.getFlag("videomodes"); + + use_freetype = g_settings->getBool("freetype"); + + random_input = g_settings->getBool("random_input") + || cmd_args.getFlag("random-input"); +} + +bool ClientLauncher::init_engine(int log_level) +{ + receiver = new MyEventReceiver(); + create_engine_device(log_level); + return device != NULL; +} + +bool ClientLauncher::launch_game(std::wstring *error_message, + GameParams &game_params, const Settings &cmd_args) +{ + // Initialize menu data + MainMenuData menudata; + menudata.address = address; + menudata.name = playername; + menudata.port = itos(game_params.socket_port); + menudata.errormessage = wide_to_narrow(*error_message); + + *error_message = L""; + + if (cmd_args.exists("password")) + menudata.password = cmd_args.get("password"); + + menudata.enable_public = g_settings->getBool("server_announce"); + + // If a world was commanded, append and select it + if (game_params.world_path != "") { + worldspec.gameid = getWorldGameId(game_params.world_path, true); + worldspec.name = _("[--world parameter]"); + + if (worldspec.gameid == "") { // Create new + worldspec.gameid = g_settings->get("default_game"); + worldspec.name += " [new]"; + } + worldspec.path = game_params.world_path; + } + + /* Show the GUI menu + */ + if (!skip_main_menu) { + main_menu(&menudata); + + // Skip further loading if there was an exit signal. + if (*porting::signal_handler_killstatus()) + return false; + + address = menudata.address; + int newport = stoi(menudata.port); + if (newport != 0) + game_params.socket_port = newport; + + simple_singleplayer_mode = menudata.simple_singleplayer_mode; + + std::vector worldspecs = getAvailableWorlds(); + + if (menudata.selected_world >= 0 + && menudata.selected_world < (int)worldspecs.size()) { + g_settings->set("selected_world_path", + worldspecs[menudata.selected_world].path); + worldspec = worldspecs[menudata.selected_world]; + } + } + + if (menudata.errormessage != "") { + /* The calling function will pass this back into this function upon the + * next iteration (if any) causing it to be displayed by the GUI + */ + *error_message = narrow_to_wide(menudata.errormessage); + return false; + } + + if (menudata.name == "") + menudata.name = std::string("Guest") + itos(myrand_range(1000, 9999)); + else + playername = menudata.name; + + password = translatePassword(playername, narrow_to_wide(menudata.password)); + + g_settings->set("name", playername); + + current_playername = playername; + current_password = password; + current_address = address; + current_port = game_params.socket_port; + + // If using simple singleplayer mode, override + if (simple_singleplayer_mode) { + assert(skip_main_menu == false); + current_playername = "singleplayer"; + current_password = ""; + current_address = ""; + current_port = myrand_range(49152, 65535); + } else if (address != "") { + ServerListSpec server; + server["name"] = menudata.servername; + server["address"] = menudata.address; + server["port"] = menudata.port; + server["description"] = menudata.serverdescription; + ServerList::insert(server); + } + + infostream << "Selected world: " << worldspec.name + << " [" << worldspec.path << "]" << std::endl; + + if (current_address == "") { // If local game + if (worldspec.path == "") { + *error_message = wgettext("No world selected and no address " + "provided. Nothing to do."); + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + if (!fs::PathExists(worldspec.path)) { + *error_message = wgettext("Provided world path doesn't exist: ") + + narrow_to_wide(worldspec.path); + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + // Load gamespec for required game + gamespec = findWorldSubgame(worldspec.path); + if (!gamespec.isValid() && !game_params.game_spec.isValid()) { + *error_message = wgettext("Could not find or load game \"") + + narrow_to_wide(worldspec.gameid) + L"\""; + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + if (porting::signal_handler_killstatus()) + return true; + + if (game_params.game_spec.isValid() && + game_params.game_spec.id != worldspec.gameid) { + errorstream << "WARNING: Overriding gamespec from \"" + << worldspec.gameid << "\" to \"" + << game_params.game_spec.id << "\"" << std::endl; + gamespec = game_params.game_spec; + } + + if (!gamespec.isValid()) { + *error_message = wgettext("Invalid gamespec."); + *error_message += L" (world_gameid=" + + narrow_to_wide(worldspec.gameid) + L")"; + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + } + + return true; +} + +void ClientLauncher::main_menu(MainMenuData *menudata) +{ + bool *kill = porting::signal_handler_killstatus(); + video::IVideoDriver *driver = device->getVideoDriver(); + + infostream << "Waiting for other menus" << std::endl; + while (device->run() && *kill == false) { + if (noMenuActive()) + break; + driver->beginScene(true, true, video::SColor(255, 128, 128, 128)); + guienv->drawAll(); + driver->endScene(); + // On some computers framerate doesn't seem to be automatically limited + sleep_ms(25); + } + infostream << "Waited for other menus" << std::endl; + + // Cursor can be non-visible when coming from the game +#ifndef ANDROID + device->getCursorControl()->setVisible(true); +#endif + + /* show main menu */ + GUIEngine mymenu(device, guiroot, &g_menumgr, smgr, menudata, *kill); + + smgr->clear(); /* leave scene manager in a clean state */ +} + +bool ClientLauncher::create_engine_device(int log_level) +{ + static const irr::ELOG_LEVEL irr_log_level[5] = { + ELL_NONE, + ELL_ERROR, + ELL_WARNING, + ELL_INFORMATION, +#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) + ELL_INFORMATION +#else + ELL_DEBUG +#endif + }; + + // Resolution selection + bool fullscreen = g_settings->getBool("fullscreen"); + u16 screenW = g_settings->getU16("screenW"); + u16 screenH = g_settings->getU16("screenH"); + + // bpp, fsaa, vsync + bool vsync = g_settings->getBool("vsync"); + u16 bits = g_settings->getU16("fullscreen_bpp"); + u16 fsaa = g_settings->getU16("fsaa"); + + // Determine driver + video::E_DRIVER_TYPE driverType = video::EDT_OPENGL; + std::string driverstring = g_settings->get("video_driver"); + std::vector drivers + = porting::getSupportedVideoDrivers(); + u32 i; + for (i = 0; i != drivers.size(); i++) { + if (!strcasecmp(driverstring.c_str(), + porting::getVideoDriverName(drivers[i]))) { + driverType = drivers[i]; + break; + } + } + if (i == drivers.size()) { + errorstream << "Invalid video_driver specified; " + "defaulting to opengl" << std::endl; + } + + SIrrlichtCreationParameters params = SIrrlichtCreationParameters(); + params.DriverType = driverType; + params.WindowSize = core::dimension2d(screenW, screenH); + params.Bits = bits; + params.AntiAlias = fsaa; + params.Fullscreen = fullscreen; + params.Stencilbuffer = false; + params.Vsync = vsync; + params.EventReceiver = receiver; + params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu"); +#ifdef __ANDROID__ + params.PrivateData = porting::app_global; + params.OGLES2ShaderPath = std::string(porting::path_user + DIR_DELIM + + "media" + DIR_DELIM + "Shaders" + DIR_DELIM).c_str(); +#endif + + device = createDeviceEx(params); + + if (device) { + // Map our log level to irrlicht engine one. + ILogger* irr_logger = device->getLogger(); + irr_logger->setLogLevel(irr_log_level[log_level]); + + porting::initIrrlicht(device); + } + + return device != NULL; +} + +void ClientLauncher::speed_tests() +{ + // volatile to avoid some potential compiler optimisations + volatile static s16 temp16; + volatile static f32 tempf; + static v3f tempv3f1; + static v3f tempv3f2; + static std::string tempstring; + static std::string tempstring2; + + tempv3f1 = v3f(); + tempv3f2 = v3f(); + tempstring = std::string(); + tempstring2 = std::string(); + + { + infostream << "The following test should take around 20ms." << std::endl; + TimeTaker timer("Testing std::string speed"); + const u32 jj = 10000; + for (u32 j = 0; j < jj; j++) { + tempstring = ""; + tempstring2 = ""; + const u32 ii = 10; + for (u32 i = 0; i < ii; i++) { + tempstring2 += "asd"; + } + for (u32 i = 0; i < ii+1; i++) { + tempstring += "asd"; + if (tempstring == tempstring2) + break; + } + } + } + + infostream << "All of the following tests should take around 100ms each." + << std::endl; + + { + TimeTaker timer("Testing floating-point conversion speed"); + tempf = 0.001; + for (u32 i = 0; i < 4000000; i++) { + temp16 += tempf; + tempf += 0.001; + } + } + + { + TimeTaker timer("Testing floating-point vector speed"); + + tempv3f1 = v3f(1, 2, 3); + tempv3f2 = v3f(4, 5, 6); + for (u32 i = 0; i < 10000000; i++) { + tempf += tempv3f1.dotProduct(tempv3f2); + tempv3f2 += v3f(7, 8, 9); + } + } + + { + TimeTaker timer("Testing std::map speed"); + + std::map map1; + tempf = -324; + const s16 ii = 300; + for (s16 y = 0; y < ii; y++) { + for (s16 x = 0; x < ii; x++) { + map1[v2s16(x, y)] = tempf; + tempf += 1; + } + } + for (s16 y = ii - 1; y >= 0; y--) { + for (s16 x = 0; x < ii; x++) { + tempf = map1[v2s16(x, y)]; + } + } + } + + { + infostream << "Around 5000/ms should do well here." << std::endl; + TimeTaker timer("Testing mutex speed"); + + JMutex m; + u32 n = 0; + u32 i = 0; + do { + n += 10000; + for (; i < n; i++) { + m.Lock(); + m.Unlock(); + } + } + // Do at least 10ms + while(timer.getTimerTime() < 10); + + u32 dtime = timer.stop(); + u32 per_ms = n / dtime; + infostream << "Done. " << dtime << "ms, " << per_ms << "/ms" << std::endl; + } +} + +bool ClientLauncher::print_video_modes() +{ + IrrlichtDevice *nulldevice; + + bool vsync = g_settings->getBool("vsync"); + u16 fsaa = g_settings->getU16("fsaa"); + MyEventReceiver* receiver = new MyEventReceiver(); + + SIrrlichtCreationParameters params = SIrrlichtCreationParameters(); + params.DriverType = video::EDT_NULL; + params.WindowSize = core::dimension2d(640, 480); + params.Bits = 24; + params.AntiAlias = fsaa; + params.Fullscreen = false; + params.Stencilbuffer = false; + params.Vsync = vsync; + params.EventReceiver = receiver; + params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu"); + + nulldevice = createDeviceEx(params); + + if (nulldevice == NULL) { + delete receiver; + return false; + } + + dstream << _("Available video modes (WxHxD):") << std::endl; + + video::IVideoModeList *videomode_list = nulldevice->getVideoModeList(); + + if (videomode_list != NULL) { + s32 videomode_count = videomode_list->getVideoModeCount(); + core::dimension2d videomode_res; + s32 videomode_depth; + for (s32 i = 0; i < videomode_count; ++i) { + videomode_res = videomode_list->getVideoModeResolution(i); + videomode_depth = videomode_list->getVideoModeDepth(i); + dstream << videomode_res.Width << "x" << videomode_res.Height + << "x" << videomode_depth << std::endl; + } + + dstream << _("Active video mode (WxHxD):") << std::endl; + videomode_res = videomode_list->getDesktopResolution(); + videomode_depth = videomode_list->getDesktopDepth(); + dstream << videomode_res.Width << "x" << videomode_res.Height + << "x" << videomode_depth << std::endl; + + } + + nulldevice->drop(); + delete receiver; + + return videomode_list != NULL; +} diff --git a/src/client/clientlauncher.h b/src/client/clientlauncher.h new file mode 100644 index 0000000..c3e0ae8 --- /dev/null +++ b/src/client/clientlauncher.h @@ -0,0 +1,129 @@ +/* +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. +*/ + +#ifndef __CLIENT_LAUNCHER_H__ +#define __CLIENT_LAUNCHER_H__ + +#include "irrlichttypes_extrabloated.h" +#include "client/inputhandler.h" +#include "gameparams.h" + +// A small helper class +class TimeGetter +{ +public: + virtual u32 getTime(TimePrecision prec) = 0; +}; + +// A precise irrlicht one +class IrrlichtTimeGetter: public TimeGetter +{ +public: + IrrlichtTimeGetter(IrrlichtDevice *device): + m_device(device) + {} + u32 getTime(TimePrecision prec) + { + if (prec == PRECISION_MILLI) { + if (m_device == NULL) + return 0; + return m_device->getTimer()->getRealTime(); + } else { + return porting::getTime(prec); + } + } +private: + IrrlichtDevice *m_device; +}; +// Not so precise one which works without irrlicht +class SimpleTimeGetter: public TimeGetter +{ +public: + u32 getTime(TimePrecision prec) + { + return porting::getTime(prec); + } +}; + +class ClientLauncher +{ +public: + ClientLauncher() : + list_video_modes(false), + skip_main_menu(false), + use_freetype(false), + random_input(false), + address(""), + playername(""), + password(""), + device(NULL), + input(NULL), + receiver(NULL), + skin(NULL), + font(NULL), + simple_singleplayer_mode(false), + current_playername("inv£lid"), + current_password(""), + current_address("does-not-exist"), + current_port(0) + {} + + ~ClientLauncher(); + + bool run(GameParams &game_params, const Settings &cmd_args); + +protected: + void init_args(GameParams &game_params, const Settings &cmd_args); + bool init_engine(int log_level); + + bool launch_game(std::wstring *error_message, GameParams &game_params, + const Settings &cmd_args); + + void main_menu(MainMenuData *menudata); + bool create_engine_device(int log_level); + + void speed_tests(); + bool print_video_modes(); + + bool list_video_modes; + bool skip_main_menu; + bool use_freetype; + bool random_input; + std::string address; + std::string playername; + std::string password; + IrrlichtDevice *device; + InputHandler *input; + MyEventReceiver *receiver; + gui::IGUISkin *skin; + gui::IGUIFont *font; + scene::ISceneManager *smgr; + SubgameSpec gamespec; + WorldSpec worldspec; + bool simple_singleplayer_mode; + + // These are set up based on the menu and other things + // TODO: Are these required since there's already playername, password, etc + std::string current_playername; + std::string current_password; + std::string current_address; + int current_port; +}; + +#endif diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h new file mode 100644 index 0000000..7cd3ddc --- /dev/null +++ b/src/client/inputhandler.h @@ -0,0 +1,428 @@ +/* +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. +*/ + +#ifndef __INPUT_HANDLER_H__ +#define __INPUT_HANDLER_H__ + +#include "irrlichttypes_extrabloated.h" + +class MyEventReceiver : public IEventReceiver +{ +public: + // This is the one method that we have to implement + virtual bool OnEvent(const SEvent& event) + { + /* + React to nothing here if a menu is active + */ + if (noMenuActive() == false) { +#ifdef HAVE_TOUCHSCREENGUI + if (m_touchscreengui != 0) { + m_touchscreengui->Toggle(false); + } +#endif + return g_menumgr.preprocessEvent(event); + } + + // Remember whether each key is down or up + if (event.EventType == irr::EET_KEY_INPUT_EVENT) { + if (event.KeyInput.PressedDown) { + keyIsDown.set(event.KeyInput); + keyWasDown.set(event.KeyInput); + } else { + keyIsDown.unset(event.KeyInput); + } + } + +#ifdef HAVE_TOUCHSCREENGUI + // case of touchscreengui we have to handle different events + if ((m_touchscreengui != 0) && + (event.EventType == irr::EET_TOUCH_INPUT_EVENT)) { + m_touchscreengui->translateEvent(event); + return true; + } +#endif + // handle mouse events + if (event.EventType == irr::EET_MOUSE_INPUT_EVENT) { + if (noMenuActive() == false) { + left_active = false; + middle_active = false; + right_active = false; + } else { + left_active = event.MouseInput.isLeftPressed(); + middle_active = event.MouseInput.isMiddlePressed(); + right_active = event.MouseInput.isRightPressed(); + + if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { + leftclicked = true; + } + if (event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN) { + rightclicked = true; + } + if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) { + leftreleased = true; + } + if (event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP) { + rightreleased = true; + } + if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) { + mouse_wheel += event.MouseInput.Wheel; + } + } + } + if (event.EventType == irr::EET_LOG_TEXT_EVENT) { + dstream << std::string("Irrlicht log: ") + std::string(event.LogEvent.Text) + << std::endl; + return true; + } + /* always return false in order to continue processing events */ + return false; + } + + bool IsKeyDown(const KeyPress &keyCode) const + { + return keyIsDown[keyCode]; + } + + // Checks whether a key was down and resets the state + bool WasKeyDown(const KeyPress &keyCode) + { + bool b = keyWasDown[keyCode]; + if (b) + keyWasDown.unset(keyCode); + return b; + } + + s32 getMouseWheel() + { + s32 a = mouse_wheel; + mouse_wheel = 0; + return a; + } + + void clearInput() + { + keyIsDown.clear(); + keyWasDown.clear(); + + leftclicked = false; + rightclicked = false; + leftreleased = false; + rightreleased = false; + + left_active = false; + middle_active = false; + right_active = false; + + mouse_wheel = 0; + } + + MyEventReceiver() + { + clearInput(); +#ifdef HAVE_TOUCHSCREENGUI + m_touchscreengui = NULL; +#endif + } + + bool leftclicked; + bool rightclicked; + bool leftreleased; + bool rightreleased; + + bool left_active; + bool middle_active; + bool right_active; + + s32 mouse_wheel; + +#ifdef HAVE_TOUCHSCREENGUI + TouchScreenGUI* m_touchscreengui; +#endif + +private: + // The current state of keys + KeyList keyIsDown; + // Whether a key has been pressed or not + KeyList keyWasDown; +}; + + +/* + Separated input handler +*/ + +class RealInputHandler : public InputHandler +{ +public: + RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver): + m_device(device), + m_receiver(receiver), + m_mousepos(0,0) + { + } + virtual bool isKeyDown(const KeyPress &keyCode) + { + return m_receiver->IsKeyDown(keyCode); + } + virtual bool wasKeyDown(const KeyPress &keyCode) + { + return m_receiver->WasKeyDown(keyCode); + } + virtual v2s32 getMousePos() + { + if (m_device->getCursorControl()) { + return m_device->getCursorControl()->getPosition(); + } + else { + return m_mousepos; + } + } + virtual void setMousePos(s32 x, s32 y) + { + if (m_device->getCursorControl()) { + m_device->getCursorControl()->setPosition(x, y); + } + else { + m_mousepos = v2s32(x,y); + } + } + + virtual bool getLeftState() + { + return m_receiver->left_active; + } + virtual bool getRightState() + { + return m_receiver->right_active; + } + + virtual bool getLeftClicked() + { + return m_receiver->leftclicked; + } + virtual bool getRightClicked() + { + return m_receiver->rightclicked; + } + virtual void resetLeftClicked() + { + m_receiver->leftclicked = false; + } + virtual void resetRightClicked() + { + m_receiver->rightclicked = false; + } + + virtual bool getLeftReleased() + { + return m_receiver->leftreleased; + } + virtual bool getRightReleased() + { + return m_receiver->rightreleased; + } + virtual void resetLeftReleased() + { + m_receiver->leftreleased = false; + } + virtual void resetRightReleased() + { + m_receiver->rightreleased = false; + } + + virtual s32 getMouseWheel() + { + return m_receiver->getMouseWheel(); + } + + void clear() + { + m_receiver->clearInput(); + } +private: + IrrlichtDevice *m_device; + MyEventReceiver *m_receiver; + v2s32 m_mousepos; +}; + +class RandomInputHandler : public InputHandler +{ +public: + RandomInputHandler() + { + leftdown = false; + rightdown = false; + leftclicked = false; + rightclicked = false; + leftreleased = false; + rightreleased = false; + keydown.clear(); + } + virtual bool isKeyDown(const KeyPress &keyCode) + { + return keydown[keyCode]; + } + virtual bool wasKeyDown(const KeyPress &keyCode) + { + return false; + } + virtual v2s32 getMousePos() + { + return mousepos; + } + virtual void setMousePos(s32 x, s32 y) + { + mousepos = v2s32(x, y); + } + + virtual bool getLeftState() + { + return leftdown; + } + virtual bool getRightState() + { + return rightdown; + } + + virtual bool getLeftClicked() + { + return leftclicked; + } + virtual bool getRightClicked() + { + return rightclicked; + } + virtual void resetLeftClicked() + { + leftclicked = false; + } + virtual void resetRightClicked() + { + rightclicked = false; + } + + virtual bool getLeftReleased() + { + return leftreleased; + } + virtual bool getRightReleased() + { + return rightreleased; + } + virtual void resetLeftReleased() + { + leftreleased = false; + } + virtual void resetRightReleased() + { + rightreleased = false; + } + + virtual s32 getMouseWheel() + { + return 0; + } + + virtual void step(float dtime) + { + { + static float counter1 = 0; + counter1 -= dtime; + if (counter1 < 0.0) { + counter1 = 0.1 * Rand(1, 40); + keydown.toggle(getKeySetting("keymap_jump")); + } + } + { + static float counter1 = 0; + counter1 -= dtime; + if (counter1 < 0.0) { + counter1 = 0.1 * Rand(1, 40); + keydown.toggle(getKeySetting("keymap_special1")); + } + } + { + static float counter1 = 0; + counter1 -= dtime; + if (counter1 < 0.0) { + counter1 = 0.1 * Rand(1, 40); + keydown.toggle(getKeySetting("keymap_forward")); + } + } + { + static float counter1 = 0; + counter1 -= dtime; + if (counter1 < 0.0) { + counter1 = 0.1 * Rand(1, 40); + keydown.toggle(getKeySetting("keymap_left")); + } + } + { + static float counter1 = 0; + counter1 -= dtime; + if (counter1 < 0.0) { + counter1 = 0.1 * Rand(1, 20); + mousespeed = v2s32(Rand(-20, 20), Rand(-15, 20)); + } + } + { + static float counter1 = 0; + counter1 -= dtime; + if (counter1 < 0.0) { + counter1 = 0.1 * Rand(1, 30); + leftdown = !leftdown; + if (leftdown) + leftclicked = true; + if (!leftdown) + leftreleased = true; + } + } + { + static float counter1 = 0; + counter1 -= dtime; + if (counter1 < 0.0) { + counter1 = 0.1 * Rand(1, 15); + rightdown = !rightdown; + if (rightdown) + rightclicked = true; + if (!rightdown) + rightreleased = true; + } + } + mousepos += mousespeed; + } + + s32 Rand(s32 min, s32 max) + { + return (myrand()%(max-min+1))+min; + } +private: + KeyList keydown; + v2s32 mousepos; + v2s32 mousespeed; + bool leftdown; + bool rightdown; + bool leftclicked; + bool rightclicked; + bool leftreleased; + bool rightreleased; +}; + +#endif diff --git a/src/client/tile.cpp b/src/client/tile.cpp new file mode 100644 index 0000000..541247f --- /dev/null +++ b/src/client/tile.cpp @@ -0,0 +1,1954 @@ +/* +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. +*/ + +#include "tile.h" + +#include +#include "util/string.h" +#include "util/container.h" +#include "util/thread.h" +#include "util/numeric.h" +#include "irrlichttypes_extrabloated.h" +#include "debug.h" +#include "main.h" // for g_settings +#include "filesys.h" +#include "settings.h" +#include "mesh.h" +#include "log.h" +#include "gamedef.h" +#include "strfnd.h" +#include "util/string.h" // for parseColorString() + +#ifdef __ANDROID__ +#include +#endif + +/* + A cache from texture name to texture path +*/ +MutexedMap g_texturename_to_path_cache; + +/* + Replaces the filename extension. + eg: + std::string image = "a/image.png" + replace_ext(image, "jpg") + -> image = "a/image.jpg" + Returns true on success. +*/ +static bool replace_ext(std::string &path, const char *ext) +{ + if (ext == NULL) + return false; + // Find place of last dot, fail if \ or / found. + s32 last_dot_i = -1; + for (s32 i=path.size()-1; i>=0; i--) + { + if (path[i] == '.') + { + last_dot_i = i; + break; + } + + if (path[i] == '\\' || path[i] == '/') + break; + } + // If not found, return an empty string + if (last_dot_i == -1) + return false; + // Else make the new path + path = path.substr(0, last_dot_i+1) + ext; + return true; +} + +/* + Find out the full path of an image by trying different filename + extensions. + + If failed, return "". +*/ +std::string getImagePath(std::string path) +{ + // A NULL-ended list of possible image extensions + const char *extensions[] = { + "png", "jpg", "bmp", "tga", + "pcx", "ppm", "psd", "wal", "rgb", + NULL + }; + // If there is no extension, add one + if (removeStringEnd(path, extensions) == "") + path = path + ".png"; + // Check paths until something is found to exist + const char **ext = extensions; + do{ + bool r = replace_ext(path, *ext); + if (r == false) + return ""; + if (fs::PathExists(path)) + return path; + } + while((++ext) != NULL); + + return ""; +} + +/* + Gets the path to a texture by first checking if the texture exists + in texture_path and if not, using the data path. + + Checks all supported extensions by replacing the original extension. + + If not found, returns "". + + Utilizes a thread-safe cache. +*/ +std::string getTexturePath(const std::string &filename) +{ + std::string fullpath = ""; + /* + Check from cache + */ + bool incache = g_texturename_to_path_cache.get(filename, &fullpath); + if (incache) + return fullpath; + + /* + Check from texture_path + */ + std::string texture_path = g_settings->get("texture_path"); + if (texture_path != "") + { + std::string testpath = texture_path + DIR_DELIM + filename; + // Check all filename extensions. Returns "" if not found. + fullpath = getImagePath(testpath); + } + + /* + Check from default data directory + */ + if (fullpath == "") + { + std::string base_path = porting::path_share + DIR_DELIM + "textures" + + DIR_DELIM + "base" + DIR_DELIM + "pack"; + std::string testpath = base_path + DIR_DELIM + filename; + // Check all filename extensions. Returns "" if not found. + fullpath = getImagePath(testpath); + } + + // Add to cache (also an empty result is cached) + g_texturename_to_path_cache.set(filename, fullpath); + + // Finally return it + return fullpath; +} + +void clearTextureNameCache() +{ + g_texturename_to_path_cache.clear(); +} + +/* + Stores internal information about a texture. +*/ + +struct TextureInfo +{ + std::string name; + video::ITexture *texture; + + TextureInfo( + const std::string &name_, + video::ITexture *texture_=NULL + ): + name(name_), + texture(texture_) + { + } +}; + +/* + SourceImageCache: A cache used for storing source images. +*/ + +class SourceImageCache +{ +public: + ~SourceImageCache() { + for (std::map::iterator iter = m_images.begin(); + iter != m_images.end(); iter++) { + iter->second->drop(); + } + m_images.clear(); + } + void insert(const std::string &name, video::IImage *img, + bool prefer_local, video::IVideoDriver *driver) + { + assert(img); // Pre-condition + // Remove old image + std::map::iterator n; + n = m_images.find(name); + if (n != m_images.end()){ + if (n->second) + n->second->drop(); + } + + video::IImage* toadd = img; + bool need_to_grab = true; + + // Try to use local texture instead if asked to + if (prefer_local){ + std::string path = getTexturePath(name); + if (path != ""){ + video::IImage *img2 = driver->createImageFromFile(path.c_str()); + if (img2){ + toadd = img2; + need_to_grab = false; + } + } + } + + if (need_to_grab) + toadd->grab(); + m_images[name] = toadd; + } + video::IImage* get(const std::string &name) + { + std::map::iterator n; + n = m_images.find(name); + if (n != m_images.end()) + return n->second; + return NULL; + } + // Primarily fetches from cache, secondarily tries to read from filesystem + video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device) + { + std::map::iterator n; + n = m_images.find(name); + if (n != m_images.end()){ + n->second->grab(); // Grab for caller + return n->second; + } + video::IVideoDriver* driver = device->getVideoDriver(); + std::string path = getTexturePath(name); + if (path == ""){ + infostream<<"SourceImageCache::getOrLoad(): No path found for \"" + <createImageFromFile(path.c_str()); + + if (img){ + m_images[name] = img; + img->grab(); // Grab for caller + } + return img; + } +private: + std::map m_images; +}; + +/* + TextureSource +*/ + +class TextureSource : public IWritableTextureSource +{ +public: + TextureSource(IrrlichtDevice *device); + virtual ~TextureSource(); + + /* + Example case: + Now, assume a texture with the id 1 exists, and has the name + "stone.png^mineral1". + Then a random thread calls getTextureId for a texture called + "stone.png^mineral1^crack0". + ...Now, WTF should happen? Well: + - getTextureId strips off stuff recursively from the end until + the remaining part is found, or nothing is left when + something is stripped out + + But it is slow to search for textures by names and modify them + like that? + - ContentFeatures is made to contain ids for the basic plain + textures + - Crack textures can be slow by themselves, but the framework + must be fast. + + Example case #2: + - Assume a texture with the id 1 exists, and has the name + "stone.png^mineral_coal.png". + - Now getNodeTile() stumbles upon a node which uses + texture id 1, and determines that MATERIAL_FLAG_CRACK + must be applied to the tile + - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and + has received the current crack level 0 from the client. It + finds out the name of the texture with getTextureName(1), + appends "^crack0" to it and gets a new texture id with + getTextureId("stone.png^mineral_coal.png^crack0"). + + */ + + /* + Gets a texture id from cache or + - if main thread, generates the texture, adds to cache and returns id. + - if other thread, adds to request queue and waits for main thread. + + The id 0 points to a NULL texture. It is returned in case of error. + */ + u32 getTextureId(const std::string &name); + + // Finds out the name of a cached texture. + std::string getTextureName(u32 id); + + /* + If texture specified by the name pointed by the id doesn't + exist, create it, then return the cached texture. + + Can be called from any thread. If called from some other thread + and not found in cache, the call is queued to the main thread + for processing. + */ + video::ITexture* getTexture(u32 id); + + video::ITexture* getTexture(const std::string &name, u32 *id); + + // Returns a pointer to the irrlicht device + virtual IrrlichtDevice* getDevice() + { + return m_device; + } + + bool isKnownSourceImage(const std::string &name) + { + bool is_known = false; + bool cache_found = m_source_image_existence.get(name, &is_known); + if (cache_found) + return is_known; + // Not found in cache; find out if a local file exists + is_known = (getTexturePath(name) != ""); + m_source_image_existence.set(name, is_known); + return is_known; + } + + // Processes queued texture requests from other threads. + // Shall be called from the main thread. + void processQueue(); + + // Insert an image into the cache without touching the filesystem. + // Shall be called from the main thread. + void insertSourceImage(const std::string &name, video::IImage *img); + + // Rebuild images and textures from the current set of source images + // Shall be called from the main thread. + void rebuildImagesAndTextures(); + + // Render a mesh to a texture. + // Returns NULL if render-to-texture failed. + // Shall be called from the main thread. + video::ITexture* generateTextureFromMesh( + const TextureFromMeshParams ¶ms); + + // Generates an image from a full string like + // "stone.png^mineral_coal.png^[crack:1:0". + // Shall be called from the main thread. + video::IImage* generateImage(const std::string &name); + + video::ITexture* getNormalTexture(const std::string &name); +private: + + // The id of the thread that is allowed to use irrlicht directly + threadid_t m_main_thread; + // The irrlicht device + IrrlichtDevice *m_device; + + // Cache of source images + // This should be only accessed from the main thread + SourceImageCache m_sourcecache; + + // Generate a texture + u32 generateTexture(const std::string &name); + + // Generate image based on a string like "stone.png" or "[crack:1:0". + // if baseimg is NULL, it is created. Otherwise stuff is made on it. + bool generateImagePart(std::string part_of_name, video::IImage *& baseimg); + + // Thread-safe cache of what source images are known (true = known) + MutexedMap m_source_image_existence; + + // A texture id is index in this array. + // The first position contains a NULL texture. + std::vector m_textureinfo_cache; + // Maps a texture name to an index in the former. + std::map m_name_to_id; + // The two former containers are behind this mutex + JMutex m_textureinfo_cache_mutex; + + // Queued texture fetches (to be processed by the main thread) + RequestQueue m_get_texture_queue; + + // Textures that have been overwritten with other ones + // but can't be deleted because the ITexture* might still be used + std::vector m_texture_trash; + + // Cached settings needed for making textures from meshes + bool m_setting_trilinear_filter; + bool m_setting_bilinear_filter; + bool m_setting_anisotropic_filter; +}; + +IWritableTextureSource* createTextureSource(IrrlichtDevice *device) +{ + return new TextureSource(device); +} + +TextureSource::TextureSource(IrrlichtDevice *device): + m_device(device) +{ + assert(m_device); // Pre-condition + + m_main_thread = get_current_thread_id(); + + // Add a NULL TextureInfo as the first index, named "" + m_textureinfo_cache.push_back(TextureInfo("")); + m_name_to_id[""] = 0; + + // Cache some settings + // Note: Since this is only done once, the game must be restarted + // for these settings to take effect + m_setting_trilinear_filter = g_settings->getBool("trilinear_filter"); + m_setting_bilinear_filter = g_settings->getBool("bilinear_filter"); + m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter"); +} + +TextureSource::~TextureSource() +{ + video::IVideoDriver* driver = m_device->getVideoDriver(); + + unsigned int textures_before = driver->getTextureCount(); + + for (std::vector::iterator iter = + m_textureinfo_cache.begin(); + iter != m_textureinfo_cache.end(); iter++) + { + //cleanup texture + if (iter->texture) + driver->removeTexture(iter->texture); + } + m_textureinfo_cache.clear(); + + for (std::vector::iterator iter = + m_texture_trash.begin(); iter != m_texture_trash.end(); + iter++) { + video::ITexture *t = *iter; + + //cleanup trashed texture + driver->removeTexture(t); + } + + infostream << "~TextureSource() "<< textures_before << "/" + << driver->getTextureCount() << std::endl; +} + +u32 TextureSource::getTextureId(const std::string &name) +{ + //infostream<<"getTextureId(): \""<::iterator n; + n = m_name_to_id.find(name); + if (n != m_name_to_id.end()) + { + return n->second; + } + } + + /* + Get texture + */ + if (get_current_thread_id() == m_main_thread) + { + return generateTexture(name); + } + else + { + infostream<<"getTextureId(): Queued: name=\""< result_queue; + + // Throw a request in + m_get_texture_queue.add(name, 0, 0, &result_queue); + + /*infostream<<"Waiting for texture from main thread, name=\"" + < + result = result_queue.pop_front(1000); + + if (result.key == name) { + return result.item; + } + } + } + catch(ItemNotFoundException &e) + { + errorstream<<"Waiting for texture " << name << " timed out."< imageTransformDimension(u32 transform, core::dimension2d dim); +// Apply transform to image data +void imageTransform(u32 transform, video::IImage *src, video::IImage *dst); + +/* + This method generates all the textures +*/ +u32 TextureSource::generateTexture(const std::string &name) +{ + //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl; + + // Empty name means texture 0 + if (name == "") { + infostream<<"generateTexture(): name is empty"<::iterator n; + n = m_name_to_id.find(name); + if (n != m_name_to_id.end()) { + return n->second; + } + } + + /* + Calling only allowed from main thread + */ + if (get_current_thread_id() != m_main_thread) { + errorstream<<"TextureSource::generateTexture() " + "called not from main thread"<getVideoDriver(); + sanity_check(driver); + + video::IImage *img = generateImage(name); + + video::ITexture *tex = NULL; + + if (img != NULL) { +#ifdef __ANDROID__ + img = Align2Npot2(img, driver); +#endif + // Create texture from resulting image + tex = driver->addTexture(name.c_str(), img); + img->drop(); + } + + /* + Add texture to caches (add NULL textures too) + */ + + JMutexAutoLock lock(m_textureinfo_cache_mutex); + + u32 id = m_textureinfo_cache.size(); + TextureInfo ti(name, tex); + m_textureinfo_cache.push_back(ti); + m_name_to_id[name] = id; + + return id; +} + +std::string TextureSource::getTextureName(u32 id) +{ + JMutexAutoLock lock(m_textureinfo_cache_mutex); + + if (id >= m_textureinfo_cache.size()) + { + errorstream<<"TextureSource::getTextureName(): id="<= m_textureinfo_cache.size()=" + <= m_textureinfo_cache.size()) + return NULL; + + return m_textureinfo_cache[id].texture; +} + +video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id) +{ + u32 actual_id = getTextureId(name); + if (id){ + *id = actual_id; + } + return getTexture(actual_id); +} + +void TextureSource::processQueue() +{ + /* + Fetch textures + */ + //NOTE this is only thread safe for ONE consumer thread! + if (!m_get_texture_queue.empty()) + { + GetRequest + request = m_get_texture_queue.pop(); + + /*infostream<<"TextureSource::processQueue(): " + <<"got texture request with " + <<"name=\""<getBool("inventory_image_hack") + ) { + // Get a scene manager + scene::ISceneManager *smgr_main = m_device->getSceneManager(); + sanity_check(smgr_main); + scene::ISceneManager *smgr = smgr_main->createNewSceneManager(); + sanity_check(smgr); + + const float scaling = 0.2; + + scene::IMeshSceneNode* meshnode = + smgr->addMeshSceneNode(params.mesh, NULL, + -1, v3f(0,0,0), v3f(0,0,0), + v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true); + meshnode->setMaterialFlag(video::EMF_LIGHTING, true); + meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true); + meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter); + meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter); + meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter); + + scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0, + params.camera_position, params.camera_lookat); + // second parameter of setProjectionMatrix (isOrthogonal) is ignored + camera->setProjectionMatrix(params.camera_projection_matrix, false); + + smgr->setAmbientLight(params.ambient_light); + smgr->addLightSceneNode(0, + params.light_position, + params.light_color, + params.light_radius*scaling); + + core::dimension2d screen = driver->getScreenSize(); + + // Render scene + driver->beginScene(true, true, video::SColor(0,0,0,0)); + driver->clearZBuffer(); + smgr->drawAll(); + + core::dimension2d partsize(screen.Width * scaling,screen.Height * scaling); + + irr::video::IImage* rawImage = + driver->createImage(irr::video::ECF_A8R8G8B8, partsize); + + u8* pixels = static_cast(rawImage->lock()); + if (!pixels) + { + rawImage->drop(); + return NULL; + } + + core::rect source( + screen.Width /2 - (screen.Width * (scaling / 2)), + screen.Height/2 - (screen.Height * (scaling / 2)), + screen.Width /2 + (screen.Width * (scaling / 2)), + screen.Height/2 + (screen.Height * (scaling / 2)) + ); + + glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y, + partsize.Width, partsize.Height, GL_RGBA, + GL_UNSIGNED_BYTE, pixels); + + driver->endScene(); + + // Drop scene manager + smgr->drop(); + + unsigned int pixelcount = partsize.Width*partsize.Height; + + u8* runptr = pixels; + for (unsigned int i=0; i < pixelcount; i++) { + + u8 B = *runptr; + u8 G = *(runptr+1); + u8 R = *(runptr+2); + u8 A = *(runptr+3); + + //BGRA -> RGBA + *runptr = R; + runptr ++; + *runptr = G; + runptr ++; + *runptr = B; + runptr ++; + *runptr = A; + runptr ++; + } + + video::IImage* inventory_image = + driver->createImage(irr::video::ECF_A8R8G8B8, params.dim); + + rawImage->copyToScaling(inventory_image); + rawImage->drop(); + + video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image); + inventory_image->drop(); + + if (rtt == NULL) { + errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl; + return NULL; + } + + driver->makeColorKeyTexture(rtt, v2s32(0,0)); + + if (params.delete_texture_on_shutdown) + m_texture_trash.push_back(rtt); + + return rtt; + } +#endif + + if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false) + { + static bool warned = false; + if (!warned) + { + errorstream<<"TextureSource::generateTextureFromMesh(): " + <<"EVDF_RENDER_TO_TARGET not supported."<addRenderTargetTexture( + params.dim, params.rtt_texture_name.c_str(), + video::ECF_A8R8G8B8); + if (rtt == NULL) + { + errorstream<<"TextureSource::generateTextureFromMesh(): " + <<"addRenderTargetTexture returned NULL."<setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) { + driver->removeTexture(rtt); + errorstream<<"TextureSource::generateTextureFromMesh(): " + <<"failed to set render target"<getSceneManager(); + assert(smgr_main); + scene::ISceneManager *smgr = smgr_main->createNewSceneManager(); + assert(smgr); + + scene::IMeshSceneNode* meshnode = + smgr->addMeshSceneNode(params.mesh, NULL, + -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true); + meshnode->setMaterialFlag(video::EMF_LIGHTING, true); + meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true); + meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter); + meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter); + meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter); + + scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0, + params.camera_position, params.camera_lookat); + // second parameter of setProjectionMatrix (isOrthogonal) is ignored + camera->setProjectionMatrix(params.camera_projection_matrix, false); + + smgr->setAmbientLight(params.ambient_light); + smgr->addLightSceneNode(0, + params.light_position, + params.light_color, + params.light_radius); + + // Render scene + driver->beginScene(true, true, video::SColor(0,0,0,0)); + smgr->drawAll(); + driver->endScene(); + + // Drop scene manager + smgr->drop(); + + // Unset render target + driver->setRenderTarget(0, false, true, 0); + + if (params.delete_texture_on_shutdown) + m_texture_trash.push_back(rtt); + + return rtt; +} + +video::IImage* TextureSource::generateImage(const std::string &name) +{ + /* + Get the base image + */ + + const char separator = '^'; + const char paren_open = '('; + const char paren_close = ')'; + + // Find last separator in the name + s32 last_separator_pos = -1; + u8 paren_bal = 0; + for (s32 i = name.size() - 1; i >= 0; i--) { + switch(name[i]) { + case separator: + if (paren_bal == 0) { + last_separator_pos = i; + i = -1; // break out of loop + } + break; + case paren_open: + if (paren_bal == 0) { + errorstream << "generateImage(): unbalanced parentheses" + << "(extranous '(') while generating texture \"" + << name << "\"" << std::endl; + return NULL; + } + paren_bal--; + break; + case paren_close: + paren_bal++; + break; + default: + break; + } + } + if (paren_bal > 0) { + errorstream << "generateImage(): unbalanced parentheses" + << "(missing matching '(') while generating texture \"" + << name << "\"" << std::endl; + return NULL; + } + + + video::IImage *baseimg = NULL; + + /* + If separator was found, make the base image + using a recursive call. + */ + if (last_separator_pos != -1) { + baseimg = generateImage(name.substr(0, last_separator_pos)); + } + + + video::IVideoDriver* driver = m_device->getVideoDriver(); + sanity_check(driver); + + /* + Parse out the last part of the name of the image and act + according to it + */ + + std::string last_part_of_name = name.substr(last_separator_pos + 1); + + /* + If this name is enclosed in parentheses, generate it + and blit it onto the base image + */ + if (last_part_of_name[0] == paren_open + && last_part_of_name[last_part_of_name.size() - 1] == paren_close) { + std::string name2 = last_part_of_name.substr(1, + last_part_of_name.size() - 2); + video::IImage *tmp = generateImage(name2); + if (!tmp) { + errorstream << "generateImage(): " + "Failed to generate \"" << name2 << "\"" + << std::endl; + return NULL; + } + core::dimension2d dim = tmp->getDimension(); + if (!baseimg) + baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); + blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim); + tmp->drop(); + } else if (!generateImagePart(last_part_of_name, baseimg)) { + // Generate image according to part of name + errorstream << "generateImage(): " + "Failed to generate \"" << last_part_of_name << "\"" + << std::endl; + } + + // If no resulting image, print a warning + if (baseimg == NULL) { + errorstream << "generateImage(): baseimg is NULL (attempted to" + " create texture \"" << name << "\")" << std::endl; + } + + return baseimg; +} + +#ifdef __ANDROID__ +#include +/** + * Check and align image to npot2 if required by hardware + * @param image image to check for npot2 alignment + * @param driver driver to use for image operations + * @return image or copy of image aligned to npot2 + */ +video::IImage * Align2Npot2(video::IImage * image, + video::IVideoDriver* driver) +{ + if (image == NULL) { + return image; + } + + core::dimension2d dim = image->getDimension(); + + std::string extensions = (char*) glGetString(GL_EXTENSIONS); + if (extensions.find("GL_OES_texture_npot") != std::string::npos) { + return image; + } + + unsigned int height = npot2(dim.Height); + unsigned int width = npot2(dim.Width); + + if ((dim.Height == height) && + (dim.Width == width)) { + return image; + } + + if (dim.Height > height) { + height *= 2; + } + + if (dim.Width > width) { + width *= 2; + } + + video::IImage *targetimage = + driver->createImage(video::ECF_A8R8G8B8, + core::dimension2d(width, height)); + + if (targetimage != NULL) { + image->copyToScaling(targetimage); + } + image->drop(); + return targetimage; +} + +#endif + +bool TextureSource::generateImagePart(std::string part_of_name, + video::IImage *& baseimg) +{ + video::IVideoDriver* driver = m_device->getVideoDriver(); + sanity_check(driver); + + // Stuff starting with [ are special commands + if (part_of_name.size() == 0 || part_of_name[0] != '[') + { + video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device); +#ifdef __ANDROID__ + image = Align2Npot2(image, driver); +#endif + if (image == NULL) { + if (part_of_name != "") { + if (part_of_name.find("_normal.png") == std::string::npos){ + errorstream<<"generateImage(): Could not load image \"" + < dim(2,2); + core::dimension2d dim(1,1); + image = driver->createImage(video::ECF_A8R8G8B8, dim); + sanity_check(image != NULL); + /*image->setPixel(0,0, video::SColor(255,255,0,0)); + image->setPixel(1,0, video::SColor(255,0,255,0)); + image->setPixel(0,1, video::SColor(255,0,0,255)); + image->setPixel(1,1, video::SColor(255,255,0,255));*/ + image->setPixel(0,0, video::SColor(255,myrand()%256, + myrand()%256,myrand()%256)); + /*image->setPixel(1,0, video::SColor(255,myrand()%256, + myrand()%256,myrand()%256)); + image->setPixel(0,1, video::SColor(255,myrand()%256, + myrand()%256,myrand()%256)); + image->setPixel(1,1, video::SColor(255,myrand()%256, + myrand()%256,myrand()%256));*/ + } + + // If base image is NULL, load as base. + if (baseimg == NULL) + { + //infostream<<"Setting "< dim = image->getDimension(); + baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); + image->copyTo(baseimg); + } + // Else blit on base. + else + { + //infostream<<"Blitting "< dim = image->getDimension(); + //core::dimension2d dim(16,16); + // Position to copy the blitted to in the base image + core::position2d pos_to(0,0); + // Position to copy the blitted from in the blitted image + core::position2d pos_from(0,0); + // Blit + /*image->copyToWithAlpha(baseimg, pos_to, + core::rect(pos_from, dim), + video::SColor(255,255,255,255), + NULL);*/ + blit_with_alpha(image, baseimg, pos_from, pos_to, dim); + } + //cleanup + image->drop(); + } + else + { + // A special texture modification + + /*infostream<<"generateImage(): generating special " + <<"modification \""<= 0) + { + draw_crack(img_crack, baseimg, + use_overlay, frame_count, + progression, driver); + img_crack->drop(); + } + } + /* + [combine:WxH:X,Y=filename:X,Y=filename2 + Creates a bigger texture from an amount of smaller ones + */ + else if (part_of_name.substr(0,8) == "[combine") + { + Strfnd sf(part_of_name); + sf.next(":"); + u32 w0 = stoi(sf.next("x")); + u32 h0 = stoi(sf.next(":")); + //infostream<<"combined w="< dim = img->getDimension(); + infostream<<"Size "< pos_base(x, y); + video::IImage *img2 = + driver->createImage(video::ECF_A8R8G8B8, dim); + img->copyTo(img2); + img->drop(); + /*img2->copyToWithAlpha(baseimg, pos_base, + core::rect(v2s32(0,0), dim), + video::SColor(255,255,255,255), + NULL);*/ + blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim); + img2->drop(); + } else { + errorstream << "generateImagePart(): Failed to load image \"" + << filename << "\" for [combine" << std::endl; + } + } + } + /* + "[brighten" + */ + else if (part_of_name.substr(0,9) == "[brighten") + { + if (baseimg == NULL) { + errorstream<<"generateImagePart(): baseimg==NULL " + <<"for part_of_name=\""< dim = baseimg->getDimension(); + + // Set alpha to full + for (u32 y=0; ygetPixel(x,y); + c.setAlpha(255); + baseimg->setPixel(x,y,c); + } + } + /* + "[makealpha:R,G,B" + Convert one color to transparent. + */ + else if (part_of_name.substr(0,11) == "[makealpha:") + { + if (baseimg == NULL) { + errorstream<<"generateImagePart(): baseimg == NULL " + <<"for part_of_name=\""< dim = baseimg->getDimension(); + + /*video::IImage *oldbaseimg = baseimg; + baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); + oldbaseimg->copyTo(baseimg); + oldbaseimg->drop();*/ + + // Set alpha to full + for (u32 y=0; ygetPixel(x,y); + u32 r = c.getRed(); + u32 g = c.getGreen(); + u32 b = c.getBlue(); + if (!(r == r1 && g == g1 && b == b1)) + continue; + c.setAlpha(0); + baseimg->setPixel(x,y,c); + } + } + /* + "[transformN" + Rotates and/or flips the image. + + N can be a number (between 0 and 7) or a transform name. + Rotations are counter-clockwise. + 0 I identity + 1 R90 rotate by 90 degrees + 2 R180 rotate by 180 degrees + 3 R270 rotate by 270 degrees + 4 FX flip X + 5 FXR90 flip X then rotate by 90 degrees + 6 FY flip Y + 7 FYR90 flip Y then rotate by 90 degrees + + Note: Transform names can be concatenated to produce + their product (applies the first then the second). + The resulting transform will be equivalent to one of the + eight existing ones, though (see: dihedral group). + */ + else if (part_of_name.substr(0,10) == "[transform") + { + if (baseimg == NULL) { + errorstream<<"generateImagePart(): baseimg == NULL " + <<"for part_of_name=\""< dim = imageTransformDimension( + transform, baseimg->getDimension()); + video::IImage *image = driver->createImage( + baseimg->getColorFormat(), dim); + sanity_check(image != NULL); + imageTransform(transform, baseimg, image); + baseimg->drop(); + baseimg = image; + } + /* + [inventorycube{topimage{leftimage{rightimage + In every subimage, replace ^ with &. + Create an "inventory cube". + NOTE: This should be used only on its own. + Example (a grass block (not actually used in game): + "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png" + */ + else if (part_of_name.substr(0,14) == "[inventorycube") + { + if (baseimg != NULL){ + errorstream<<"generateImagePart(): baseimg != NULL " + <<"for part_of_name=\""<getDimension().Height == npot2(img_top->getDimension().Height)); + assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width)); + + assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height)); + assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width)); + + assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height)); + assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width)); +#endif + + // Create textures from images + video::ITexture *texture_top = driver->addTexture( + (imagename_top + "__temp__").c_str(), img_top); + video::ITexture *texture_left = driver->addTexture( + (imagename_left + "__temp__").c_str(), img_left); + video::ITexture *texture_right = driver->addTexture( + (imagename_right + "__temp__").c_str(), img_right); + FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), ""); + + // Drop images + img_top->drop(); + img_left->drop(); + img_right->drop(); + + /* + Draw a cube mesh into a render target texture + */ + scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1)); + setMeshColor(cube, video::SColor(255, 255, 255, 255)); + cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top); + cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top); + cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right); + cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right); + cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left); + cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left); + + TextureFromMeshParams params; + params.mesh = cube; + params.dim.set(64, 64); + params.rtt_texture_name = part_of_name + "_RTT"; + // We will delete the rtt texture ourselves + params.delete_texture_on_shutdown = false; + params.camera_position.set(0, 1.0, -1.5); + params.camera_position.rotateXZBy(45); + params.camera_lookat.set(0, 0, 0); + // Set orthogonal projection + params.camera_projection_matrix.buildProjectionMatrixOrthoLH( + 1.65, 1.65, 0, 100); + + params.ambient_light.set(1.0, 0.2, 0.2, 0.2); + params.light_position.set(10, 100, -50); + params.light_color.set(1.0, 0.5, 0.5, 0.5); + params.light_radius = 1000; + + video::ITexture *rtt = generateTextureFromMesh(params); + + // Drop mesh + cube->drop(); + + // Free textures + driver->removeTexture(texture_top); + driver->removeTexture(texture_left); + driver->removeTexture(texture_right); + + if (rtt == NULL) { + baseimg = generateImage(imagename_top); + return true; + } + + // Create image of render target + video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim); + FATAL_ERROR_IF(!image, "Could not create image of render target"); + + // Cleanup texture + driver->removeTexture(rtt); + + baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim); + + if (image) { + image->copyTo(baseimg); + image->drop(); + } + } + /* + [lowpart:percent:filename + Adds the lower part of a texture + */ + else if (part_of_name.substr(0,9) == "[lowpart:") + { + Strfnd sf(part_of_name); + sf.next(":"); + u32 percent = stoi(sf.next(":")); + std::string filename = sf.next(":"); + //infostream<<"power part "<createImage(video::ECF_A8R8G8B8, v2u32(16,16)); + video::IImage *img = m_sourcecache.getOrLoad(filename, m_device); + if (img) + { + core::dimension2d dim = img->getDimension(); + core::position2d pos_base(0, 0); + video::IImage *img2 = + driver->createImage(video::ECF_A8R8G8B8, dim); + img->copyTo(img2); + img->drop(); + core::position2d clippos(0, 0); + clippos.Y = dim.Height * (100-percent) / 100; + core::dimension2d clipdim = dim; + clipdim.Height = clipdim.Height * percent / 100 + 1; + core::rect cliprect(clippos, clipdim); + img2->copyToWithAlpha(baseimg, pos_base, + core::rect(v2s32(0,0), dim), + video::SColor(255,255,255,255), + &cliprect); + img2->drop(); + } + } + /* + [verticalframe:N:I + Crops a frame of a vertical animation. + N = frame count, I = frame index + */ + else if (part_of_name.substr(0,15) == "[verticalframe:") + { + Strfnd sf(part_of_name); + sf.next(":"); + u32 frame_count = stoi(sf.next(":")); + u32 frame_index = stoi(sf.next(":")); + + if (baseimg == NULL){ + errorstream<<"generateImagePart(): baseimg != NULL " + <<"for part_of_name=\""<getDimension(); + frame_size.Y /= frame_count; + + video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, + frame_size); + if (!img){ + errorstream<<"generateImagePart(): Could not create image " + <<"for part_of_name=\""<fill(video::SColor(0,0,0,0)); + + core::dimension2d dim = frame_size; + core::position2d pos_dst(0, 0); + core::position2d pos_src(0, frame_index * frame_size.Y); + baseimg->copyToWithAlpha(img, pos_dst, + core::rect(pos_src, dim), + video::SColor(255,255,255,255), + NULL); + // Replace baseimg + baseimg->drop(); + baseimg = img; + } + /* + [mask:filename + Applies a mask to an image + */ + else if (part_of_name.substr(0,6) == "[mask:") + { + if (baseimg == NULL) { + errorstream << "generateImage(): baseimg == NULL " + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; + return false; + } + Strfnd sf(part_of_name); + sf.next(":"); + std::string filename = sf.next(":"); + + video::IImage *img = m_sourcecache.getOrLoad(filename, m_device); + if (img) { + apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0), + img->getDimension()); + } else { + errorstream << "generateImage(): Failed to load \"" + << filename << "\"."; + } + } + /* + [colorize:color + Overlays image with given color + color = color as ColorString + */ + else if (part_of_name.substr(0,10) == "[colorize:") { + Strfnd sf(part_of_name); + sf.next(":"); + std::string color_str = sf.next(":"); + std::string ratio_str = sf.next(":"); + + if (baseimg == NULL) { + errorstream << "generateImagePart(): baseimg != NULL " + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; + return false; + } + + video::SColor color; + int ratio = -1; + + if (!parseColorString(color_str, color, false)) + return false; + + if (is_number(ratio_str)) + ratio = mystoi(ratio_str, 0, 255); + + core::dimension2d dim = baseimg->getDimension(); + video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim); + + if (!img) { + errorstream << "generateImagePart(): Could not create image " + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; + return false; + } + + img->fill(video::SColor(color)); + // Overlay the colored image + blit_with_interpolate_overlay(img, baseimg, v2s32(0,0), v2s32(0,0), dim, ratio); + img->drop(); + } + else + { + errorstream << "generateImagePart(): Invalid " + " modification: \"" << part_of_name << "\"" << std::endl; + } + } + + return true; +} + +/* + Draw an image on top of an another one, using the alpha channel of the + source image + + This exists because IImage::copyToWithAlpha() doesn't seem to always + work. +*/ +static void blit_with_alpha(video::IImage *src, video::IImage *dst, + v2s32 src_pos, v2s32 dst_pos, v2u32 size) +{ + for (u32 y0=0; y0getPixel(src_x, src_y); + video::SColor dst_c = dst->getPixel(dst_x, dst_y); + dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); + dst->setPixel(dst_x, dst_y, dst_c); + } +} + +/* + Draw an image on top of an another one, using the alpha channel of the + source image; only modify fully opaque pixels in destinaion +*/ +static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst, + v2s32 src_pos, v2s32 dst_pos, v2u32 size) +{ + for (u32 y0=0; y0getPixel(src_x, src_y); + video::SColor dst_c = dst->getPixel(dst_x, dst_y); + if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0) + { + dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); + dst->setPixel(dst_x, dst_y, dst_c); + } + } +} + +/* + Draw an image on top of an another one, using the specified ratio + modify all partially-opaque pixels in the destination. +*/ +static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst, + v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio) +{ + for (u32 y0 = 0; y0 < size.Y; y0++) + for (u32 x0 = 0; x0 < size.X; x0++) + { + s32 src_x = src_pos.X + x0; + s32 src_y = src_pos.Y + y0; + s32 dst_x = dst_pos.X + x0; + s32 dst_y = dst_pos.Y + y0; + video::SColor src_c = src->getPixel(src_x, src_y); + video::SColor dst_c = dst->getPixel(dst_x, dst_y); + if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0) + { + if (ratio == -1) + dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); + else + dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f); + dst->setPixel(dst_x, dst_y, dst_c); + } + } +} + +/* + Apply mask to destination +*/ +static void apply_mask(video::IImage *mask, video::IImage *dst, + v2s32 mask_pos, v2s32 dst_pos, v2u32 size) +{ + for (u32 y0 = 0; y0 < size.Y; y0++) { + for (u32 x0 = 0; x0 < size.X; x0++) { + s32 mask_x = x0 + mask_pos.X; + s32 mask_y = y0 + mask_pos.Y; + s32 dst_x = x0 + dst_pos.X; + s32 dst_y = y0 + dst_pos.Y; + video::SColor mask_c = mask->getPixel(mask_x, mask_y); + video::SColor dst_c = dst->getPixel(dst_x, dst_y); + dst_c.color &= mask_c.color; + dst->setPixel(dst_x, dst_y, dst_c); + } + } +} + +static void draw_crack(video::IImage *crack, video::IImage *dst, + bool use_overlay, s32 frame_count, s32 progression, + video::IVideoDriver *driver) +{ + // Dimension of destination image + core::dimension2d dim_dst = dst->getDimension(); + // Dimension of original image + core::dimension2d dim_crack = crack->getDimension(); + // Count of crack stages + s32 crack_count = dim_crack.Height / dim_crack.Width; + // Limit frame_count + if (frame_count > (s32) dim_dst.Height) + frame_count = dim_dst.Height; + if (frame_count < 1) + frame_count = 1; + // Limit progression + if (progression > crack_count-1) + progression = crack_count-1; + // Dimension of a single crack stage + core::dimension2d dim_crack_cropped( + dim_crack.Width, + dim_crack.Width + ); + // Dimension of the scaled crack stage, + // which is the same as the dimension of a single destination frame + core::dimension2d dim_crack_scaled( + dim_dst.Width, + dim_dst.Height / frame_count + ); + // Create cropped and scaled crack images + video::IImage *crack_cropped = driver->createImage( + video::ECF_A8R8G8B8, dim_crack_cropped); + video::IImage *crack_scaled = driver->createImage( + video::ECF_A8R8G8B8, dim_crack_scaled); + + if (crack_cropped && crack_scaled) + { + // Crop crack image + v2s32 pos_crack(0, progression*dim_crack.Width); + crack->copyTo(crack_cropped, + v2s32(0,0), + core::rect(pos_crack, dim_crack_cropped)); + // Scale crack image by copying + crack_cropped->copyToScaling(crack_scaled); + // Copy or overlay crack image onto each frame + for (s32 i = 0; i < frame_count; ++i) + { + v2s32 dst_pos(0, dim_crack_scaled.Height * i); + if (use_overlay) + { + blit_with_alpha_overlay(crack_scaled, dst, + v2s32(0,0), dst_pos, + dim_crack_scaled); + } + else + { + blit_with_alpha(crack_scaled, dst, + v2s32(0,0), dst_pos, + dim_crack_scaled); + } + } + } + + if (crack_scaled) + crack_scaled->drop(); + + if (crack_cropped) + crack_cropped->drop(); +} + +void brighten(video::IImage *image) +{ + if (image == NULL) + return; + + core::dimension2d dim = image->getDimension(); + + for (u32 y=0; ygetPixel(x,y); + c.setRed(0.5 * 255 + 0.5 * (float)c.getRed()); + c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen()); + c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue()); + image->setPixel(x,y,c); + } +} + +u32 parseImageTransform(const std::string& s) +{ + int total_transform = 0; + + std::string transform_names[8]; + transform_names[0] = "i"; + transform_names[1] = "r90"; + transform_names[2] = "r180"; + transform_names[3] = "r270"; + transform_names[4] = "fx"; + transform_names[6] = "fy"; + + std::size_t pos = 0; + while(pos < s.size()) + { + int transform = -1; + for (int i = 0; i <= 7; ++i) + { + const std::string &name_i = transform_names[i]; + + if (s[pos] == ('0' + i)) + { + transform = i; + pos++; + break; + } + else if (!(name_i.empty()) && + lowercase(s.substr(pos, name_i.size())) == name_i) + { + transform = i; + pos += name_i.size(); + break; + } + } + if (transform < 0) + break; + + // Multiply total_transform and transform in the group D4 + int new_total = 0; + if (transform < 4) + new_total = (transform + total_transform) % 4; + else + new_total = (transform - total_transform + 8) % 4; + if ((transform >= 4) ^ (total_transform >= 4)) + new_total += 4; + + total_transform = new_total; + } + return total_transform; +} + +core::dimension2d imageTransformDimension(u32 transform, core::dimension2d dim) +{ + if (transform % 2 == 0) + return dim; + else + return core::dimension2d(dim.Height, dim.Width); +} + +void imageTransform(u32 transform, video::IImage *src, video::IImage *dst) +{ + if (src == NULL || dst == NULL) + return; + + core::dimension2d dstdim = dst->getDimension(); + + // Pre-conditions + assert(dstdim == imageTransformDimension(transform, src->getDimension())); + assert(transform <= 7); + + /* + Compute the transformation from source coordinates (sx,sy) + to destination coordinates (dx,dy). + */ + int sxn = 0; + int syn = 2; + if (transform == 0) // identity + sxn = 0, syn = 2; // sx = dx, sy = dy + else if (transform == 1) // rotate by 90 degrees ccw + sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx + else if (transform == 2) // rotate by 180 degrees + sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy + else if (transform == 3) // rotate by 270 degrees ccw + sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx + else if (transform == 4) // flip x + sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy + else if (transform == 5) // flip x then rotate by 90 degrees ccw + sxn = 2, syn = 0; // sx = dy, sy = dx + else if (transform == 6) // flip y + sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy + else if (transform == 7) // flip y then rotate by 90 degrees ccw + sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx + + for (u32 dy=0; dygetPixel(sx,sy); + dst->setPixel(dx,dy,c); + } +} + +video::ITexture* TextureSource::getNormalTexture(const std::string &name) +{ + u32 id; + if (isKnownSourceImage("override_normal.png")) + return getTexture("override_normal.png", &id); + std::string fname_base = name; + std::string normal_ext = "_normal.png"; + size_t pos = fname_base.find("."); + std::string fname_normal = fname_base.substr(0, pos) + normal_ext; + if (isKnownSourceImage(fname_normal)) { + // look for image extension and replace it + size_t i = 0; + while ((i = fname_base.find(".", i)) != std::string::npos) { + fname_base.replace(i, 4, normal_ext); + i += normal_ext.length(); + } + return getTexture(fname_base, &id); + } + return NULL; +} diff --git a/src/client/tile.h b/src/client/tile.h new file mode 100644 index 0000000..ea7a913 --- /dev/null +++ b/src/client/tile.h @@ -0,0 +1,280 @@ +/* +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. +*/ + +#ifndef TILE_HEADER +#define TILE_HEADER + +#include "irrlichttypes.h" +#include "irr_v2d.h" +#include "irr_v3d.h" +#include +#include +#include "threads.h" +#include +#include + +class IGameDef; + +/* + tile.{h,cpp}: Texture handling stuff. +*/ + +/* + Find out the full path of an image by trying different filename + extensions. + + If failed, return "". + + TODO: Should probably be moved out from here, because things needing + this function do not need anything else from this header +*/ +std::string getImagePath(std::string path); + +/* + Gets the path to a texture by first checking if the texture exists + in texture_path and if not, using the data path. + + Checks all supported extensions by replacing the original extension. + + If not found, returns "". + + Utilizes a thread-safe cache. +*/ +std::string getTexturePath(const std::string &filename); + +void clearTextureNameCache(); + +/* + ITextureSource::generateTextureFromMesh parameters +*/ +namespace irr {namespace scene {class IMesh;}} +struct TextureFromMeshParams +{ + scene::IMesh *mesh; + core::dimension2d dim; + std::string rtt_texture_name; + bool delete_texture_on_shutdown; + v3f camera_position; + v3f camera_lookat; + core::CMatrix4 camera_projection_matrix; + video::SColorf ambient_light; + v3f light_position; + video::SColorf light_color; + f32 light_radius; +}; + +/* + TextureSource creates and caches textures. +*/ + +class ISimpleTextureSource +{ +public: + ISimpleTextureSource(){} + virtual ~ISimpleTextureSource(){} + virtual video::ITexture* getTexture( + const std::string &name, u32 *id = NULL) = 0; +}; + +class ITextureSource : public ISimpleTextureSource +{ +public: + ITextureSource(){} + virtual ~ITextureSource(){} + virtual u32 getTextureId(const std::string &name)=0; + virtual std::string getTextureName(u32 id)=0; + virtual video::ITexture* getTexture(u32 id)=0; + virtual video::ITexture* getTexture( + const std::string &name, u32 *id = NULL)=0; + virtual IrrlichtDevice* getDevice()=0; + virtual bool isKnownSourceImage(const std::string &name)=0; + virtual video::ITexture* generateTextureFromMesh( + const TextureFromMeshParams ¶ms)=0; + virtual video::ITexture* getNormalTexture(const std::string &name)=0; +}; + +class IWritableTextureSource : public ITextureSource +{ +public: + IWritableTextureSource(){} + virtual ~IWritableTextureSource(){} + virtual u32 getTextureId(const std::string &name)=0; + virtual std::string getTextureName(u32 id)=0; + virtual video::ITexture* getTexture(u32 id)=0; + virtual video::ITexture* getTexture( + const std::string &name, u32 *id = NULL)=0; + virtual IrrlichtDevice* getDevice()=0; + virtual bool isKnownSourceImage(const std::string &name)=0; + virtual video::ITexture* generateTextureFromMesh( + const TextureFromMeshParams ¶ms)=0; + + virtual void processQueue()=0; + virtual void insertSourceImage(const std::string &name, video::IImage *img)=0; + virtual void rebuildImagesAndTextures()=0; + virtual video::ITexture* getNormalTexture(const std::string &name)=0; +}; + +IWritableTextureSource* createTextureSource(IrrlichtDevice *device); + +#ifdef __ANDROID__ +/** + * @param size get next npot2 value + * @return npot2 value + */ +inline unsigned int npot2(unsigned int size) +{ + if (size == 0) return 0; + unsigned int npot = 1; + + while ((size >>= 1) > 0) { + npot <<= 1; + } + return npot; +} + +video::IImage * Align2Npot2(video::IImage * image, video::IVideoDriver* driver); +#endif + +enum MaterialType{ + TILE_MATERIAL_BASIC, + TILE_MATERIAL_ALPHA, + TILE_MATERIAL_LIQUID_TRANSPARENT, + TILE_MATERIAL_LIQUID_OPAQUE, + TILE_MATERIAL_WAVING_LEAVES, + TILE_MATERIAL_WAVING_PLANTS +}; + +// Material flags +// Should backface culling be enabled? +#define MATERIAL_FLAG_BACKFACE_CULLING 0x01 +// Should a crack be drawn? +#define MATERIAL_FLAG_CRACK 0x02 +// Should the crack be drawn on transparent pixels (unset) or not (set)? +// Ignored if MATERIAL_FLAG_CRACK is not set. +#define MATERIAL_FLAG_CRACK_OVERLAY 0x04 +// Animation made up by splitting the texture to vertical frames, as +// defined by extra parameters +#define MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES 0x08 +#define MATERIAL_FLAG_HIGHLIGHTED 0x10 + +/* + This fully defines the looks of a tile. + The SMaterial of a tile is constructed according to this. +*/ +struct FrameSpec +{ + FrameSpec(): + texture_id(0), + texture(NULL), + normal_texture(NULL) + { + } + u32 texture_id; + video::ITexture *texture; + video::ITexture *normal_texture; +}; + +struct TileSpec +{ + TileSpec(): + texture_id(0), + texture(NULL), + normal_texture(NULL), + alpha(255), + material_type(TILE_MATERIAL_BASIC), + material_flags( + //0 // <- DEBUG, Use the one below + MATERIAL_FLAG_BACKFACE_CULLING + ), + shader_id(0), + animation_frame_count(1), + animation_frame_length_ms(0), + rotation(0) + { + } + + bool operator==(const TileSpec &other) const + { + return ( + texture_id == other.texture_id && + /* texture == other.texture && */ + alpha == other.alpha && + material_type == other.material_type && + material_flags == other.material_flags && + rotation == other.rotation + ); + } + + bool operator!=(const TileSpec &other) const + { + return !(*this == other); + } + + // Sets everything else except the texture in the material + void applyMaterialOptions(video::SMaterial &material) const + { + switch (material_type) { + case TILE_MATERIAL_BASIC: + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + break; + case TILE_MATERIAL_ALPHA: + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + break; + case TILE_MATERIAL_LIQUID_TRANSPARENT: + material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; + break; + case TILE_MATERIAL_LIQUID_OPAQUE: + material.MaterialType = video::EMT_SOLID; + break; + case TILE_MATERIAL_WAVING_LEAVES: + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + break; + case TILE_MATERIAL_WAVING_PLANTS: + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + break; + } + material.BackfaceCulling = (material_flags & MATERIAL_FLAG_BACKFACE_CULLING) + ? true : false; + } + + void applyMaterialOptionsWithShaders(video::SMaterial &material) const + { + material.BackfaceCulling = (material_flags & MATERIAL_FLAG_BACKFACE_CULLING) + ? true : false; + } + + u32 texture_id; + video::ITexture *texture; + video::ITexture *normal_texture; + + // Vertex alpha (when MATERIAL_ALPHA_VERTEX is used) + u8 alpha; + // Material parameters + u8 material_type; + u8 material_flags; + u32 shader_id; + // Animation parameters + u8 animation_frame_count; + u16 animation_frame_length_ms; + std::vector frames; + + u8 rotation; +}; + +#endif diff --git a/src/clientiface.cpp b/src/clientiface.cpp new file mode 100644 index 0000000..3171e77 --- /dev/null +++ b/src/clientiface.cpp @@ -0,0 +1,806 @@ +/* +Minetest +Copyright (C) 2010-2014 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. +*/ + +#include + +#include "clientiface.h" +#include "util/numeric.h" +#include "util/mathconstants.h" +#include "player.h" +#include "settings.h" +#include "mapblock.h" +#include "network/connection.h" +#include "environment.h" +#include "map.h" +#include "emerge.h" +#include "serverobject.h" // TODO this is used for cleanup of only +#include "main.h" // for g_settings +#include "log.h" + +const char *ClientInterface::statenames[] = { + "Invalid", + "Disconnecting", + "Denied", + "Created", + "InitSent", + "InitDone", + "DefinitionsSent", + "Active" +}; + + + +std::string ClientInterface::state2Name(ClientState state) +{ + return statenames[state]; +} + +void RemoteClient::ResendBlockIfOnWire(v3s16 p) +{ + // if this block is on wire, mark it for sending again as soon as possible + if (m_blocks_sending.find(p) != m_blocks_sending.end()) { + SetBlockNotSent(p); + } +} + +void RemoteClient::GetNextBlocks ( + ServerEnvironment *env, + EmergeManager * emerge, + float dtime, + std::vector &dest) +{ + DSTACK(__FUNCTION_NAME); + + + // Increment timers + m_nothing_to_send_pause_timer -= dtime; + m_nearest_unsent_reset_timer += dtime; + + if(m_nothing_to_send_pause_timer >= 0) + return; + + Player *player = env->getPlayer(peer_id); + // This can happen sometimes; clients and players are not in perfect sync. + if(player == NULL) + return; + + // Won't send anything if already sending + if(m_blocks_sending.size() >= g_settings->getU16 + ("max_simultaneous_block_sends_per_client")) + { + //infostream<<"Not sending any blocks, Queue full."<getPosition(); + v3f playerspeed = player->getSpeed(); + v3f playerspeeddir(0,0,0); + if(playerspeed.getLength() > 1.0*BS) + playerspeeddir = playerspeed / playerspeed.getLength(); + // Predict to next block + v3f playerpos_predicted = playerpos + playerspeeddir*MAP_BLOCKSIZE*BS; + + v3s16 center_nodepos = floatToInt(playerpos_predicted, BS); + + v3s16 center = getNodeBlockPos(center_nodepos); + + // Camera position and direction + v3f camera_pos = player->getEyePosition(); + v3f camera_dir = v3f(0,0,1); + camera_dir.rotateYZBy(player->getPitch()); + camera_dir.rotateXZBy(player->getYaw()); + + /*infostream<<"camera_dir=("<getPlayerName(peer_id)<getFloat( + "full_block_send_enable_min_time_from_building")) + { + max_simul_sends_usually + = LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS; + } + + /* + Number of blocks sending + number of blocks selected for sending + */ + u32 num_blocks_selected = m_blocks_sending.size(); + + /* + next time d will be continued from the d from which the nearest + unsent block was found this time. + + This is because not necessarily any of the blocks found this + time are actually sent. + */ + s32 new_nearest_unsent_d = -1; + + const s16 full_d_max = g_settings->getS16("max_block_send_distance"); + s16 d_max = full_d_max; + s16 d_max_gen = g_settings->getS16("max_block_generate_distance"); + + // Don't loop very much at a time + s16 max_d_increment_at_time = 2; + if(d_max > d_start + max_d_increment_at_time) + d_max = d_start + max_d_increment_at_time; + + s32 nearest_emerged_d = -1; + s32 nearest_emergefull_d = -1; + s32 nearest_sent_d = -1; + //bool queue_is_full = false; + + s16 d; + for(d = d_start; d <= d_max; d++) { + /* + Get the border/face dot coordinates of a "d-radiused" + box + */ + std::vector list = FacePositionCache::getFacePositions(d); + + std::vector::iterator li; + for(li = list.begin(); li != list.end(); ++li) { + v3s16 p = *li + center; + + /* + Send throttling + - Don't allow too many simultaneous transfers + - EXCEPT when the blocks are very close + + Also, don't send blocks that are already flying. + */ + + // Start with the usual maximum + u16 max_simul_dynamic = max_simul_sends_usually; + + // If block is very close, allow full maximum + if(d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D) + max_simul_dynamic = max_simul_sends_setting; + + // Don't select too many blocks for sending + if(num_blocks_selected >= max_simul_dynamic) + { + //queue_is_full = true; + goto queue_full_break; + } + + // Don't send blocks that are currently being transferred + if(m_blocks_sending.find(p) != m_blocks_sending.end()) + continue; + + /* + Do not go over-limit + */ + if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE + || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE + || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE + || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE + || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE + || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE) + continue; + + // If this is true, inexistent block will be made from scratch + bool generate = d <= d_max_gen; + + { + /*// Limit the generating area vertically to 2/3 + if(abs(p.Y - center.Y) > d_max_gen - d_max_gen / 3) + generate = false;*/ + + // Limit the send area vertically to 1/2 + if(abs(p.Y - center.Y) > full_d_max / 2) + continue; + } + + /* + Don't generate or send if not in sight + FIXME This only works if the client uses a small enough + FOV setting. The default of 72 degrees is fine. + */ + + float camera_fov = (72.0*M_PI/180) * 4./3.; + if(isBlockInSight(p, camera_pos, camera_dir, camera_fov, 10000*BS) == false) + { + continue; + } + + /* + Don't send already sent blocks + */ + { + if(m_blocks_sent.find(p) != m_blocks_sent.end()) + { + continue; + } + } + + /* + Check if map has this block + */ + MapBlock *block = env->getMap().getBlockNoCreateNoEx(p); + + bool surely_not_found_on_disk = false; + bool block_is_invalid = false; + if(block != NULL) + { + // Reset usage timer, this block will be of use in the future. + block->resetUsageTimer(); + + // Block is dummy if data doesn't exist. + // It means it has been not found from disk and not generated + if(block->isDummy()) + { + surely_not_found_on_disk = true; + } + + // Block is valid if lighting is up-to-date and data exists + if(block->isValid() == false) + { + block_is_invalid = true; + } + + if(block->isGenerated() == false) + block_is_invalid = true; + + /* + If block is not close, don't send it unless it is near + ground level. + + Block is near ground level if night-time mesh + differs from day-time mesh. + */ + if(d >= 4) + { + if(block->getDayNightDiff() == false) + continue; + } + } + + /* + If block has been marked to not exist on disk (dummy) + and generating new ones is not wanted, skip block. + */ + if(generate == false && surely_not_found_on_disk == true) + { + // get next one. + continue; + } + + /* + Add inexistent block to emerge queue. + */ + if(block == NULL || surely_not_found_on_disk || block_is_invalid) + { + if (emerge->enqueueBlockEmerge(peer_id, p, generate)) { + if (nearest_emerged_d == -1) + nearest_emerged_d = d; + } else { + if (nearest_emergefull_d == -1) + nearest_emergefull_d = d; + goto queue_full_break; + } + + // get next one. + continue; + } + + if(nearest_sent_d == -1) + nearest_sent_d = d; + + /* + Add block to send queue + */ + PrioritySortedBlockTransfer q((float)d, p, peer_id); + + dest.push_back(q); + + num_blocks_selected += 1; + } + } +queue_full_break: + + // If nothing was found for sending and nothing was queued for + // emerging, continue next time browsing from here + if(nearest_emerged_d != -1){ + new_nearest_unsent_d = nearest_emerged_d; + } else if(nearest_emergefull_d != -1){ + new_nearest_unsent_d = nearest_emergefull_d; + } else { + if(d > g_settings->getS16("max_block_send_distance")){ + new_nearest_unsent_d = 0; + m_nothing_to_send_pause_timer = 2.0; + } else { + if(nearest_sent_d != -1) + new_nearest_unsent_d = nearest_sent_d; + else + new_nearest_unsent_d = d; + } + } + + if(new_nearest_unsent_d != -1) + m_nearest_unsent_d = new_nearest_unsent_d; +} + +void RemoteClient::GotBlock(v3s16 p) +{ + if(m_blocks_sending.find(p) != m_blocks_sending.end()) + m_blocks_sending.erase(p); + else + { + m_excess_gotblocks++; + } + m_blocks_sent.insert(p); +} + +void RemoteClient::SentBlock(v3s16 p) +{ + if(m_blocks_sending.find(p) == m_blocks_sending.end()) + m_blocks_sending[p] = 0.0; + else + infostream<<"RemoteClient::SentBlock(): Sent block" + " already in m_blocks_sending"< &blocks) +{ + m_nearest_unsent_d = 0; + + for(std::map::iterator + i = blocks.begin(); + i != blocks.end(); ++i) + { + v3s16 p = i->first; + + if(m_blocks_sending.find(p) != m_blocks_sending.end()) + m_blocks_sending.erase(p); + if(m_blocks_sent.find(p) != m_blocks_sent.end()) + m_blocks_sent.erase(p); + } +} + +void RemoteClient::notifyEvent(ClientStateEvent event) +{ + std::ostringstream myerror; + switch (m_state) + { + case CS_Invalid: + //intentionally do nothing + break; + case CS_Created: + switch(event) + { + case CSE_Init: + m_state = CS_InitSent; + break; + case CSE_Disconnect: + m_state = CS_Disconnecting; + break; + case CSE_SetDenied: + m_state = CS_Denied; + break; + /* GotInit2 SetDefinitionsSent SetMediaSent */ + default: + myerror << "Created: Invalid client state transition! " << event; + throw ClientStateError(myerror.str()); + } + break; + case CS_Denied: + /* don't do anything if in denied state */ + break; + case CS_InitSent: + switch(event) + { + case CSE_GotInit2: + confirmSerializationVersion(); + m_state = CS_InitDone; + break; + case CSE_Disconnect: + m_state = CS_Disconnecting; + break; + case CSE_SetDenied: + m_state = CS_Denied; + break; + + /* Init SetDefinitionsSent SetMediaSent */ + default: + myerror << "InitSent: Invalid client state transition! " << event; + throw ClientStateError(myerror.str()); + } + break; + + case CS_InitDone: + switch(event) + { + case CSE_SetDefinitionsSent: + m_state = CS_DefinitionsSent; + break; + case CSE_Disconnect: + m_state = CS_Disconnecting; + break; + case CSE_SetDenied: + m_state = CS_Denied; + break; + + /* Init GotInit2 SetMediaSent */ + default: + myerror << "InitDone: Invalid client state transition! " << event; + throw ClientStateError(myerror.str()); + } + break; + case CS_DefinitionsSent: + switch(event) + { + case CSE_SetClientReady: + m_state = CS_Active; + break; + case CSE_Disconnect: + m_state = CS_Disconnecting; + break; + case CSE_SetDenied: + m_state = CS_Denied; + break; + /* Init GotInit2 SetDefinitionsSent */ + default: + myerror << "DefinitionsSent: Invalid client state transition! " << event; + throw ClientStateError(myerror.str()); + } + break; + case CS_Active: + switch(event) + { + case CSE_SetDenied: + m_state = CS_Denied; + break; + case CSE_Disconnect: + m_state = CS_Disconnecting; + break; + /* Init GotInit2 SetDefinitionsSent SetMediaSent SetDenied */ + default: + myerror << "Active: Invalid client state transition! " << event; + throw ClientStateError(myerror.str()); + break; + } + break; + case CS_Disconnecting: + /* we are already disconnecting */ + break; + } +} + +u32 RemoteClient::uptime() +{ + return getTime(PRECISION_SECONDS) - m_connection_time; +} + +ClientInterface::ClientInterface(con::Connection* con) +: + m_con(con), + m_env(NULL), + m_print_info_timer(0.0) +{ + +} +ClientInterface::~ClientInterface() +{ + /* + Delete clients + */ + { + JMutexAutoLock clientslock(m_clients_mutex); + + for(std::map::iterator + i = m_clients.begin(); + i != m_clients.end(); ++i) + { + + // Delete client + delete i->second; + } + } +} + +std::vector ClientInterface::getClientIDs(ClientState min_state) +{ + std::vector reply; + JMutexAutoLock clientslock(m_clients_mutex); + + for(std::map::iterator + i = m_clients.begin(); + i != m_clients.end(); ++i) + { + if (i->second->getState() >= min_state) + reply.push_back(i->second->peer_id); + } + + return reply; +} + +std::vector ClientInterface::getPlayerNames() +{ + return m_clients_names; +} + + +void ClientInterface::step(float dtime) +{ + m_print_info_timer += dtime; + if(m_print_info_timer >= 30.0) + { + m_print_info_timer = 0.0; + UpdatePlayerList(); + } +} + +void ClientInterface::UpdatePlayerList() +{ + if (m_env != NULL) + { + std::vector clients = getClientIDs(); + m_clients_names.clear(); + + + if(!clients.empty()) + infostream<<"Players:"<::iterator + i = clients.begin(); + i != clients.end(); ++i) { + Player *player = m_env->getPlayer(*i); + + if (player == NULL) + continue; + + infostream << "* " << player->getName() << "\t"; + + { + JMutexAutoLock clientslock(m_clients_mutex); + RemoteClient* client = lockedGetClientNoEx(*i); + if(client != NULL) + client->PrintInfo(infostream); + } + + m_clients_names.push_back(player->getName()); + } + } +} + +void ClientInterface::send(u16 peer_id, u8 channelnum, + NetworkPacket* pkt, bool reliable) +{ + m_con->Send(peer_id, channelnum, pkt, reliable); +} + +void ClientInterface::sendToAll(u16 channelnum, + NetworkPacket* pkt, bool reliable) +{ + JMutexAutoLock clientslock(m_clients_mutex); + for(std::map::iterator + i = m_clients.begin(); + i != m_clients.end(); ++i) { + RemoteClient *client = i->second; + + if (client->net_proto_version != 0) { + m_con->Send(client->peer_id, channelnum, pkt, reliable); + } + } +} + +RemoteClient* ClientInterface::getClientNoEx(u16 peer_id, ClientState state_min) +{ + JMutexAutoLock clientslock(m_clients_mutex); + std::map::iterator n; + n = m_clients.find(peer_id); + // The client may not exist; clients are immediately removed if their + // access is denied, and this event occurs later then. + if(n == m_clients.end()) + return NULL; + + if (n->second->getState() >= state_min) + return n->second; + else + return NULL; +} + +RemoteClient* ClientInterface::lockedGetClientNoEx(u16 peer_id, ClientState state_min) +{ + std::map::iterator n; + n = m_clients.find(peer_id); + // The client may not exist; clients are immediately removed if their + // access is denied, and this event occurs later then. + if(n == m_clients.end()) + return NULL; + + if (n->second->getState() >= state_min) + return n->second; + else + return NULL; +} + +ClientState ClientInterface::getClientState(u16 peer_id) +{ + JMutexAutoLock clientslock(m_clients_mutex); + std::map::iterator n; + n = m_clients.find(peer_id); + // The client may not exist; clients are immediately removed if their + // access is denied, and this event occurs later then. + if(n == m_clients.end()) + return CS_Invalid; + + return n->second->getState(); +} + +void ClientInterface::setPlayerName(u16 peer_id,std::string name) +{ + JMutexAutoLock clientslock(m_clients_mutex); + std::map::iterator n; + n = m_clients.find(peer_id); + // The client may not exist; clients are immediately removed if their + // access is denied, and this event occurs later then. + if(n != m_clients.end()) + n->second->setName(name); +} + +void ClientInterface::DeleteClient(u16 peer_id) +{ + JMutexAutoLock conlock(m_clients_mutex); + + // Error check + std::map::iterator n; + n = m_clients.find(peer_id); + // The client may not exist; clients are immediately removed if their + // access is denied, and this event occurs later then. + if(n == m_clients.end()) + return; + + /* + Mark objects to be not known by the client + */ + //TODO this should be done by client destructor!!! + RemoteClient *client = n->second; + // Handle objects + for(std::set::iterator + i = client->m_known_objects.begin(); + i != client->m_known_objects.end(); ++i) + { + // Get object + u16 id = *i; + ServerActiveObject* obj = m_env->getActiveObject(id); + + if(obj && obj->m_known_by_count > 0) + obj->m_known_by_count--; + } + + // Delete client + delete m_clients[peer_id]; + m_clients.erase(peer_id); +} + +void ClientInterface::CreateClient(u16 peer_id) +{ + JMutexAutoLock conlock(m_clients_mutex); + + // Error check + std::map::iterator n; + n = m_clients.find(peer_id); + // The client shouldn't already exist + if(n != m_clients.end()) return; + + // Create client + RemoteClient *client = new RemoteClient(); + client->peer_id = peer_id; + m_clients[client->peer_id] = client; +} + +void ClientInterface::event(u16 peer_id, ClientStateEvent event) +{ + { + JMutexAutoLock clientlock(m_clients_mutex); + + // Error check + std::map::iterator n; + n = m_clients.find(peer_id); + + // No client to deliver event + if (n == m_clients.end()) + return; + n->second->notifyEvent(event); + } + + if ((event == CSE_SetClientReady) || + (event == CSE_Disconnect) || + (event == CSE_SetDenied)) + { + UpdatePlayerList(); + } +} + +u16 ClientInterface::getProtocolVersion(u16 peer_id) +{ + JMutexAutoLock conlock(m_clients_mutex); + + // Error check + std::map::iterator n; + n = m_clients.find(peer_id); + + // No client to get version + if (n == m_clients.end()) + return 0; + + return n->second->net_proto_version; +} + +void ClientInterface::setClientVersion(u16 peer_id, u8 major, u8 minor, u8 patch, std::string full) +{ + JMutexAutoLock conlock(m_clients_mutex); + + // Error check + std::map::iterator n; + n = m_clients.find(peer_id); + + // No client to set versions + if (n == m_clients.end()) + return; + + n->second->setVersionInfo(major,minor,patch,full); +} diff --git a/src/clientiface.h b/src/clientiface.h new file mode 100644 index 0000000..54b2502 --- /dev/null +++ b/src/clientiface.h @@ -0,0 +1,474 @@ +/* +Minetest +Copyright (C) 2010-2014 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. +*/ +#ifndef _CLIENTIFACE_H_ +#define _CLIENTIFACE_H_ + +#include "irr_v3d.h" // for irrlicht datatypes + +#include "constants.h" +#include "serialization.h" // for SER_FMT_VER_INVALID +#include "jthread/jmutex.h" +#include "network/networkpacket.h" + +#include +#include +#include +#include + +class MapBlock; +class ServerEnvironment; +class EmergeManager; + +/* + * State Transitions + + Start + (peer connect) + | + v + /-----------------\ + | | + | Created | + | | + \-----------------/ + | + | ++-----------------------------+ invalid playername, password +|IN: | or denied by mod +| TOSERVER_INIT |------------------------------ ++-----------------------------+ | + | | + | Auth ok | + | | ++-----------------------------+ | +|OUT: | | +| TOCLIENT_INIT | | ++-----------------------------+ | + | | + v | + /-----------------\ | + | | | + | InitSent | | + | | | + \-----------------/ +------------------ + | | | ++-----------------------------+ +-----------------------------+ | +|IN: | |OUT: | | +| TOSERVER_INIT2 | | TOCLIENT_ACCESS_DENIED | | ++-----------------------------+ +-----------------------------+ | + | | | + v v | + /-----------------\ /-----------------\ | + | | | | | + | InitDone | | Denied | | + | | | | | + \-----------------/ \-----------------/ | + | | ++-----------------------------+ | +|OUT: | | +| TOCLIENT_MOVEMENT | | +| TOCLIENT_ITEMDEF | | +| TOCLIENT_NODEDEF | | +| TOCLIENT_ANNOUNCE_MEDIA | | +| TOCLIENT_DETACHED_INVENTORY | | +| TOCLIENT_TIME_OF_DAY | | ++-----------------------------+ | + | | + | | + | ----------------------------------- | + v | | | + /-----------------\ v | + | | +-----------------------------+ | + | DefinitionsSent | |IN: | | + | | | TOSERVER_REQUEST_MEDIA | | + \-----------------/ | TOSERVER_RECEIVED_MEDIA | | + | +-----------------------------+ | + | ^ | | + | ----------------------------------- | + | | ++-----------------------------+ | +|IN: | | +| TOSERVER_CLIENT_READY | | ++-----------------------------+ | + | async | + v mod action | ++-----------------------------+ (ban,kick) | +|OUT: | | +| TOCLIENT_MOVE_PLAYER | | +| TOCLIENT_PRIVILEGES | | +| TOCLIENT_INVENTORY_FORMSPEC | | +| UpdateCrafting | | +| TOCLIENT_INVENTORY | | +| TOCLIENT_HP (opt) | | +| TOCLIENT_BREATH | | +| TOCLIENT_DEATHSCREEN | | ++-----------------------------+ | + | | + v | + /-----------------\ | + | |------------------------------------------------------ + | Active | + | |---------------------------------- + \-----------------/ timeout | + | +-----------------------------+ + | |OUT: | + | | TOCLIENT_DISCONNECT | + | +-----------------------------+ + | | + | v ++-----------------------------+ /-----------------\ +|IN: | | | +| TOSERVER_DISCONNECT |------------------->| Disconnecting | ++-----------------------------+ | | + \-----------------/ +*/ +namespace con { + class Connection; +} + +#define CI_ARRAYSIZE(a) (sizeof(a) / sizeof((a)[0])) + +enum ClientState +{ + CS_Invalid, + CS_Disconnecting, + CS_Denied, + CS_Created, + CS_InitSent, + CS_InitDone, + CS_DefinitionsSent, + CS_Active +}; + +enum ClientStateEvent +{ + CSE_Init, + CSE_GotInit2, + CSE_SetDenied, + CSE_SetDefinitionsSent, + CSE_SetClientReady, + CSE_Disconnect +}; + +/* + Used for queueing and sorting block transfers in containers + + Lower priority number means higher priority. +*/ +struct PrioritySortedBlockTransfer +{ + PrioritySortedBlockTransfer(float a_priority, v3s16 a_pos, u16 a_peer_id) + { + priority = a_priority; + pos = a_pos; + peer_id = a_peer_id; + } + bool operator < (const PrioritySortedBlockTransfer &other) const + { + return priority < other.priority; + } + float priority; + v3s16 pos; + u16 peer_id; +}; + +class RemoteClient +{ +public: + // peer_id=0 means this client has no associated peer + // NOTE: If client is made allowed to exist while peer doesn't, + // this has to be set to 0 when there is no peer. + // Also, the client must be moved to some other container. + u16 peer_id; + // The serialization version to use with the client + u8 serialization_version; + // + u16 net_proto_version; + + RemoteClient(): + peer_id(PEER_ID_INEXISTENT), + serialization_version(SER_FMT_VER_INVALID), + net_proto_version(0), + m_time_from_building(9999), + m_pending_serialization_version(SER_FMT_VER_INVALID), + m_state(CS_Created), + m_nearest_unsent_d(0), + m_nearest_unsent_reset_timer(0.0), + m_excess_gotblocks(0), + m_nothing_to_send_pause_timer(0.0), + m_name(""), + m_version_major(0), + m_version_minor(0), + m_version_patch(0), + m_full_version("unknown"), + m_supported_compressions(0), + m_connection_time(getTime(PRECISION_SECONDS)) + { + } + ~RemoteClient() + { + } + + /* + Finds block that should be sent next to the client. + Environment should be locked when this is called. + dtime is used for resetting send radius at slow interval + */ + void GetNextBlocks(ServerEnvironment *env, EmergeManager* emerge, + float dtime, std::vector &dest); + + void GotBlock(v3s16 p); + + void SentBlock(v3s16 p); + + void SetBlockNotSent(v3s16 p); + void SetBlocksNotSent(std::map &blocks); + + /** + * tell client about this block being modified right now. + * this information is required to requeue the block in case it's "on wire" + * while modification is processed by server + * @param p position of modified block + */ + void ResendBlockIfOnWire(v3s16 p); + + s32 SendingCount() + { + return m_blocks_sending.size(); + } + + // Increments timeouts and removes timed-out blocks from list + // NOTE: This doesn't fix the server-not-sending-block bug + // because it is related to emerging, not sending. + //void RunSendingTimeouts(float dtime, float timeout); + + void PrintInfo(std::ostream &o) + { + o<<"RemoteClient "< +#include +#include "log.h" +#include "mapsector.h" +#include "main.h" // dout_client, g_settings +#include "nodedef.h" +#include "mapblock.h" +#include "profiler.h" +#include "settings.h" +#include "camera.h" // CameraModes +#include "util/mathconstants.h" +#include + +#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" + +ClientMap::ClientMap( + Client *client, + IGameDef *gamedef, + MapDrawControl &control, + scene::ISceneNode* parent, + scene::ISceneManager* mgr, + s32 id +): + Map(dout_client, gamedef), + scene::ISceneNode(parent, mgr, id), + m_client(client), + m_control(control), + m_camera_position(0,0,0), + m_camera_direction(0,0,1), + m_camera_fov(M_PI) +{ + m_box = core::aabbox3d(-BS*1000000,-BS*1000000,-BS*1000000, + BS*1000000,BS*1000000,BS*1000000); + + /* TODO: Add a callback function so these can be updated when a setting + * changes. At this point in time it doesn't matter (e.g. /set + * is documented to change server settings only) + * + * TODO: Local caching of settings is not optimal and should at some stage + * be updated to use a global settings object for getting thse values + * (as opposed to the this local caching). This can be addressed in + * a later release. + */ + m_cache_trilinear_filter = g_settings->getBool("trilinear_filter"); + m_cache_bilinear_filter = g_settings->getBool("bilinear_filter"); + m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter"); + +} + +ClientMap::~ClientMap() +{ + /*JMutexAutoLock lock(mesh_mutex); + + if(mesh != NULL) + { + mesh->drop(); + mesh = NULL; + }*/ +} + +MapSector * ClientMap::emergeSector(v2s16 p2d) +{ + DSTACK(__FUNCTION_NAME); + // Check that it doesn't exist already + try{ + return getSectorNoGenerate(p2d); + } + catch(InvalidPositionException &e) + { + } + + // Create a sector + ClientMapSector *sector = new ClientMapSector(this, p2d, m_gamedef); + + { + //JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out + m_sectors[p2d] = sector; + } + + return sector; +} + +void ClientMap::OnRegisterSceneNode() +{ + if(IsVisible) + { + SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID); + SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT); + } + + ISceneNode::OnRegisterSceneNode(); +} + +static bool isOccluded(Map *map, v3s16 p0, v3s16 p1, float step, float stepfac, + float start_off, float end_off, u32 needed_count, INodeDefManager *nodemgr) +{ + float d0 = (float)BS * p0.getDistanceFrom(p1); + v3s16 u0 = p1 - p0; + v3f uf = v3f(u0.X, u0.Y, u0.Z) * BS; + uf.normalize(); + v3f p0f = v3f(p0.X, p0.Y, p0.Z) * BS; + u32 count = 0; + for(float s=start_off; sgetNodeNoEx(p); + bool is_transparent = false; + const ContentFeatures &f = nodemgr->get(n); + if(f.solidness == 0) + is_transparent = (f.visual_solidness != 2); + else + is_transparent = (f.solidness != 2); + if(!is_transparent){ + if(count == needed_count) + return true; + count++; + } + step *= stepfac; + } + return false; +} + +void ClientMap::updateDrawList(video::IVideoDriver* driver) +{ + ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG); + g_profiler->add("CM::updateDrawList() count", 1); + + INodeDefManager *nodemgr = m_gamedef->ndef(); + + for(std::map::iterator + i = m_drawlist.begin(); + i != m_drawlist.end(); ++i) + { + MapBlock *block = i->second; + block->refDrop(); + } + m_drawlist.clear(); + + m_camera_mutex.Lock(); + v3f camera_position = m_camera_position; + v3f camera_direction = m_camera_direction; + f32 camera_fov = m_camera_fov; + //v3s16 camera_offset = m_camera_offset; + m_camera_mutex.Unlock(); + + // Use a higher fov to accomodate faster camera movements. + // Blocks are cropped better when they are drawn. + // Or maybe they aren't? Well whatever. + camera_fov *= 1.2; + + v3s16 cam_pos_nodes = floatToInt(camera_position, BS); + v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1); + v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d; + v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d; + // Take a fair amount as we will be dropping more out later + // Umm... these additions are a bit strange but they are needed. + v3s16 p_blocks_min( + p_nodes_min.X / MAP_BLOCKSIZE - 3, + p_nodes_min.Y / MAP_BLOCKSIZE - 3, + p_nodes_min.Z / MAP_BLOCKSIZE - 3); + v3s16 p_blocks_max( + p_nodes_max.X / MAP_BLOCKSIZE + 1, + p_nodes_max.Y / MAP_BLOCKSIZE + 1, + p_nodes_max.Z / MAP_BLOCKSIZE + 1); + + // Number of blocks in rendering range + u32 blocks_in_range = 0; + // Number of blocks occlusion culled + u32 blocks_occlusion_culled = 0; + // Number of blocks in rendering range but don't have a mesh + u32 blocks_in_range_without_mesh = 0; + // Blocks that had mesh that would have been drawn according to + // rendering range (if max blocks limit didn't kick in) + u32 blocks_would_have_drawn = 0; + // Blocks that were drawn and had a mesh + u32 blocks_drawn = 0; + // Blocks which had a corresponding meshbuffer for this pass + //u32 blocks_had_pass_meshbuf = 0; + // Blocks from which stuff was actually drawn + //u32 blocks_without_stuff = 0; + // Distance to farthest drawn block + float farthest_drawn = 0; + + for(std::map::iterator + si = m_sectors.begin(); + si != m_sectors.end(); ++si) + { + MapSector *sector = si->second; + v2s16 sp = sector->getPos(); + + if(m_control.range_all == false) + { + if(sp.X < p_blocks_min.X + || sp.X > p_blocks_max.X + || sp.Y < p_blocks_min.Z + || sp.Y > p_blocks_max.Z) + continue; + } + + MapBlockVect sectorblocks; + sector->getBlocks(sectorblocks); + + /* + Loop through blocks in sector + */ + + u32 sector_blocks_drawn = 0; + + for(MapBlockVect::iterator i = sectorblocks.begin(); + i != sectorblocks.end(); i++) + { + MapBlock *block = *i; + + /* + Compare block position to camera position, skip + if not seen on display + */ + + if (block->mesh != NULL) + block->mesh->updateCameraOffset(m_camera_offset); + + float range = 100000 * BS; + if(m_control.range_all == false) + range = m_control.wanted_range * BS; + + float d = 0.0; + if(isBlockInSight(block->getPos(), camera_position, + camera_direction, camera_fov, + range, &d) == false) + { + continue; + } + + // This is ugly (spherical distance limit?) + /*if(m_control.range_all == false && + d - 0.5*BS*MAP_BLOCKSIZE > range) + continue;*/ + + blocks_in_range++; + + /* + Ignore if mesh doesn't exist + */ + { + //JMutexAutoLock lock(block->mesh_mutex); + + if(block->mesh == NULL){ + blocks_in_range_without_mesh++; + continue; + } + } + + /* + Occlusion culling + */ + + // No occlusion culling when free_move is on and camera is + // inside ground + bool occlusion_culling_enabled = true; + if(g_settings->getBool("free_move")){ + MapNode n = getNodeNoEx(cam_pos_nodes); + if(n.getContent() == CONTENT_IGNORE || + nodemgr->get(n).solidness == 2) + occlusion_culling_enabled = false; + } + + v3s16 cpn = block->getPos() * MAP_BLOCKSIZE; + cpn += v3s16(MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2, MAP_BLOCKSIZE/2); + float step = BS*1; + float stepfac = 1.1; + float startoff = BS*1; + float endoff = -BS*MAP_BLOCKSIZE*1.42*1.42; + v3s16 spn = cam_pos_nodes + v3s16(0,0,0); + s16 bs2 = MAP_BLOCKSIZE/2 + 1; + u32 needed_count = 1; + if( + occlusion_culling_enabled && + isOccluded(this, spn, cpn + v3s16(0,0,0), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(bs2,bs2,bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(bs2,bs2,-bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(bs2,-bs2,bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(bs2,-bs2,-bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(-bs2,bs2,bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(-bs2,bs2,-bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) && + isOccluded(this, spn, cpn + v3s16(-bs2,-bs2,-bs2), + step, stepfac, startoff, endoff, needed_count, nodemgr) + ) + { + blocks_occlusion_culled++; + continue; + } + + // This block is in range. Reset usage timer. + block->resetUsageTimer(); + + // Limit block count in case of a sudden increase + blocks_would_have_drawn++; + if(blocks_drawn >= m_control.wanted_max_blocks + && m_control.range_all == false + && d > m_control.wanted_min_range * BS) + continue; + + // Add to set + block->refGrab(); + m_drawlist[block->getPos()] = block; + + sector_blocks_drawn++; + blocks_drawn++; + if(d/BS > farthest_drawn) + farthest_drawn = d/BS; + + } // foreach sectorblocks + + if(sector_blocks_drawn != 0) + m_last_drawn_sectors.insert(sp); + } + + m_control.blocks_would_have_drawn = blocks_would_have_drawn; + m_control.blocks_drawn = blocks_drawn; + m_control.farthest_drawn = farthest_drawn; + + g_profiler->avg("CM: blocks in range", blocks_in_range); + g_profiler->avg("CM: blocks occlusion culled", blocks_occlusion_culled); + if(blocks_in_range != 0) + g_profiler->avg("CM: blocks in range without mesh (frac)", + (float)blocks_in_range_without_mesh/blocks_in_range); + g_profiler->avg("CM: blocks drawn", blocks_drawn); + g_profiler->avg("CM: farthest drawn", farthest_drawn); + g_profiler->avg("CM: wanted max blocks", m_control.wanted_max_blocks); +} + +struct MeshBufList +{ + video::SMaterial m; + std::vector bufs; +}; + +struct MeshBufListList +{ + std::vector lists; + + void clear() + { + lists.clear(); + } + + void add(scene::IMeshBuffer *buf) + { + for(std::vector::iterator i = lists.begin(); + i != lists.end(); ++i){ + MeshBufList &l = *i; + video::SMaterial &m = buf->getMaterial(); + + // comparing a full material is quite expensive so we don't do it if + // not even first texture is equal + if (l.m.TextureLayer[0].Texture != m.TextureLayer[0].Texture) + continue; + + if (l.m == m) { + l.bufs.push_back(buf); + return; + } + } + MeshBufList l; + l.m = buf->getMaterial(); + l.bufs.push_back(buf); + lists.push_back(l); + } +}; + +void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) +{ + DSTACK(__FUNCTION_NAME); + + bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT; + + std::string prefix; + if(pass == scene::ESNRP_SOLID) + prefix = "CM: solid: "; + else + prefix = "CM: transparent: "; + + /* + This is called two times per frame, reset on the non-transparent one + */ + if(pass == scene::ESNRP_SOLID) + { + m_last_drawn_sectors.clear(); + } + + /* + Get time for measuring timeout. + + Measuring time is very useful for long delays when the + machine is swapping a lot. + */ + int time1 = time(0); + + /* + Get animation parameters + */ + float animation_time = m_client->getAnimationTime(); + int crack = m_client->getCrackLevel(); + u32 daynight_ratio = m_client->getEnv().getDayNightRatio(); + + m_camera_mutex.Lock(); + v3f camera_position = m_camera_position; + v3f camera_direction = m_camera_direction; + f32 camera_fov = m_camera_fov; + m_camera_mutex.Unlock(); + + /* + Get all blocks and draw all visible ones + */ + + v3s16 cam_pos_nodes = floatToInt(camera_position, BS); + + v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1); + + v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d; + v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d; + + // Take a fair amount as we will be dropping more out later + // Umm... these additions are a bit strange but they are needed. + v3s16 p_blocks_min( + p_nodes_min.X / MAP_BLOCKSIZE - 3, + p_nodes_min.Y / MAP_BLOCKSIZE - 3, + p_nodes_min.Z / MAP_BLOCKSIZE - 3); + v3s16 p_blocks_max( + p_nodes_max.X / MAP_BLOCKSIZE + 1, + p_nodes_max.Y / MAP_BLOCKSIZE + 1, + p_nodes_max.Z / MAP_BLOCKSIZE + 1); + + u32 vertex_count = 0; + u32 meshbuffer_count = 0; + + // For limiting number of mesh animations per frame + u32 mesh_animate_count = 0; + u32 mesh_animate_count_far = 0; + + // Blocks that were drawn and had a mesh + u32 blocks_drawn = 0; + // Blocks which had a corresponding meshbuffer for this pass + u32 blocks_had_pass_meshbuf = 0; + // Blocks from which stuff was actually drawn + u32 blocks_without_stuff = 0; + + /* + Draw the selected MapBlocks + */ + + { + ScopeProfiler sp(g_profiler, prefix+"drawing blocks", SPT_AVG); + + MeshBufListList drawbufs; + + for(std::map::iterator + i = m_drawlist.begin(); + i != m_drawlist.end(); ++i) + { + MapBlock *block = i->second; + + // If the mesh of the block happened to get deleted, ignore it + if(block->mesh == NULL) + continue; + + float d = 0.0; + if(isBlockInSight(block->getPos(), camera_position, + camera_direction, camera_fov, + 100000*BS, &d) == false) + { + continue; + } + + // Mesh animation + { + //JMutexAutoLock lock(block->mesh_mutex); + MapBlockMesh *mapBlockMesh = block->mesh; + assert(mapBlockMesh); + // Pretty random but this should work somewhat nicely + bool faraway = d >= BS*50; + //bool faraway = d >= m_control.wanted_range * BS; + if(mapBlockMesh->isAnimationForced() || + !faraway || + mesh_animate_count_far < (m_control.range_all ? 200 : 50)) + { + bool animated = mapBlockMesh->animate( + faraway, + animation_time, + crack, + daynight_ratio); + if(animated) + mesh_animate_count++; + if(animated && faraway) + mesh_animate_count_far++; + } + else + { + mapBlockMesh->decreaseAnimationForceTimer(); + } + } + + /* + Get the meshbuffers of the block + */ + { + //JMutexAutoLock lock(block->mesh_mutex); + + MapBlockMesh *mapBlockMesh = block->mesh; + assert(mapBlockMesh); + + scene::SMesh *mesh = mapBlockMesh->getMesh(); + assert(mesh); + + u32 c = mesh->getMeshBufferCount(); + for(u32 i=0; igetMeshBuffer(i); + + buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, m_cache_trilinear_filter); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, m_cache_bilinear_filter); + buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, m_cache_anistropic_filter); + + const video::SMaterial& material = buf->getMaterial(); + video::IMaterialRenderer* rnd = + driver->getMaterialRenderer(material.MaterialType); + bool transparent = (rnd && rnd->isTransparent()); + if(transparent == is_transparent_pass) + { + if(buf->getVertexCount() == 0) + errorstream<<"Block ["< &lists = drawbufs.lists; + + int timecheck_counter = 0; + for(std::vector::iterator i = lists.begin(); + i != lists.end(); ++i) { + timecheck_counter++; + if(timecheck_counter > 50) { + timecheck_counter = 0; + int time2 = time(0); + if(time2 > time1 + 4) { + infostream << "ClientMap::renderMap(): " + "Rendering takes ages, returning." + << std::endl; + return; + } + } + + MeshBufList &list = *i; + + driver->setMaterial(list.m); + + for(std::vector::iterator j = list.bufs.begin(); + j != list.bufs.end(); ++j) { + scene::IMeshBuffer *buf = *j; + driver->drawMeshBuffer(buf); + vertex_count += buf->getVertexCount(); + meshbuffer_count++; + } + + } + } // ScopeProfiler + + // Log only on solid pass because values are the same + if(pass == scene::ESNRP_SOLID){ + g_profiler->avg("CM: animated meshes", mesh_animate_count); + g_profiler->avg("CM: animated meshes (far)", mesh_animate_count_far); + } + + g_profiler->avg(prefix+"vertices drawn", vertex_count); + if(blocks_had_pass_meshbuf != 0) + g_profiler->avg(prefix+"meshbuffers per block", + (float)meshbuffer_count / (float)blocks_had_pass_meshbuf); + if(blocks_drawn != 0) + g_profiler->avg(prefix+"empty blocks (frac)", + (float)blocks_without_stuff / blocks_drawn); + + /*infostream<<"renderMap(): is_transparent_pass="<getNodeNoEx(p); + if(ndef->get(n).param_type == CPT_LIGHT && + !ndef->get(n).sunlight_propagates) + allow_allowing_non_sunlight_propagates = true; + } + // If would start at CONTENT_IGNORE, start closer + { + v3s16 p = floatToInt(pf, BS); + MapNode n = map->getNodeNoEx(p); + if(n.getContent() == CONTENT_IGNORE){ + float newd = 2*BS; + pf = p0 + dir * 2*newd; + distance = newd; + sunlight_min_d = 0; + } + } + for(int i=0; distance < end_distance; i++){ + pf += dir * step; + distance += step; + step *= step_multiplier; + + v3s16 p = floatToInt(pf, BS); + MapNode n = map->getNodeNoEx(p); + if(allow_allowing_non_sunlight_propagates && i == 0 && + ndef->get(n).param_type == CPT_LIGHT && + !ndef->get(n).sunlight_propagates){ + allow_non_sunlight_propagates = true; + } + if(ndef->get(n).param_type != CPT_LIGHT || + (!ndef->get(n).sunlight_propagates && + !allow_non_sunlight_propagates)){ + nonlight_seen = true; + noncount++; + if(noncount >= 4) + break; + continue; + } + if(distance >= sunlight_min_d && *sunlight_seen == false + && nonlight_seen == false) + if(n.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN) + *sunlight_seen = true; + noncount = 0; + brightness_sum += decode_light(n.getLightBlend(daylight_factor, ndef)); + brightness_count++; + } + *result = 0; + if(brightness_count == 0) + return false; + *result = brightness_sum / brightness_count; + /*std::cerr<<"Sampled "< 35*BS) + sunlight_min_d = 35*BS; + std::vector values; + for(u32 i=0; i a; + a.buildRotateFromTo(v3f(0,1,0), z_dir); + v3f dir = m_camera_direction; + a.rotateVect(dir); + int br = 0; + float step = BS*1.5; + if(max_d > 35*BS) + step = max_d / 35 * 1.5; + float off = step * z_offsets[i]; + bool sunlight_seen_now = false; + bool ok = getVisibleBrightness(this, m_camera_position, dir, + step, 1.0, max_d*0.6+off, max_d, ndef, daylight_factor, + sunlight_min_d, + &br, &sunlight_seen_now); + if(sunlight_seen_now) + sunlight_seen_count++; + if(!ok) + continue; + values.push_back(br); + // Don't try too much if being in the sun is clear + if(sunlight_seen_count >= 20) + break; + } + int brightness_sum = 0; + int brightness_count = 0; + std::sort(values.begin(), values.end()); + u32 num_values_to_use = values.size(); + if(num_values_to_use >= 10) + num_values_to_use -= num_values_to_use/2; + else if(num_values_to_use >= 7) + num_values_to_use -= num_values_to_use/3; + u32 first_value_i = (values.size() - num_values_to_use) / 2; + if(debugprint){ + for(u32 i=0; i < first_value_i; i++) + std::cerr<get(n).param_type == CPT_LIGHT){ + ret = decode_light(n.getLightBlend(daylight_factor, ndef)); + } else { + ret = oldvalue; + } + } else { + /*float pre = (float)brightness_sum / (float)brightness_count; + float tmp = pre; + const float d = 0.2; + pre *= 1.0 + d*2; + pre -= tmp * d; + int preint = pre; + ret = MYMAX(0, MYMIN(255, preint));*/ + ret = brightness_sum / brightness_count; + } + if(debugprint) + std::cerr<<"Result: "<get(n); + video::SColor post_effect_color = features.post_effect_color; + if(features.solidness == 2 && !(g_settings->getBool("noclip") && + m_gamedef->checkLocalPrivilege("noclip")) && + cam_mode == CAMERA_MODE_FIRST) + { + post_effect_color = video::SColor(255, 0, 0, 0); + } + if (post_effect_color.getAlpha() != 0) + { + // Draw a full-screen rectangle + video::IVideoDriver* driver = SceneManager->getVideoDriver(); + v2u32 ss = driver->getScreenSize(); + core::rect rect(0,0, ss.X, ss.Y); + driver->draw2DRectangle(post_effect_color, rect); + } +} + +void ClientMap::PrintInfo(std::ostream &out) +{ + out<<"ClientMap: "; +} + + diff --git a/src/clientmap.h b/src/clientmap.h new file mode 100644 index 0000000..492e23f --- /dev/null +++ b/src/clientmap.h @@ -0,0 +1,164 @@ +/* +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. +*/ + +#ifndef CLIENTMAP_HEADER +#define CLIENTMAP_HEADER + +#include "irrlichttypes_extrabloated.h" +#include "map.h" +#include "camera.h" +#include +#include + +struct MapDrawControl +{ + MapDrawControl(): + range_all(false), + wanted_range(50), + wanted_max_blocks(0), + wanted_min_range(0), + blocks_drawn(0), + blocks_would_have_drawn(0), + farthest_drawn(0) + { + } + // Overrides limits by drawing everything + bool range_all; + // Wanted drawing range + float wanted_range; + // Maximum number of blocks to draw + u32 wanted_max_blocks; + // Blocks in this range are drawn regardless of number of blocks drawn + float wanted_min_range; + // Number of blocks rendered is written here by the renderer + u32 blocks_drawn; + // Number of blocks that would have been drawn in wanted_range + u32 blocks_would_have_drawn; + // Distance to the farthest block drawn + float farthest_drawn; +}; + +class Client; +class ITextureSource; + +/* + ClientMap + + This is the only map class that is able to render itself on screen. +*/ + +class ClientMap : public Map, public scene::ISceneNode +{ +public: + ClientMap( + Client *client, + IGameDef *gamedef, + MapDrawControl &control, + scene::ISceneNode* parent, + scene::ISceneManager* mgr, + s32 id + ); + + ~ClientMap(); + + s32 mapType() const + { + return MAPTYPE_CLIENT; + } + + void drop() + { + ISceneNode::drop(); + } + + void updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset) + { + JMutexAutoLock lock(m_camera_mutex); + m_camera_position = pos; + m_camera_direction = dir; + m_camera_fov = fov; + m_camera_offset = offset; + } + + /* + Forcefully get a sector from somewhere + */ + MapSector * emergeSector(v2s16 p); + + //void deSerializeSector(v2s16 p2d, std::istream &is); + + /* + ISceneNode methods + */ + + virtual void OnRegisterSceneNode(); + + virtual void render() + { + video::IVideoDriver* driver = SceneManager->getVideoDriver(); + driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); + renderMap(driver, SceneManager->getSceneNodeRenderPass()); + } + + virtual const core::aabbox3d& getBoundingBox() const + { + return m_box; + } + + void updateDrawList(video::IVideoDriver* driver); + void renderMap(video::IVideoDriver* driver, s32 pass); + + int getBackgroundBrightness(float max_d, u32 daylight_factor, + int oldvalue, bool *sunlight_seen_result); + + void renderPostFx(CameraMode cam_mode); + + // For debug printing + virtual void PrintInfo(std::ostream &out); + + // Check if sector was drawn on last render() + bool sectorWasDrawn(v2s16 p) + { + return (m_last_drawn_sectors.find(p) != m_last_drawn_sectors.end()); + } + +private: + Client *m_client; + + core::aabbox3d m_box; + + MapDrawControl &m_control; + + v3f m_camera_position; + v3f m_camera_direction; + f32 m_camera_fov; + v3s16 m_camera_offset; + JMutex m_camera_mutex; + + std::map m_drawlist; + + std::set m_last_drawn_sectors; + + bool m_cache_trilinear_filter; + bool m_cache_bilinear_filter; + bool m_cache_anistropic_filter; +}; + +#endif + diff --git a/src/clientmedia.cpp b/src/clientmedia.cpp new file mode 100644 index 0000000..0918e8a --- /dev/null +++ b/src/clientmedia.cpp @@ -0,0 +1,656 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#include "clientmedia.h" +#include "httpfetch.h" +#include "client.h" +#include "filecache.h" +#include "filesys.h" +#include "debug.h" +#include "log.h" +#include "porting.h" +#include "settings.h" +#include "main.h" +#include "network/networkprotocol.h" +#include "util/hex.h" +#include "util/serialize.h" +#include "util/sha1.h" +#include "util/string.h" + +static std::string getMediaCacheDir() +{ + return porting::path_user + DIR_DELIM + "cache" + DIR_DELIM + "media"; +} + +/* + ClientMediaDownloader +*/ + +ClientMediaDownloader::ClientMediaDownloader(): + m_media_cache(getMediaCacheDir()) +{ + m_initial_step_done = false; + m_name_bound = ""; // works because "" is an invalid file name + m_uncached_count = 0; + m_uncached_received_count = 0; + m_httpfetch_caller = HTTPFETCH_DISCARD; + m_httpfetch_active = 0; + m_httpfetch_active_limit = 0; + m_httpfetch_next_id = 0; + m_httpfetch_timeout = 0; + m_outstanding_hash_sets = 0; +} + +ClientMediaDownloader::~ClientMediaDownloader() +{ + if (m_httpfetch_caller != HTTPFETCH_DISCARD) + httpfetch_caller_free(m_httpfetch_caller); + + for (std::map::iterator it = m_files.begin(); + it != m_files.end(); ++it) + delete it->second; + + for (u32 i = 0; i < m_remotes.size(); ++i) + delete m_remotes[i]; +} + +void ClientMediaDownloader::addFile(std::string name, std::string sha1) +{ + assert(!m_initial_step_done); // pre-condition + + // if name was already announced, ignore the new announcement + if (m_files.count(name) != 0) { + errorstream << "Client: ignoring duplicate media announcement " + << "sent by server: \"" << name << "\"" + << std::endl; + return; + } + + // if name is empty or contains illegal characters, ignore the file + if (name.empty() || !string_allowed(name, TEXTURENAME_ALLOWED_CHARS)) { + errorstream << "Client: ignoring illegal file name " + << "sent by server: \"" << name << "\"" + << std::endl; + return; + } + + // length of sha1 must be exactly 20 (160 bits), else ignore the file + if (sha1.size() != 20) { + errorstream << "Client: ignoring illegal SHA1 sent by server: " + << hex_encode(sha1) << " \"" << name << "\"" + << std::endl; + return; + } + + FileStatus *filestatus = new FileStatus; + filestatus->received = false; + filestatus->sha1 = sha1; + filestatus->current_remote = -1; + m_files.insert(std::make_pair(name, filestatus)); +} + +void ClientMediaDownloader::addRemoteServer(std::string baseurl) +{ + assert(!m_initial_step_done); // pre-condition + + #ifdef USE_CURL + + if (g_settings->getBool("enable_remote_media_server")) { + infostream << "Client: Adding remote server \"" + << baseurl << "\" for media download" << std::endl; + + RemoteServerStatus *remote = new RemoteServerStatus; + remote->baseurl = baseurl; + remote->active_count = 0; + remote->request_by_filename = false; + m_remotes.push_back(remote); + } + + #else + + infostream << "Client: Ignoring remote server \"" + << baseurl << "\" because cURL support is not compiled in" + << std::endl; + + #endif +} + +void ClientMediaDownloader::step(Client *client) +{ + if (!m_initial_step_done) { + initialStep(client); + m_initial_step_done = true; + } + + // Remote media: check for completion of fetches + if (m_httpfetch_active) { + bool fetched_something = false; + HTTPFetchResult fetch_result; + + while (httpfetch_async_get(m_httpfetch_caller, fetch_result)) { + m_httpfetch_active--; + fetched_something = true; + + // Is this a hashset (index.mth) or a media file? + if (fetch_result.request_id < m_remotes.size()) + remoteHashSetReceived(fetch_result); + else + remoteMediaReceived(fetch_result, client); + } + + if (fetched_something) + startRemoteMediaTransfers(); + + // Did all remote transfers end and no new ones can be started? + // If so, request still missing files from the minetest server + // (Or report that we have all files.) + if (m_httpfetch_active == 0) { + if (m_uncached_received_count < m_uncached_count) { + infostream << "Client: Failed to remote-fetch " + << (m_uncached_count-m_uncached_received_count) + << " files. Requesting them" + << " the usual way." << std::endl; + } + startConventionalTransfers(client); + } + } +} + +void ClientMediaDownloader::initialStep(Client *client) +{ + // Check media cache + m_uncached_count = m_files.size(); + for (std::map::iterator + it = m_files.begin(); + it != m_files.end(); ++it) { + std::string name = it->first; + FileStatus *filestatus = it->second; + const std::string &sha1 = filestatus->sha1; + + std::ostringstream tmp_os(std::ios_base::binary); + bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os); + + // If found in cache, try to load it from there + if (found_in_cache) { + bool success = checkAndLoad(name, sha1, + tmp_os.str(), true, client); + if (success) { + filestatus->received = true; + m_uncached_count--; + } + } + } + + assert(m_uncached_received_count == 0); + + // Create the media cache dir if we are likely to write to it + if (m_uncached_count != 0) { + bool did = fs::CreateAllDirs(getMediaCacheDir()); + if (!did) { + errorstream << "Client: " + << "Could not create media cache directory: " + << getMediaCacheDir() + << std::endl; + } + } + + // If we found all files in the cache, report this fact to the server. + // If the server reported no remote servers, immediately start + // conventional transfers. Note: if cURL support is not compiled in, + // m_remotes is always empty, so "!USE_CURL" is redundant but may + // reduce the size of the compiled code + if (!USE_CURL || m_uncached_count == 0 || m_remotes.empty()) { + startConventionalTransfers(client); + } + else { + // Otherwise start off by requesting each server's sha1 set + + // This is the first time we use httpfetch, so alloc a caller ID + m_httpfetch_caller = httpfetch_caller_alloc(); + m_httpfetch_timeout = g_settings->getS32("curl_timeout"); + + // Set the active fetch limit to curl_parallel_limit or 84, + // whichever is greater. This gives us some leeway so that + // inefficiencies in communicating with the httpfetch thread + // don't slow down fetches too much. (We still want some limit + // so that when the first remote server returns its hash set, + // not all files are requested from that server immediately.) + // One such inefficiency is that ClientMediaDownloader::step() + // is only called a couple times per second, while httpfetch + // might return responses much faster than that. + // Note that httpfetch strictly enforces curl_parallel_limit + // but at no inter-thread communication cost. This however + // doesn't help with the aforementioned inefficiencies. + // The signifance of 84 is that it is 2*6*9 in base 13. + m_httpfetch_active_limit = g_settings->getS32("curl_parallel_limit"); + m_httpfetch_active_limit = MYMAX(m_httpfetch_active_limit, 84); + + // Write a list of hashes that we need. This will be POSTed + // to the server using Content-Type: application/octet-stream + std::string required_hash_set = serializeRequiredHashSet(); + + // minor fixme: this loop ignores m_httpfetch_active_limit + + // another minor fixme, unlikely to matter in normal usage: + // these index.mth fetches do (however) count against + // m_httpfetch_active_limit when starting actual media file + // requests, so if there are lots of remote servers that are + // not responding, those will stall new media file transfers. + + for (u32 i = 0; i < m_remotes.size(); ++i) { + assert(m_httpfetch_next_id == i); + + RemoteServerStatus *remote = m_remotes[i]; + actionstream << "Client: Contacting remote server \"" + << remote->baseurl << "\"" << std::endl; + + HTTPFetchRequest fetch_request; + fetch_request.url = + remote->baseurl + MTHASHSET_FILE_NAME; + fetch_request.caller = m_httpfetch_caller; + fetch_request.request_id = m_httpfetch_next_id; // == i + fetch_request.timeout = m_httpfetch_timeout; + fetch_request.connect_timeout = m_httpfetch_timeout; + fetch_request.post_data = required_hash_set; + fetch_request.extra_headers.push_back( + "Content-Type: application/octet-stream"); + httpfetch_async(fetch_request); + + m_httpfetch_active++; + m_httpfetch_next_id++; + m_outstanding_hash_sets++; + } + } +} + +void ClientMediaDownloader::remoteHashSetReceived( + const HTTPFetchResult &fetch_result) +{ + u32 remote_id = fetch_result.request_id; + assert(remote_id < m_remotes.size()); + RemoteServerStatus *remote = m_remotes[remote_id]; + + m_outstanding_hash_sets--; + + if (fetch_result.succeeded) { + try { + // Server sent a list of file hashes that are + // available on it, try to parse the list + + std::set sha1_set; + deSerializeHashSet(fetch_result.data, sha1_set); + + // Parsing succeeded: For every file that is + // available on this server, add this server + // to the available_remotes array + + for(std::map::iterator + it = m_files.upper_bound(m_name_bound); + it != m_files.end(); ++it) { + FileStatus *f = it->second; + if (!f->received && sha1_set.count(f->sha1)) + f->available_remotes.push_back(remote_id); + } + } + catch (SerializationError &e) { + infostream << "Client: Remote server \"" + << remote->baseurl << "\" sent invalid hash set: " + << e.what() << std::endl; + } + } + + // For compatibility: If index.mth is not found, assume that the + // server contains files named like the original files (not their sha1) + + // Do NOT check for any particular response code (e.g. 404) here, + // because different servers respond differently + + if (!fetch_result.succeeded && !fetch_result.timeout) { + infostream << "Client: Enabling compatibility mode for remote " + << "server \"" << remote->baseurl << "\"" << std::endl; + remote->request_by_filename = true; + + // Assume every file is available on this server + + for(std::map::iterator + it = m_files.upper_bound(m_name_bound); + it != m_files.end(); ++it) { + FileStatus *f = it->second; + if (!f->received) + f->available_remotes.push_back(remote_id); + } + } +} + +void ClientMediaDownloader::remoteMediaReceived( + const HTTPFetchResult &fetch_result, + Client *client) +{ + // Some remote server sent us a file. + // -> decrement number of active fetches + // -> mark file as received if fetch succeeded + // -> try to load media + + std::string name; + { + std::map::iterator it = + m_remote_file_transfers.find(fetch_result.request_id); + assert(it != m_remote_file_transfers.end()); + name = it->second; + m_remote_file_transfers.erase(it); + } + + sanity_check(m_files.count(name) != 0); + + FileStatus *filestatus = m_files[name]; + sanity_check(!filestatus->received); + sanity_check(filestatus->current_remote >= 0); + + RemoteServerStatus *remote = m_remotes[filestatus->current_remote]; + + filestatus->current_remote = -1; + remote->active_count--; + + // If fetch succeeded, try to load media file + + if (fetch_result.succeeded) { + bool success = checkAndLoad(name, filestatus->sha1, + fetch_result.data, false, client); + if (success) { + filestatus->received = true; + assert(m_uncached_received_count < m_uncached_count); + m_uncached_received_count++; + } + } +} + +s32 ClientMediaDownloader::selectRemoteServer(FileStatus *filestatus) +{ + // Pre-conditions + assert(filestatus != NULL); + assert(!filestatus->received); + assert(filestatus->current_remote < 0); + + if (filestatus->available_remotes.empty()) + return -1; + else { + // Of all servers that claim to provide the file (and haven't + // been unsuccessfully tried before), find the one with the + // smallest number of currently active transfers + + s32 best = 0; + s32 best_remote_id = filestatus->available_remotes[best]; + s32 best_active_count = m_remotes[best_remote_id]->active_count; + + for (u32 i = 1; i < filestatus->available_remotes.size(); ++i) { + s32 remote_id = filestatus->available_remotes[i]; + s32 active_count = m_remotes[remote_id]->active_count; + if (active_count < best_active_count) { + best = i; + best_remote_id = remote_id; + best_active_count = active_count; + } + } + + filestatus->available_remotes.erase( + filestatus->available_remotes.begin() + best); + + return best_remote_id; + } +} + +void ClientMediaDownloader::startRemoteMediaTransfers() +{ + bool changing_name_bound = true; + + for (std::map::iterator + files_iter = m_files.upper_bound(m_name_bound); + files_iter != m_files.end(); ++files_iter) { + + // Abort if active fetch limit is exceeded + if (m_httpfetch_active >= m_httpfetch_active_limit) + break; + + const std::string &name = files_iter->first; + FileStatus *filestatus = files_iter->second; + + if (!filestatus->received && filestatus->current_remote < 0) { + // File has not been received yet and is not currently + // being transferred. Choose a server for it. + s32 remote_id = selectRemoteServer(filestatus); + if (remote_id >= 0) { + // Found a server, so start fetching + RemoteServerStatus *remote = + m_remotes[remote_id]; + + std::string url = remote->baseurl + + (remote->request_by_filename ? name : + hex_encode(filestatus->sha1)); + verbosestream << "Client: " + << "Requesting remote media file " + << "\"" << name << "\" " + << "\"" << url << "\"" << std::endl; + + HTTPFetchRequest fetch_request; + fetch_request.url = url; + fetch_request.caller = m_httpfetch_caller; + fetch_request.request_id = m_httpfetch_next_id; + fetch_request.timeout = 0; // no data timeout! + fetch_request.connect_timeout = + m_httpfetch_timeout; + httpfetch_async(fetch_request); + + m_remote_file_transfers.insert(std::make_pair( + m_httpfetch_next_id, + name)); + + filestatus->current_remote = remote_id; + remote->active_count++; + m_httpfetch_active++; + m_httpfetch_next_id++; + } + } + + if (filestatus->received || + (filestatus->current_remote < 0 && + !m_outstanding_hash_sets)) { + // If we arrive here, we conclusively know that we + // won't fetch this file from a remote server in the + // future. So update the name bound if possible. + if (changing_name_bound) + m_name_bound = name; + } + else + changing_name_bound = false; + } + +} + +void ClientMediaDownloader::startConventionalTransfers(Client *client) +{ + assert(m_httpfetch_active == 0); // pre-condition + + if (m_uncached_received_count != m_uncached_count) { + // Some media files have not been received yet, use the + // conventional slow method (minetest protocol) to get them + std::vector file_requests; + for (std::map::iterator + it = m_files.begin(); + it != m_files.end(); ++it) { + if (!it->second->received) + file_requests.push_back(it->first); + } + assert((s32) file_requests.size() == + m_uncached_count - m_uncached_received_count); + client->request_media(file_requests); + } +} + +void ClientMediaDownloader::conventionalTransferDone( + const std::string &name, + const std::string &data, + Client *client) +{ + // Check that file was announced + std::map::iterator + file_iter = m_files.find(name); + if (file_iter == m_files.end()) { + errorstream << "Client: server sent media file that was" + << "not announced, ignoring it: \"" << name << "\"" + << std::endl; + return; + } + FileStatus *filestatus = file_iter->second; + assert(filestatus != NULL); + + // Check that file hasn't already been received + if (filestatus->received) { + errorstream << "Client: server sent media file that we already" + << "received, ignoring it: \"" << name << "\"" + << std::endl; + return; + } + + // Mark file as received, regardless of whether loading it works and + // whether the checksum matches (because at this point there is no + // other server that could send a replacement) + filestatus->received = true; + assert(m_uncached_received_count < m_uncached_count); + m_uncached_received_count++; + + // Check that received file matches announced checksum + // If so, load it + checkAndLoad(name, filestatus->sha1, data, false, client); +} + +bool ClientMediaDownloader::checkAndLoad( + const std::string &name, const std::string &sha1, + const std::string &data, bool is_from_cache, Client *client) +{ + const char *cached_or_received = is_from_cache ? "cached" : "received"; + const char *cached_or_received_uc = is_from_cache ? "Cached" : "Received"; + std::string sha1_hex = hex_encode(sha1); + + // Compute actual checksum of data + std::string data_sha1; + { + SHA1 data_sha1_calculator; + data_sha1_calculator.addBytes(data.c_str(), data.size()); + unsigned char *data_tmpdigest = data_sha1_calculator.getDigest(); + data_sha1.assign((char*) data_tmpdigest, 20); + free(data_tmpdigest); + } + + // Check that received file matches announced checksum + if (data_sha1 != sha1) { + std::string data_sha1_hex = hex_encode(data_sha1); + infostream << "Client: " + << cached_or_received_uc << " media file " + << sha1_hex << " \"" << name << "\" " + << "mismatches actual checksum " << data_sha1_hex + << std::endl; + return false; + } + + // Checksum is ok, try loading the file + bool success = client->loadMedia(data, name); + if (!success) { + infostream << "Client: " + << "Failed to load " << cached_or_received << " media: " + << sha1_hex << " \"" << name << "\"" + << std::endl; + return false; + } + + verbosestream << "Client: " + << "Loaded " << cached_or_received << " media: " + << sha1_hex << " \"" << name << "\"" + << std::endl; + + // Update cache (unless we just loaded the file from the cache) + if (!is_from_cache) + m_media_cache.update(sha1_hex, data); + + return true; +} + + +/* + Minetest Hashset File Format + + All values are stored in big-endian byte order. + [u32] signature: 'MTHS' + [u16] version: 1 + For each hash in set: + [u8*20] SHA1 hash + + Version changes: + 1 - Initial version +*/ + +std::string ClientMediaDownloader::serializeRequiredHashSet() +{ + std::ostringstream os(std::ios::binary); + + writeU32(os, MTHASHSET_FILE_SIGNATURE); // signature + writeU16(os, 1); // version + + // Write list of hashes of files that have not been + // received (found in cache) yet + for (std::map::iterator + it = m_files.begin(); + it != m_files.end(); ++it) { + if (!it->second->received) { + FATAL_ERROR_IF(it->second->sha1.size() != 20, "Invalid SHA1 size"); + os << it->second->sha1; + } + } + + return os.str(); +} + +void ClientMediaDownloader::deSerializeHashSet(const std::string &data, + std::set &result) +{ + if (data.size() < 6 || data.size() % 20 != 6) { + throw SerializationError( + "ClientMediaDownloader::deSerializeHashSet: " + "invalid hash set file size"); + } + + const u8 *data_cstr = (const u8*) data.c_str(); + + u32 signature = readU32(&data_cstr[0]); + if (signature != MTHASHSET_FILE_SIGNATURE) { + throw SerializationError( + "ClientMediaDownloader::deSerializeHashSet: " + "invalid hash set file signature"); + } + + u16 version = readU16(&data_cstr[4]); + if (version != 1) { + throw SerializationError( + "ClientMediaDownloader::deSerializeHashSet: " + "unsupported hash set file version"); + } + + for (u32 pos = 6; pos < data.size(); pos += 20) { + result.insert(data.substr(pos, 20)); + } +} diff --git a/src/clientmedia.h b/src/clientmedia.h new file mode 100644 index 0000000..1f0da70 --- /dev/null +++ b/src/clientmedia.h @@ -0,0 +1,150 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef CLIENTMEDIA_HEADER +#define CLIENTMEDIA_HEADER + +#include "irrlichttypes.h" +#include "filecache.h" +#include +#include +#include +#include + +class Client; +struct HTTPFetchResult; + +#define MTHASHSET_FILE_SIGNATURE 0x4d544853 // 'MTHS' +#define MTHASHSET_FILE_NAME "index.mth" + +class ClientMediaDownloader +{ +public: + ClientMediaDownloader(); + ~ClientMediaDownloader(); + + float getProgress() const { + if (m_uncached_count >= 1) + return 1.0 * m_uncached_received_count / + m_uncached_count; + else + return 0.0; + } + + bool isStarted() const { + return m_initial_step_done; + } + + // If this returns true, the downloader is done and can be deleted + bool isDone() const { + return m_initial_step_done && + m_uncached_received_count == m_uncached_count; + } + + // Add a file to the list of required file (but don't fetch it yet) + void addFile(std::string name, std::string sha1); + + // Add a remote server to the list; ignored if not built with cURL + void addRemoteServer(std::string baseurl); + + // Steps the media downloader: + // - May load media into client by calling client->loadMedia() + // - May check media cache for files + // - May add files to media cache + // - May start remote transfers by calling httpfetch_async + // - May check for completion of current remote transfers + // - May start conventional transfers by calling client->request_media() + // - May inform server that all media has been loaded + // by calling client->received_media() + // After step has been called once, don't call addFile/addRemoteServer. + void step(Client *client); + + // Must be called for each file received through TOCLIENT_MEDIA + void conventionalTransferDone( + const std::string &name, + const std::string &data, + Client *client); + +private: + struct FileStatus { + bool received; + std::string sha1; + s32 current_remote; + std::vector available_remotes; + }; + + struct RemoteServerStatus { + std::string baseurl; + s32 active_count; + bool request_by_filename; + }; + + void initialStep(Client *client); + void remoteHashSetReceived(const HTTPFetchResult &fetch_result); + void remoteMediaReceived(const HTTPFetchResult &fetch_result, + Client *client); + s32 selectRemoteServer(FileStatus *filestatus); + void startRemoteMediaTransfers(); + void startConventionalTransfers(Client *client); + + bool checkAndLoad(const std::string &name, const std::string &sha1, + const std::string &data, bool is_from_cache, + Client *client); + + std::string serializeRequiredHashSet(); + static void deSerializeHashSet(const std::string &data, + std::set &result); + + // Maps filename to file status + std::map m_files; + + // Array of remote media servers + std::vector m_remotes; + + // Filesystem-based media cache + FileCache m_media_cache; + + // Has an attempt been made to load media files from the file cache? + // Have hash sets been requested from remote servers? + bool m_initial_step_done; + + // Total number of media files to load + s32 m_uncached_count; + + // Number of media files that have been received + s32 m_uncached_received_count; + + // Status of remote transfers + unsigned long m_httpfetch_caller; + unsigned long m_httpfetch_next_id; + long m_httpfetch_timeout; + s32 m_httpfetch_active; + s32 m_httpfetch_active_limit; + s32 m_outstanding_hash_sets; + std::map m_remote_file_transfers; + + // All files up to this name have either been received from a + // remote server or failed on all remote servers, so those files + // don't need to be looked at again + // (use m_files.upper_bound(m_name_bound) to get an iterator) + std::string m_name_bound; + +}; + +#endif // !CLIENTMEDIA_HEADER diff --git a/src/clientobject.cpp b/src/clientobject.cpp new file mode 100644 index 0000000..ae1be09 --- /dev/null +++ b/src/clientobject.cpp @@ -0,0 +1,69 @@ +/* +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. +*/ + +#include "clientobject.h" +#include "debug.h" +#include "porting.h" +#include "constants.h" + +/* + ClientActiveObject +*/ + +ClientActiveObject::ClientActiveObject(u16 id, IGameDef *gamedef, + ClientEnvironment *env): + ActiveObject(id), + m_gamedef(gamedef), + m_env(env) +{ +} + +ClientActiveObject::~ClientActiveObject() +{ + removeFromScene(true); +} + +ClientActiveObject* ClientActiveObject::create(ActiveObjectType type, + IGameDef *gamedef, ClientEnvironment *env) +{ + // Find factory function + std::map::iterator n; + n = m_types.find(type); + if(n == m_types.end()) { + // If factory is not found, just return. + dstream<<"WARNING: ClientActiveObject: No factory for type=" + <<(int)type<second; + ClientActiveObject *object = (*f)(gamedef, env); + return object; +} + +void ClientActiveObject::registerType(u16 type, Factory f) +{ + std::map::iterator n; + n = m_types.find(type); + if(n != m_types.end()) + return; + m_types[type] = f; +} + + diff --git a/src/clientobject.h b/src/clientobject.h new file mode 100644 index 0000000..4a77139 --- /dev/null +++ b/src/clientobject.h @@ -0,0 +1,125 @@ +/* +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. +*/ + +#ifndef CLIENTOBJECT_HEADER +#define CLIENTOBJECT_HEADER + +#include "irrlichttypes_extrabloated.h" +#include "activeobject.h" +#include + +/* + +Some planning +------------- + +* Client receives a network packet with information of added objects + in it +* Client supplies the information to its ClientEnvironment +* The environment adds the specified objects to itself + +*/ + +class ClientEnvironment; +class ITextureSource; +class IGameDef; +class LocalPlayer; +struct ItemStack; +class WieldMeshSceneNode; + +class ClientActiveObject : public ActiveObject +{ +public: + ClientActiveObject(u16 id, IGameDef *gamedef, ClientEnvironment *env); + virtual ~ClientActiveObject(); + + virtual void addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, + IrrlichtDevice *irr){} + virtual void removeFromScene(bool permanent){} + // 0 <= light_at_pos <= LIGHT_SUN + virtual void updateLight(u8 light_at_pos){} + virtual v3s16 getLightPosition(){return v3s16(0,0,0);} + virtual core::aabbox3d* getSelectionBox(){return NULL;} + virtual bool getCollisionBox(aabb3f *toset){return false;} + virtual bool collideWithObjects(){return false;} + virtual v3f getPosition(){return v3f(0,0,0);} + virtual scene::ISceneNode *getSceneNode(){return NULL;} + virtual scene::IMeshSceneNode *getMeshSceneNode(){return NULL;} + virtual scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode(){return NULL;} + virtual WieldMeshSceneNode *getWieldMeshSceneNode(){return NULL;} + virtual scene::IBillboardSceneNode *getSpriteSceneNode(){return NULL;} + virtual bool isPlayer() const {return false;} + virtual bool isLocalPlayer() const {return false;} + virtual void setAttachments(){} + virtual bool doShowSelectionBox(){return true;} + virtual void updateCameraOffset(v3s16 camera_offset){}; + + // Step object in time + virtual void step(float dtime, ClientEnvironment *env){} + + // Process a message sent by the server side object + virtual void processMessage(const std::string &data){} + + virtual std::string infoText() {return "";} + virtual std::string debugInfoText() {return "";} + + /* + This takes the return value of + ServerActiveObject::getClientInitializationData + */ + virtual void initialize(const std::string &data){} + + // Create a certain type of ClientActiveObject + static ClientActiveObject* create(ActiveObjectType type, IGameDef *gamedef, + ClientEnvironment *env); + + // If returns true, punch will not be sent to the server + virtual bool directReportPunch(v3f dir, const ItemStack *punchitem=NULL, + float time_from_last_punch=1000000) + { return false; } + +protected: + // Used for creating objects based on type + typedef ClientActiveObject* (*Factory)(IGameDef *gamedef, ClientEnvironment *env); + static void registerType(u16 type, Factory f); + IGameDef *m_gamedef; + ClientEnvironment *m_env; +private: + // Used for creating objects based on type + static std::map m_types; +}; + +struct DistanceSortedActiveObject +{ + ClientActiveObject *obj; + f32 d; + + DistanceSortedActiveObject(ClientActiveObject *a_obj, f32 a_d) + { + obj = a_obj; + d = a_d; + } + + bool operator < (const DistanceSortedActiveObject &other) const + { + return d < other.d; + } +}; + +#endif diff --git a/src/clientsimpleobject.h b/src/clientsimpleobject.h new file mode 100644 index 0000000..c94db22 --- /dev/null +++ b/src/clientsimpleobject.h @@ -0,0 +1,38 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef CLIENTSIMPLEOBJECT_HEADER +#define CLIENTSIMPLEOBJECT_HEADER + +#include "irrlichttypes_bloated.h" +class ClientEnvironment; + +class ClientSimpleObject +{ +protected: +public: + bool m_to_be_removed; + + ClientSimpleObject(): m_to_be_removed(false) {} + virtual ~ClientSimpleObject(){} + virtual void step(float dtime){} +}; + +#endif + diff --git a/src/clouds.cpp b/src/clouds.cpp new file mode 100644 index 0000000..8fea7a6 --- /dev/null +++ b/src/clouds.cpp @@ -0,0 +1,346 @@ +/* +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. +*/ + +#include "clouds.h" +#include "noise.h" +#include "constants.h" +#include "debug.h" +#include "main.h" // For g_profiler and g_settings +#include "profiler.h" +#include "settings.h" + +Clouds::Clouds( + scene::ISceneNode* parent, + scene::ISceneManager* mgr, + s32 id, + u32 seed, + s16 cloudheight +): + scene::ISceneNode(parent, mgr, id), + m_seed(seed), + m_camera_pos(0,0), + m_time(0), + m_camera_offset(0,0,0) +{ + m_material.setFlag(video::EMF_LIGHTING, false); + //m_material.setFlag(video::EMF_BACK_FACE_CULLING, false); + m_material.setFlag(video::EMF_BACK_FACE_CULLING, true); + m_material.setFlag(video::EMF_BILINEAR_FILTER, false); + m_material.setFlag(video::EMF_FOG_ENABLE, true); + m_material.setFlag(video::EMF_ANTI_ALIASING, true); + //m_material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; + m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + + m_cloud_y = BS * (cloudheight ? cloudheight : + g_settings->getS16("cloud_height")); + + m_box = core::aabbox3d(-BS*1000000,m_cloud_y-BS,-BS*1000000, + BS*1000000,m_cloud_y+BS,BS*1000000); + +} + +Clouds::~Clouds() +{ +} + +void Clouds::OnRegisterSceneNode() +{ + if(IsVisible) + { + SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT); + //SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID); + } + + ISceneNode::OnRegisterSceneNode(); +} + +#define MYROUND(x) (x > 0.0 ? (int)x : (int)x - 1) + +void Clouds::render() +{ + video::IVideoDriver* driver = SceneManager->getVideoDriver(); + + if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_TRANSPARENT) + //if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_SOLID) + return; + + ScopeProfiler sp(g_profiler, "Rendering of clouds, avg", SPT_AVG); + + bool enable_3d = g_settings->getBool("enable_3d_clouds"); + int num_faces_to_draw = enable_3d ? 6 : 1; + + m_material.setFlag(video::EMF_BACK_FACE_CULLING, enable_3d); + + driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); + driver->setMaterial(m_material); + + /* + Clouds move from X+ towards X- + */ + + const s16 cloud_radius_i = 12; + const float cloud_size = BS * 64; + const v2f cloud_speed(0, -BS * 2); + + const float cloud_full_radius = cloud_size * cloud_radius_i; + + // Position of cloud noise origin in world coordinates + v2f world_cloud_origin_pos_f = m_time * cloud_speed; + // Position of cloud noise origin from the camera + v2f cloud_origin_from_camera_f = world_cloud_origin_pos_f - m_camera_pos; + // The center point of drawing in the noise + v2f center_of_drawing_in_noise_f = -cloud_origin_from_camera_f; + // The integer center point of drawing in the noise + v2s16 center_of_drawing_in_noise_i( + MYROUND(center_of_drawing_in_noise_f.X / cloud_size), + MYROUND(center_of_drawing_in_noise_f.Y / cloud_size) + ); + // The world position of the integer center point of drawing in the noise + v2f world_center_of_drawing_in_noise_f = v2f( + center_of_drawing_in_noise_i.X * cloud_size, + center_of_drawing_in_noise_i.Y * cloud_size + ) + world_cloud_origin_pos_f; + + /*video::SColor c_top(128,b*240,b*240,b*255); + video::SColor c_side_1(128,b*230,b*230,b*255); + video::SColor c_side_2(128,b*220,b*220,b*245); + video::SColor c_bottom(128,b*205,b*205,b*230);*/ + video::SColorf c_top_f(m_color); + video::SColorf c_side_1_f(m_color); + video::SColorf c_side_2_f(m_color); + video::SColorf c_bottom_f(m_color); + c_side_1_f.r *= 0.95; + c_side_1_f.g *= 0.95; + c_side_1_f.b *= 0.95; + c_side_2_f.r *= 0.90; + c_side_2_f.g *= 0.90; + c_side_2_f.b *= 0.90; + c_bottom_f.r *= 0.80; + c_bottom_f.g *= 0.80; + c_bottom_f.b *= 0.80; + c_top_f.a = 0.9; + c_side_1_f.a = 0.9; + c_side_2_f.a = 0.9; + c_bottom_f.a = 0.9; + video::SColor c_top = c_top_f.toSColor(); + video::SColor c_side_1 = c_side_1_f.toSColor(); + video::SColor c_side_2 = c_side_2_f.toSColor(); + video::SColor c_bottom = c_bottom_f.toSColor(); + + // Get fog parameters for setting them back later + video::SColor fog_color(0,0,0,0); + video::E_FOG_TYPE fog_type = video::EFT_FOG_LINEAR; + f32 fog_start = 0; + f32 fog_end = 0; + f32 fog_density = 0; + bool fog_pixelfog = false; + bool fog_rangefog = false; + driver->getFog(fog_color, fog_type, fog_start, fog_end, fog_density, + fog_pixelfog, fog_rangefog); + + // Set our own fog + driver->setFog(fog_color, fog_type, cloud_full_radius * 0.5, + cloud_full_radius*1.2, fog_density, fog_pixelfog, fog_rangefog); + + // Read noise + + bool *grid = new bool[cloud_radius_i * 2 * cloud_radius_i * 2]; + + float cloud_size_noise = cloud_size / BS / 200; + + for(s16 zi = -cloud_radius_i; zi < cloud_radius_i; zi++) { + u32 si = (zi + cloud_radius_i) * cloud_radius_i * 2 + cloud_radius_i; + + for(s16 xi = -cloud_radius_i; xi < cloud_radius_i; xi++) { + u32 i = si + xi; + + v2s16 p_in_noise_i( + xi + center_of_drawing_in_noise_i.X, + zi + center_of_drawing_in_noise_i.Y + ); + + double noise = noise2d_perlin( + (float)p_in_noise_i.X * cloud_size_noise, + (float)p_in_noise_i.Y * cloud_size_noise, + m_seed, 3, 0.5); + grid[i] = (noise >= 0.4); + } + } + +#define GETINDEX(x, z, radius) (((z)+(radius))*(radius)*2 + (x)+(radius)) +#define INAREA(x, z, radius) \ + ((x) >= -(radius) && (x) < (radius) && (z) >= -(radius) && (z) < (radius)) + + for(s16 zi0=-cloud_radius_i; zi0= 0) + zi = cloud_radius_i - zi - 1; + if(xi >= 0) + xi = cloud_radius_i - xi - 1; + + u32 i = GETINDEX(xi, zi, cloud_radius_i); + + if(grid[i] == false) + continue; + + v2f p0 = v2f(xi,zi)*cloud_size + world_center_of_drawing_in_noise_f; + + video::S3DVertex v[4] = { + video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 1), + video::S3DVertex(0,0,0, 0,0,0, c_top, 1, 1), + video::S3DVertex(0,0,0, 0,0,0, c_top, 1, 0), + video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 0) + }; + + /*if(zi <= 0 && xi <= 0){ + v[0].Color.setBlue(255); + v[1].Color.setBlue(255); + v[2].Color.setBlue(255); + v[3].Color.setBlue(255); + }*/ + + f32 rx = cloud_size/2; + f32 ry = 8 * BS; + f32 rz = cloud_size / 2; + + for(int i=0; idrawVertexPrimitiveList(v, 4, indices, 2, + video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_16BIT); + } + } + + delete[] grid; + + // Restore fog settings + driver->setFog(fog_color, fog_type, fog_start, fog_end, fog_density, + fog_pixelfog, fog_rangefog); +} + +void Clouds::step(float dtime) +{ + m_time += dtime; +} + +void Clouds::update(v2f camera_p, video::SColorf color) +{ + m_camera_pos = camera_p; + m_color = color; + //m_brightness = brightness; + //dstream<<"m_brightness="< + +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. +*/ + +#ifndef CLOUDS_HEADER +#define CLOUDS_HEADER + +#include "irrlichttypes_extrabloated.h" +#include +#include "constants.h" + +class Clouds : public scene::ISceneNode +{ +public: + Clouds( + scene::ISceneNode* parent, + scene::ISceneManager* mgr, + s32 id, + u32 seed, + s16 cloudheight=0 + ); + + ~Clouds(); + + /* + ISceneNode methods + */ + + virtual void OnRegisterSceneNode(); + + virtual void render(); + + virtual const core::aabbox3d& getBoundingBox() const + { + return m_box; + } + + virtual u32 getMaterialCount() const + { + return 1; + } + + virtual video::SMaterial& getMaterial(u32 i) + { + return m_material; + } + + /* + Other stuff + */ + + void step(float dtime); + + void update(v2f camera_p, video::SColorf color); + + void updateCameraOffset(v3s16 camera_offset) + { + m_camera_offset = camera_offset; + m_box = core::aabbox3d(-BS * 1000000, m_cloud_y - BS - BS * camera_offset.Y, -BS * 1000000, + BS * 1000000, m_cloud_y + BS - BS * camera_offset.Y, BS * 1000000); + } + +private: + video::SMaterial m_material; + core::aabbox3d m_box; + float m_cloud_y; + video::SColorf m_color; + u32 m_seed; + v2f m_camera_pos; + float m_time; + v3s16 m_camera_offset; +}; + + + +#endif + diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in new file mode 100644 index 0000000..e111a65 --- /dev/null +++ b/src/cmake_config.h.in @@ -0,0 +1,33 @@ +// Filled in by the build system + +#ifndef CMAKE_CONFIG_H +#define CMAKE_CONFIG_H + +#define CMAKE_PROJECT_NAME "@PROJECT_NAME@" +#define CMAKE_VERSION_STRING "@VERSION_STRING@" +#define CMAKE_PRODUCT_VERSION_STRING "@VERSION_MAJOR@.@VERSION_MINOR@" +#define CMAKE_RUN_IN_PLACE @RUN_IN_PLACE@ +#define CMAKE_USE_GETTEXT @USE_GETTEXT@ +#define CMAKE_USE_CURL @USE_CURL@ +#define CMAKE_USE_SOUND @USE_SOUND@ +#define CMAKE_USE_FREETYPE @USE_FREETYPE@ +#define CMAKE_STATIC_SHAREDIR "@SHAREDIR@" +#define CMAKE_USE_LEVELDB @USE_LEVELDB@ +#define CMAKE_USE_LUAJIT @USE_LUAJIT@ +#define CMAKE_USE_REDIS @USE_REDIS@ +#define CMAKE_VERSION_MAJOR @VERSION_MAJOR@ +#define CMAKE_VERSION_MINOR @VERSION_MINOR@ +#define CMAKE_VERSION_PATCH @VERSION_PATCH@ +#define CMAKE_VERSION_PATCH_ORIG @VERSION_PATCH_ORIG@ +#define CMAKE_VERSION_EXTRA_STRING "@VERSION_EXTRA@" +#define CMAKE_HAVE_ENDIAN_H @HAVE_ENDIAN_H@ + +#ifdef NDEBUG + #define CMAKE_BUILD_TYPE "Release" +#else + #define CMAKE_BUILD_TYPE "Debug" +#endif +#define CMAKE_BUILD_INFO "BUILD_TYPE=" CMAKE_BUILD_TYPE " RUN_IN_PLACE=@RUN_IN_PLACE@ USE_GETTEXT=@USE_GETTEXT@ USE_SOUND=@USE_SOUND@ USE_CURL=@USE_CURL@ USE_FREETYPE=@USE_FREETYPE@ USE_LUAJIT=@USE_LUAJIT@ STATIC_SHAREDIR=@SHAREDIR@" + +#endif + diff --git a/src/cmake_config_githash.h.in b/src/cmake_config_githash.h.in new file mode 100644 index 0000000..4d5fcd6 --- /dev/null +++ b/src/cmake_config_githash.h.in @@ -0,0 +1,10 @@ +// Filled in by the build system +// Separated from cmake_config.h to avoid excessive rebuilds on every commit + +#ifndef CMAKE_CONFIG_GITHASH_H +#define CMAKE_CONFIG_GITHASH_H + +#define CMAKE_VERSION_GITHASH "@VERSION_GITHASH@" + +#endif + diff --git a/src/collision.cpp b/src/collision.cpp new file mode 100644 index 0000000..12eabff --- /dev/null +++ b/src/collision.cpp @@ -0,0 +1,636 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#include "collision.h" +#include "mapblock.h" +#include "map.h" +#include "nodedef.h" +#include "gamedef.h" +#include "log.h" +#include "environment.h" +#include "serverobject.h" +#include +#include +#include "util/timetaker.h" +#include "main.h" // g_profiler +#include "profiler.h" + +// float error is 10 - 9.96875 = 0.03125 +//#define COLL_ZERO 0.032 // broken unit tests +#define COLL_ZERO 0 + +// Helper function: +// Checks for collision of a moving aabbox with a static aabbox +// Returns -1 if no collision, 0 if X collision, 1 if Y collision, 2 if Z collision +// The time after which the collision occurs is stored in dtime. +int axisAlignedCollision( + const aabb3f &staticbox, const aabb3f &movingbox, + const v3f &speed, f32 d, f32 &dtime) +{ + //TimeTaker tt("axisAlignedCollision"); + + f32 xsize = (staticbox.MaxEdge.X - staticbox.MinEdge.X) - COLL_ZERO; // reduce box size for solve collision stuck (flying sand) + f32 ysize = (staticbox.MaxEdge.Y - staticbox.MinEdge.Y); // - COLL_ZERO; // Y - no sense for falling, but maybe try later + f32 zsize = (staticbox.MaxEdge.Z - staticbox.MinEdge.Z) - COLL_ZERO; + + aabb3f relbox( + movingbox.MinEdge.X - staticbox.MinEdge.X, + movingbox.MinEdge.Y - staticbox.MinEdge.Y, + movingbox.MinEdge.Z - staticbox.MinEdge.Z, + movingbox.MaxEdge.X - staticbox.MinEdge.X, + movingbox.MaxEdge.Y - staticbox.MinEdge.Y, + movingbox.MaxEdge.Z - staticbox.MinEdge.Z + ); + + if(speed.X > 0) // Check for collision with X- plane + { + if(relbox.MaxEdge.X <= d) + { + dtime = - relbox.MaxEdge.X / speed.X; + if((relbox.MinEdge.Y + speed.Y * dtime < ysize) && + (relbox.MaxEdge.Y + speed.Y * dtime > COLL_ZERO) && + (relbox.MinEdge.Z + speed.Z * dtime < zsize) && + (relbox.MaxEdge.Z + speed.Z * dtime > COLL_ZERO)) + return 0; + } + else if(relbox.MinEdge.X > xsize) + { + return -1; + } + } + else if(speed.X < 0) // Check for collision with X+ plane + { + if(relbox.MinEdge.X >= xsize - d) + { + dtime = (xsize - relbox.MinEdge.X) / speed.X; + if((relbox.MinEdge.Y + speed.Y * dtime < ysize) && + (relbox.MaxEdge.Y + speed.Y * dtime > COLL_ZERO) && + (relbox.MinEdge.Z + speed.Z * dtime < zsize) && + (relbox.MaxEdge.Z + speed.Z * dtime > COLL_ZERO)) + return 0; + } + else if(relbox.MaxEdge.X < 0) + { + return -1; + } + } + + // NO else if here + + if(speed.Y > 0) // Check for collision with Y- plane + { + if(relbox.MaxEdge.Y <= d) + { + dtime = - relbox.MaxEdge.Y / speed.Y; + if((relbox.MinEdge.X + speed.X * dtime < xsize) && + (relbox.MaxEdge.X + speed.X * dtime > COLL_ZERO) && + (relbox.MinEdge.Z + speed.Z * dtime < zsize) && + (relbox.MaxEdge.Z + speed.Z * dtime > COLL_ZERO)) + return 1; + } + else if(relbox.MinEdge.Y > ysize) + { + return -1; + } + } + else if(speed.Y < 0) // Check for collision with Y+ plane + { + if(relbox.MinEdge.Y >= ysize - d) + { + dtime = (ysize - relbox.MinEdge.Y) / speed.Y; + if((relbox.MinEdge.X + speed.X * dtime < xsize) && + (relbox.MaxEdge.X + speed.X * dtime > COLL_ZERO) && + (relbox.MinEdge.Z + speed.Z * dtime < zsize) && + (relbox.MaxEdge.Z + speed.Z * dtime > COLL_ZERO)) + return 1; + } + else if(relbox.MaxEdge.Y < 0) + { + return -1; + } + } + + // NO else if here + + if(speed.Z > 0) // Check for collision with Z- plane + { + if(relbox.MaxEdge.Z <= d) + { + dtime = - relbox.MaxEdge.Z / speed.Z; + if((relbox.MinEdge.X + speed.X * dtime < xsize) && + (relbox.MaxEdge.X + speed.X * dtime > COLL_ZERO) && + (relbox.MinEdge.Y + speed.Y * dtime < ysize) && + (relbox.MaxEdge.Y + speed.Y * dtime > COLL_ZERO)) + return 2; + } + //else if(relbox.MinEdge.Z > zsize) + //{ + // return -1; + //} + } + else if(speed.Z < 0) // Check for collision with Z+ plane + { + if(relbox.MinEdge.Z >= zsize - d) + { + dtime = (zsize - relbox.MinEdge.Z) / speed.Z; + if((relbox.MinEdge.X + speed.X * dtime < xsize) && + (relbox.MaxEdge.X + speed.X * dtime > COLL_ZERO) && + (relbox.MinEdge.Y + speed.Y * dtime < ysize) && + (relbox.MaxEdge.Y + speed.Y * dtime > COLL_ZERO)) + return 2; + } + //else if(relbox.MaxEdge.Z < 0) + //{ + // return -1; + //} + } + + return -1; +} + +// Helper function: +// Checks if moving the movingbox up by the given distance would hit a ceiling. +bool wouldCollideWithCeiling( + const std::vector &staticboxes, + const aabb3f &movingbox, + f32 y_increase, f32 d) +{ + //TimeTaker tt("wouldCollideWithCeiling"); + + assert(y_increase >= 0); // pre-condition + + for(std::vector::const_iterator + i = staticboxes.begin(); + i != staticboxes.end(); i++) + { + const aabb3f& staticbox = *i; + if((movingbox.MaxEdge.Y - d <= staticbox.MinEdge.Y) && + (movingbox.MaxEdge.Y + y_increase > staticbox.MinEdge.Y) && + (movingbox.MinEdge.X < staticbox.MaxEdge.X) && + (movingbox.MaxEdge.X > staticbox.MinEdge.X) && + (movingbox.MinEdge.Z < staticbox.MaxEdge.Z) && + (movingbox.MaxEdge.Z > staticbox.MinEdge.Z)) + return true; + } + + return false; +} + + +collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, + f32 pos_max_d, const aabb3f &box_0, + f32 stepheight, f32 dtime, + v3f &pos_f, v3f &speed_f, + v3f &accel_f,ActiveObject* self, + bool collideWithObjects) +{ + Map *map = &env->getMap(); + //TimeTaker tt("collisionMoveSimple"); + ScopeProfiler sp(g_profiler, "collisionMoveSimple avg", SPT_AVG); + + collisionMoveResult result; + + /* + Calculate new velocity + */ + if( dtime > 0.5 ) { + infostream<<"collisionMoveSimple: WARNING: maximum step interval exceeded, lost movement details!"< cboxes; + std::vector is_unloaded; + std::vector is_step_up; + std::vector is_object; + std::vector bouncy_values; + std::vector node_positions; + { + //TimeTaker tt2("collisionMoveSimple collect boxes"); + ScopeProfiler sp(g_profiler, "collisionMoveSimple collect boxes avg", SPT_AVG); + + v3s16 oldpos_i = floatToInt(pos_f, BS); + v3s16 newpos_i = floatToInt(pos_f + speed_f * dtime, BS); + s16 min_x = MYMIN(oldpos_i.X, newpos_i.X) + (box_0.MinEdge.X / BS) - 1; + s16 min_y = MYMIN(oldpos_i.Y, newpos_i.Y) + (box_0.MinEdge.Y / BS) - 1; + s16 min_z = MYMIN(oldpos_i.Z, newpos_i.Z) + (box_0.MinEdge.Z / BS) - 1; + s16 max_x = MYMAX(oldpos_i.X, newpos_i.X) + (box_0.MaxEdge.X / BS) + 1; + s16 max_y = MYMAX(oldpos_i.Y, newpos_i.Y) + (box_0.MaxEdge.Y / BS) + 1; + s16 max_z = MYMAX(oldpos_i.Z, newpos_i.Z) + (box_0.MaxEdge.Z / BS) + 1; + + for(s16 x = min_x; x <= max_x; x++) + for(s16 y = min_y; y <= max_y; y++) + for(s16 z = min_z; z <= max_z; z++) + { + v3s16 p(x,y,z); + + bool is_position_valid; + MapNode n = map->getNodeNoEx(p, &is_position_valid); + + if (is_position_valid) { + // Object collides into walkable nodes + + const ContentFeatures &f = gamedef->getNodeDefManager()->get(n); + if(f.walkable == false) + continue; + int n_bouncy_value = itemgroup_get(f.groups, "bouncy"); + + std::vector nodeboxes = n.getCollisionBoxes(gamedef->ndef()); + for(std::vector::iterator + i = nodeboxes.begin(); + i != nodeboxes.end(); i++) + { + aabb3f box = *i; + box.MinEdge += v3f(x, y, z)*BS; + box.MaxEdge += v3f(x, y, z)*BS; + cboxes.push_back(box); + is_unloaded.push_back(false); + is_step_up.push_back(false); + bouncy_values.push_back(n_bouncy_value); + node_positions.push_back(p); + is_object.push_back(false); + } + } + else { + // Collide with unloaded nodes + aabb3f box = getNodeBox(p, BS); + cboxes.push_back(box); + is_unloaded.push_back(true); + is_step_up.push_back(false); + bouncy_values.push_back(0); + node_positions.push_back(p); + is_object.push_back(false); + } + } + } // tt2 + + if(collideWithObjects) + { + ScopeProfiler sp(g_profiler, "collisionMoveSimple objects avg", SPT_AVG); + //TimeTaker tt3("collisionMoveSimple collect object boxes"); + + /* add object boxes to cboxes */ + + + std::vector objects; +#ifndef SERVER + ClientEnvironment *c_env = dynamic_cast(env); + if (c_env != 0) { + f32 distance = speed_f.getLength(); + std::vector clientobjects; + c_env->getActiveObjects(pos_f,distance * 1.5,clientobjects); + for (size_t i=0; i < clientobjects.size(); i++) { + if ((self == 0) || (self != clientobjects[i].obj)) { + objects.push_back((ActiveObject*)clientobjects[i].obj); + } + } + } + else +#endif + { + ServerEnvironment *s_env = dynamic_cast(env); + if (s_env != 0) { + f32 distance = speed_f.getLength(); + std::set s_objects = s_env->getObjectsInsideRadius(pos_f,distance * 1.5); + for (std::set::iterator iter = s_objects.begin(); iter != s_objects.end(); iter++) { + ServerActiveObject *current = s_env->getActiveObject(*iter); + if ((self == 0) || (self != current)) { + objects.push_back((ActiveObject*)current); + } + } + } + } + + for (std::vector::const_iterator iter = objects.begin(); + iter != objects.end(); ++iter) { + ActiveObject *object = *iter; + + if (object != NULL) { + aabb3f object_collisionbox; + if (object->getCollisionBox(&object_collisionbox) && + object->collideWithObjects()) { + cboxes.push_back(object_collisionbox); + is_unloaded.push_back(false); + is_step_up.push_back(false); + bouncy_values.push_back(0); + node_positions.push_back(v3s16(0,0,0)); + is_object.push_back(true); + } + } + } + } //tt3 + + assert(cboxes.size() == is_unloaded.size()); // post-condition + assert(cboxes.size() == is_step_up.size()); // post-condition + assert(cboxes.size() == bouncy_values.size()); // post-condition + assert(cboxes.size() == node_positions.size()); // post-condition + assert(cboxes.size() == is_object.size()); // post-condition + + /* + Collision detection + */ + + /* + Collision uncertainty radius + Make it a bit larger than the maximum distance of movement + */ + f32 d = pos_max_d * 1.1; + // A fairly large value in here makes moving smoother + //f32 d = 0.15*BS; + + // This should always apply, otherwise there are glitches + assert(d > pos_max_d); // invariant + + int loopcount = 0; + + while(dtime > BS*1e-10) + { + //TimeTaker tt3("collisionMoveSimple dtime loop"); + ScopeProfiler sp(g_profiler, "collisionMoveSimple dtime loop avg", SPT_AVG); + + // Avoid infinite loop + loopcount++; + if(loopcount >= 100) + { + infostream<<"collisionMoveSimple: WARNING: Loop count exceeded, aborting to avoid infiniite loop"<= nearest_dtime) + continue; + + nearest_dtime = dtime_tmp; + nearest_collided = collided; + nearest_boxindex = boxindex; + } + + if(nearest_collided == -1) + { + // No collision with any collision box. + pos_f += speed_f * dtime; + dtime = 0; // Set to 0 to avoid "infinite" loop due to small FP numbers + } + else + { + // Otherwise, a collision occurred. + + const aabb3f& cbox = cboxes[nearest_boxindex]; + + // Check for stairs. + bool step_up = (nearest_collided != 1) && // must not be Y direction + (movingbox.MinEdge.Y < cbox.MaxEdge.Y) && + (movingbox.MinEdge.Y + stepheight > cbox.MaxEdge.Y) && + (!wouldCollideWithCeiling(cboxes, movingbox, + cbox.MaxEdge.Y - movingbox.MinEdge.Y, + d)); + + // Get bounce multiplier + bool bouncy = (bouncy_values[nearest_boxindex] >= 1); + float bounce = -(float)bouncy_values[nearest_boxindex] / 100.0; + + // Move to the point of collision and reduce dtime by nearest_dtime + if(nearest_dtime < 0) + { + // Handle negative nearest_dtime (can be caused by the d allowance) + if(!step_up) + { + if(nearest_collided == 0) + pos_f.X += speed_f.X * nearest_dtime; + if(nearest_collided == 1) + pos_f.Y += speed_f.Y * nearest_dtime; + if(nearest_collided == 2) + pos_f.Z += speed_f.Z * nearest_dtime; + } + } + else + { + pos_f += speed_f * nearest_dtime; + dtime -= nearest_dtime; + } + + bool is_collision = true; + if(is_unloaded[nearest_boxindex]) + is_collision = false; + + CollisionInfo info; + if (is_object[nearest_boxindex]) { + info.type = COLLISION_OBJECT; + } + else { + info.type = COLLISION_NODE; + } + info.node_p = node_positions[nearest_boxindex]; + info.bouncy = bouncy; + info.old_speed = speed_f; + + // Set the speed component that caused the collision to zero + if(step_up) + { + // Special case: Handle stairs + is_step_up[nearest_boxindex] = true; + is_collision = false; + } + else if(nearest_collided == 0) // X + { + if(fabs(speed_f.X) > BS*3) + speed_f.X *= bounce; + else + speed_f.X = 0; + result.collides = true; + result.collides_xz = true; + } + else if(nearest_collided == 1) // Y + { + if(fabs(speed_f.Y) > BS*3) + speed_f.Y *= bounce; + else + speed_f.Y = 0; + result.collides = true; + } + else if(nearest_collided == 2) // Z + { + if(fabs(speed_f.Z) > BS*3) + speed_f.Z *= bounce; + else + speed_f.Z = 0; + result.collides = true; + result.collides_xz = true; + } + + info.new_speed = speed_f; + if(info.new_speed.getDistanceFrom(info.old_speed) < 0.1*BS) + is_collision = false; + + if(is_collision){ + result.collisions.push_back(info); + } + } + } + + /* + Final touches: Check if standing on ground, step up stairs. + */ + aabb3f box = box_0; + box.MinEdge += pos_f; + box.MaxEdge += pos_f; + for(u32 boxindex = 0; boxindex < cboxes.size(); boxindex++) + { + const aabb3f& cbox = cboxes[boxindex]; + + /* + See if the object is touching ground. + + Object touches ground if object's minimum Y is near node's + maximum Y and object's X-Z-area overlaps with the node's + X-Z-area. + + Use 0.15*BS so that it is easier to get on a node. + */ + if( + cbox.MaxEdge.X-d > box.MinEdge.X && + cbox.MinEdge.X+d < box.MaxEdge.X && + cbox.MaxEdge.Z-d > box.MinEdge.Z && + cbox.MinEdge.Z+d < box.MaxEdge.Z + ){ + if(is_step_up[boxindex]) + { + pos_f.Y += (cbox.MaxEdge.Y - box.MinEdge.Y); + box = box_0; + box.MinEdge += pos_f; + box.MaxEdge += pos_f; + } + if(fabs(cbox.MaxEdge.Y-box.MinEdge.Y) < 0.15*BS) + { + result.touching_ground = true; + if(is_unloaded[boxindex]) + result.standing_on_unloaded = true; + } + } + } + + return result; +} + +#if 0 +// This doesn't seem to work and isn't used +collisionMoveResult collisionMovePrecise(Map *map, IGameDef *gamedef, + f32 pos_max_d, const aabb3f &box_0, + f32 stepheight, f32 dtime, + v3f &pos_f, v3f &speed_f, v3f &accel_f) +{ + //TimeTaker tt("collisionMovePrecise"); + ScopeProfiler sp(g_profiler, "collisionMovePrecise avg", SPT_AVG); + + collisionMoveResult final_result; + + // If there is no speed, there are no collisions + if(speed_f.getLength() == 0) + return final_result; + + // Don't allow overly huge dtime + if(dtime > 2.0) + dtime = 2.0; + + f32 dtime_downcount = dtime; + + u32 loopcount = 0; + do + { + loopcount++; + + // Maximum time increment (for collision detection etc) + // time = distance / speed + f32 dtime_max_increment = 1.0; + if(speed_f.getLength() != 0) + dtime_max_increment = pos_max_d / speed_f.getLength(); + + // Maximum time increment is 10ms or lower + if(dtime_max_increment > 0.01) + dtime_max_increment = 0.01; + + f32 dtime_part; + if(dtime_downcount > dtime_max_increment) + { + dtime_part = dtime_max_increment; + dtime_downcount -= dtime_part; + } + else + { + dtime_part = dtime_downcount; + /* + Setting this to 0 (no -=dtime_part) disables an infinite loop + when dtime_part is so small that dtime_downcount -= dtime_part + does nothing + */ + dtime_downcount = 0; + } + + collisionMoveResult result = collisionMoveSimple(map, gamedef, + pos_max_d, box_0, stepheight, dtime_part, + pos_f, speed_f, accel_f); + + if(result.touching_ground) + final_result.touching_ground = true; + if(result.collides) + final_result.collides = true; + if(result.collides_xz) + final_result.collides_xz = true; + if(result.standing_on_unloaded) + final_result.standing_on_unloaded = true; + } + while(dtime_downcount > 0.001); + + return final_result; +} +#endif diff --git a/src/collision.h b/src/collision.h new file mode 100644 index 0000000..32086aa --- /dev/null +++ b/src/collision.h @@ -0,0 +1,104 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef COLLISION_HEADER +#define COLLISION_HEADER + +#include "irrlichttypes_bloated.h" +#include + +class Map; +class IGameDef; +class Environment; +class ActiveObject; + +enum CollisionType +{ + COLLISION_NODE, + COLLISION_OBJECT, +}; + +struct CollisionInfo +{ + enum CollisionType type; + v3s16 node_p; // COLLISION_NODE + bool bouncy; + v3f old_speed; + v3f new_speed; + + CollisionInfo(): + type(COLLISION_NODE), + node_p(-32768,-32768,-32768), + bouncy(false), + old_speed(0,0,0), + new_speed(0,0,0) + {} +}; + +struct collisionMoveResult +{ + bool touching_ground; + bool collides; + bool collides_xz; + bool standing_on_unloaded; + std::vector collisions; + + collisionMoveResult(): + touching_ground(false), + collides(false), + collides_xz(false), + standing_on_unloaded(false) + {} +}; + +// Moves using a single iteration; speed should not exceed pos_max_d/dtime +collisionMoveResult collisionMoveSimple(Environment *env,IGameDef *gamedef, + f32 pos_max_d, const aabb3f &box_0, + f32 stepheight, f32 dtime, + v3f &pos_f, v3f &speed_f, + v3f &accel_f,ActiveObject* self=0, + bool collideWithObjects=true); + +#if 0 +// This doesn't seem to work and isn't used +// Moves using as many iterations as needed +collisionMoveResult collisionMovePrecise(Map *map, IGameDef *gamedef, + f32 pos_max_d, const aabb3f &box_0, + f32 stepheight, f32 dtime, + v3f &pos_f, v3f &speed_f, v3f &accel_f); +#endif + +// Helper function: +// Checks for collision of a moving aabbox with a static aabbox +// Returns -1 if no collision, 0 if X collision, 1 if Y collision, 2 if Z collision +// dtime receives time until first collision, invalid if -1 is returned +int axisAlignedCollision( + const aabb3f &staticbox, const aabb3f &movingbox, + const v3f &speed, f32 d, f32 &dtime); + +// Helper function: +// Checks if moving the movingbox up by the given distance would hit a ceiling. +bool wouldCollideWithCeiling( + const std::vector &staticboxes, + const aabb3f &movingbox, + f32 y_increase, f32 d); + + +#endif + diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..2c15f43 --- /dev/null +++ b/src/config.h @@ -0,0 +1,87 @@ +/* + If CMake is used, includes the cmake-generated cmake_config.h. + Otherwise use default values +*/ + +#ifndef CONFIG_H +#define CONFIG_H + +#define PROJECT_NAME "Blokel" +#define RUN_IN_PLACE 0 +#define STATIC_SHAREDIR "" + +#define USE_GETTEXT 0 + +#ifndef USE_SOUND + #define USE_SOUND 0 +#endif + +#ifndef USE_CURL + #define USE_CURL 0 +#endif + +#ifndef USE_FREETYPE + #define USE_FREETYPE 0 +#endif + +#ifndef USE_LEVELDB + #define USE_LEVELDB 0 +#endif + +#ifndef USE_LUAJIT + #define USE_LUAJIT 0 +#endif + +#ifndef USE_REDIS + #define USE_REDIS 0 +#endif + +#define HAVE_ENDIAN_H 0 + +#ifdef USE_CMAKE_CONFIG_H + #include "cmake_config.h" + #undef PROJECT_NAME + #define PROJECT_NAME CMAKE_PROJECT_NAME + #undef RUN_IN_PLACE + #define RUN_IN_PLACE CMAKE_RUN_IN_PLACE + #undef USE_GETTEXT + #define USE_GETTEXT CMAKE_USE_GETTEXT + #undef USE_SOUND + #define USE_SOUND CMAKE_USE_SOUND + #undef USE_CURL + #define USE_CURL CMAKE_USE_CURL + #undef USE_FREETYPE + #define USE_FREETYPE CMAKE_USE_FREETYPE + #undef STATIC_SHAREDIR + #define STATIC_SHAREDIR CMAKE_STATIC_SHAREDIR + #undef USE_LEVELDB + #define USE_LEVELDB CMAKE_USE_LEVELDB + #undef USE_LUAJIT + #define USE_LUAJIT CMAKE_USE_LUAJIT + #undef USE_REDIS + #define USE_REDIS CMAKE_USE_REDIS + #undef VERSION_MAJOR + #define VERSION_MAJOR CMAKE_VERSION_MAJOR + #undef VERSION_MINOR + #define VERSION_MINOR CMAKE_VERSION_MINOR + #undef VERSION_PATCH + #define VERSION_PATCH CMAKE_VERSION_PATCH + #undef VERSION_PATCH_ORIG + #define VERSION_PATCH_ORIG CMAKE_VERSION_PATCH_ORIG + #undef VERSION_STRING + #define VERSION_STRING CMAKE_VERSION_STRING + #undef PRODUCT_VERSION_STRING + #define PRODUCT_VERSION_STRING CMAKE_PRODUCT_VERSION_STRING + #undef VERSION_EXTRA_STRING + #define VERSION_EXTRA_STRING CMAKE_VERSION_EXTRA_STRING + #undef HAVE_ENDIAN_H + #define HAVE_ENDIAN_H CMAKE_HAVE_ENDIAN_H +#endif + +#ifdef __ANDROID__ + #include "android_version.h" + #define VERSION_STRING CMAKE_VERSION_STRING +#endif + +#endif + diff --git a/src/constants.h b/src/constants.h new file mode 100644 index 0000000..d7163bf --- /dev/null +++ b/src/constants.h @@ -0,0 +1,113 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef CONSTANTS_HEADER +#define CONSTANTS_HEADER + +/* + All kinds of constants. + + Cross-platform compatibility crap should go in porting.h. + + Some things here are legacy crap. +*/ + +/* + Connection +*/ + +#define PEER_ID_INEXISTENT 0 +#define PEER_ID_SERVER 1 + +// Define for simulating the quirks of sending through internet. +// Causes the socket class to deliberately drop random packets. +// This disables unit testing of socket and connection. +#define INTERNET_SIMULATOR 0 +#define INTERNET_SIMULATOR_PACKET_LOSS 10 // 10 = easy, 4 = hard + +#define CONNECTION_TIMEOUT 30 + +#define RESEND_TIMEOUT_MIN 0.1 +#define RESEND_TIMEOUT_MAX 3.0 +// resend_timeout = avg_rtt * this +#define RESEND_TIMEOUT_FACTOR 4 + +/* + Server +*/ + +// This many blocks are sent when player is building +#define LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS 0 +// Override for the previous one when distance of block is very low +#define BLOCK_SEND_DISABLE_LIMITS_MAX_D 1 + +/* + Map-related things +*/ + +// The absolute working limit is (2^15 - viewing_range). +// I really don't want to make every algorithm to check if it's going near +// the limit or not, so this is lower. +#define MAP_GENERATION_LIMIT (31000) + +// Size of node in floating-point units +// The original idea behind this is to disallow plain casts between +// floating-point and integer positions, which potentially give wrong +// results. (negative coordinates, values between nodes, ...) +// Use floatToInt(p, BS) and intToFloat(p, BS). +#define BS (10.0) + +// Dimension of a MapBlock +#define MAP_BLOCKSIZE 16 +// This makes mesh updates too slow, as many meshes are updated during +// the main loop (related to TempMods and day/night) +//#define MAP_BLOCKSIZE 32 + +/* + Old stuff that shouldn't be hardcoded +*/ + +// Size of player's main inventory +#define PLAYER_INVENTORY_SIZE (8*4) + +// Maximum hit points of a player +#define PLAYER_MAX_HP 20 + +// Maximal breath of a player +#define PLAYER_MAX_BREATH 11 + +// Number of different files to try to save a player to if the first fails +// (because of a case-insensitive filesystem) +// TODO: Use case-insensitive player names instead of this hack. +#define PLAYER_FILE_ALTERNATE_TRIES 1000 + +/* + GUI related things +*/ + +// TODO: implement dpi-based scaling for windows and remove this hack +#if defined(_WIN32) + #define TTF_DEFAULT_FONT_SIZE (18) +#else + #define TTF_DEFAULT_FONT_SIZE (15) +#endif +#define DEFAULT_FONT_SIZE (10) + +#endif + diff --git a/src/content_abm.cpp b/src/content_abm.cpp new file mode 100644 index 0000000..1ee41b2 --- /dev/null +++ b/src/content_abm.cpp @@ -0,0 +1,37 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#include "content_abm.h" + +#include "environment.h" +#include "gamedef.h" +#include "nodedef.h" +#include "content_sao.h" +#include "settings.h" +#include "mapblock.h" // For getNodeBlockPos +#include "main.h" // for g_settings +#include "map.h" +#include "scripting_game.h" +#include "log.h" + +#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" + +void add_legacy_abms(ServerEnvironment *env, INodeDefManager *nodedef) { + +} diff --git a/src/content_abm.h b/src/content_abm.h new file mode 100644 index 0000000..0a91a96 --- /dev/null +++ b/src/content_abm.h @@ -0,0 +1,33 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef CONTENT_ABM_HEADER +#define CONTENT_ABM_HEADER + +class ServerEnvironment; +class INodeDefManager; + +/* + Legacy ActiveBlockModifiers +*/ + +void add_legacy_abms(ServerEnvironment *env, INodeDefManager *nodedef); + +#endif + diff --git a/src/content_cao.cpp b/src/content_cao.cpp new file mode 100644 index 0000000..4c8962e --- /dev/null +++ b/src/content_cao.cpp @@ -0,0 +1,1775 @@ +/* +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. +*/ + +#include +#include +#include +#include +#include +#include +#include "content_cao.h" +#include "util/numeric.h" // For IntervalLimiter +#include "util/serialize.h" +#include "util/mathconstants.h" +#include "client/tile.h" +#include "environment.h" +#include "collision.h" +#include "settings.h" +#include "serialization.h" // For decompressZlib +#include "gamedef.h" +#include "clientobject.h" +#include "mesh.h" +#include "itemdef.h" +#include "tool.h" +#include "content_cso.h" +#include "sound.h" +#include "nodedef.h" +#include "localplayer.h" +#include "map.h" +#include "main.h" // g_settings +#include "camera.h" // CameraModes +#include "wieldmesh.h" +#include "log.h" + +class Settings; +struct ToolCapabilities; + +#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" + +std::map ClientActiveObject::m_types; + +SmoothTranslator::SmoothTranslator(): + vect_old(0,0,0), + vect_show(0,0,0), + vect_aim(0,0,0), + anim_counter(0), + anim_time(0), + anim_time_counter(0), + aim_is_end(true) +{} + +void SmoothTranslator::init(v3f vect) +{ + vect_old = vect; + vect_show = vect; + vect_aim = vect; + anim_counter = 0; + anim_time = 0; + anim_time_counter = 0; + aim_is_end = true; +} + +void SmoothTranslator::sharpen() +{ + init(vect_show); +} + +void SmoothTranslator::update(v3f vect_new, bool is_end_position, float update_interval) +{ + aim_is_end = is_end_position; + vect_old = vect_show; + vect_aim = vect_new; + if(update_interval > 0) + { + anim_time = update_interval; + } else { + if(anim_time < 0.001 || anim_time > 1.0) + anim_time = anim_time_counter; + else + anim_time = anim_time * 0.9 + anim_time_counter * 0.1; + } + anim_time_counter = 0; + anim_counter = 0; +} + +void SmoothTranslator::translate(f32 dtime) +{ + anim_time_counter = anim_time_counter + dtime; + anim_counter = anim_counter + dtime; + v3f vect_move = vect_aim - vect_old; + f32 moveratio = 1.0; + if(anim_time > 0.001) + moveratio = anim_time_counter / anim_time; + // Move a bit less than should, to avoid oscillation + moveratio = moveratio * 0.8; + float move_end = 1.5; + if(aim_is_end) + move_end = 1.0; + if(moveratio > move_end) + moveratio = move_end; + vect_show = vect_old + vect_move * moveratio; +} + +bool SmoothTranslator::is_moving() +{ + return ((anim_time_counter / anim_time) < 1.4); +} + +/* + Other stuff +*/ + +static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill, + float txs, float tys, int col, int row) +{ + video::SMaterial& material = bill->getMaterial(0); + core::matrix4& matrix = material.getTextureMatrix(0); + matrix.setTextureTranslate(txs*col, tys*row); + matrix.setTextureScale(txs, tys); +} + +/* + TestCAO +*/ + +class TestCAO : public ClientActiveObject +{ +public: + TestCAO(IGameDef *gamedef, ClientEnvironment *env); + virtual ~TestCAO(); + + ActiveObjectType getType() const + { + return ACTIVEOBJECT_TYPE_TEST; + } + + static ClientActiveObject* create(IGameDef *gamedef, ClientEnvironment *env); + + void addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, + IrrlichtDevice *irr); + void removeFromScene(bool permanent); + void updateLight(u8 light_at_pos); + v3s16 getLightPosition(); + void updateNodePos(); + + void step(float dtime, ClientEnvironment *env); + + void processMessage(const std::string &data); + + bool getCollisionBox(aabb3f *toset) { return false; } +private: + scene::IMeshSceneNode *m_node; + v3f m_position; +}; + +// Prototype +TestCAO proto_TestCAO(NULL, NULL); + +TestCAO::TestCAO(IGameDef *gamedef, ClientEnvironment *env): + ClientActiveObject(0, gamedef, env), + m_node(NULL), + m_position(v3f(0,10*BS,0)) +{ + ClientActiveObject::registerType(getType(), create); +} + +TestCAO::~TestCAO() +{ +} + +ClientActiveObject* TestCAO::create(IGameDef *gamedef, ClientEnvironment *env) +{ + return new TestCAO(gamedef, env); +} + +void TestCAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, + IrrlichtDevice *irr) +{ + if(m_node != NULL) + return; + + //video::IVideoDriver* driver = smgr->getVideoDriver(); + + scene::SMesh *mesh = new scene::SMesh(); + scene::IMeshBuffer *buf = new scene::SMeshBuffer(); + video::SColor c(255,255,255,255); + video::S3DVertex vertices[4] = + { + video::S3DVertex(-BS/2,-BS/4,0, 0,0,0, c, 0,1), + video::S3DVertex(BS/2,-BS/4,0, 0,0,0, c, 1,1), + video::S3DVertex(BS/2,BS/4,0, 0,0,0, c, 1,0), + video::S3DVertex(-BS/2,BS/4,0, 0,0,0, c, 0,0), + }; + u16 indices[] = {0,1,2,2,3,0}; + buf->append(vertices, 4, indices, 6); + // Set material + buf->getMaterial().setFlag(video::EMF_LIGHTING, false); + buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false); + buf->getMaterial().setTexture(0, tsrc->getTexture("rat.png")); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); + buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); + buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + // Add to mesh + mesh->addMeshBuffer(buf); + buf->drop(); + m_node = smgr->addMeshSceneNode(mesh, NULL); + mesh->drop(); + updateNodePos(); +} + +void TestCAO::removeFromScene(bool permanent) +{ + if(m_node == NULL) + return; + + m_node->remove(); + m_node = NULL; +} + +void TestCAO::updateLight(u8 light_at_pos) +{ +} + +v3s16 TestCAO::getLightPosition() +{ + return floatToInt(m_position, BS); +} + +void TestCAO::updateNodePos() +{ + if(m_node == NULL) + return; + + m_node->setPosition(m_position); + //m_node->setRotation(v3f(0, 45, 0)); +} + +void TestCAO::step(float dtime, ClientEnvironment *env) +{ + if(m_node) + { + v3f rot = m_node->getRotation(); + //infostream<<"dtime="<>cmd; + if(cmd == 0) + { + v3f newpos; + is>>newpos.X; + is>>newpos.Y; + is>>newpos.Z; + m_position = newpos; + updateNodePos(); + } +} + +/* + ItemCAO +*/ + +class ItemCAO : public ClientActiveObject +{ +public: + ItemCAO(IGameDef *gamedef, ClientEnvironment *env); + virtual ~ItemCAO(); + + ActiveObjectType getType() const + { + return ACTIVEOBJECT_TYPE_ITEM; + } + + static ClientActiveObject* create(IGameDef *gamedef, ClientEnvironment *env); + + void addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, + IrrlichtDevice *irr); + void removeFromScene(bool permanent); + void updateLight(u8 light_at_pos); + v3s16 getLightPosition(); + void updateNodePos(); + void updateInfoText(); + void updateTexture(); + + void step(float dtime, ClientEnvironment *env); + + void processMessage(const std::string &data); + + void initialize(const std::string &data); + + core::aabbox3d* getSelectionBox() + {return &m_selection_box;} + v3f getPosition() + {return m_position;} + + std::string infoText() + {return m_infotext;} + + bool getCollisionBox(aabb3f *toset) { return false; } +private: + core::aabbox3d m_selection_box; + scene::IMeshSceneNode *m_node; + v3f m_position; + std::string m_itemstring; + std::string m_infotext; +}; + +#include "inventory.h" + +// Prototype +ItemCAO proto_ItemCAO(NULL, NULL); + +ItemCAO::ItemCAO(IGameDef *gamedef, ClientEnvironment *env): + ClientActiveObject(0, gamedef, env), + m_selection_box(-BS/3.,0.0,-BS/3., BS/3.,BS*2./3.,BS/3.), + m_node(NULL), + m_position(v3f(0,10*BS,0)) +{ + if(!gamedef && !env) + { + ClientActiveObject::registerType(getType(), create); + } +} + +ItemCAO::~ItemCAO() +{ +} + +ClientActiveObject* ItemCAO::create(IGameDef *gamedef, ClientEnvironment *env) +{ + return new ItemCAO(gamedef, env); +} + +void ItemCAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, + IrrlichtDevice *irr) +{ + if(m_node != NULL) + return; + + //video::IVideoDriver* driver = smgr->getVideoDriver(); + + scene::SMesh *mesh = new scene::SMesh(); + scene::IMeshBuffer *buf = new scene::SMeshBuffer(); + video::SColor c(255,255,255,255); + video::S3DVertex vertices[4] = + { + /*video::S3DVertex(-BS/2,-BS/4,0, 0,0,0, c, 0,1), + video::S3DVertex(BS/2,-BS/4,0, 0,0,0, c, 1,1), + video::S3DVertex(BS/2,BS/4,0, 0,0,0, c, 1,0), + video::S3DVertex(-BS/2,BS/4,0, 0,0,0, c, 0,0),*/ + video::S3DVertex(BS/3.,0,0, 0,0,0, c, 0,1), + video::S3DVertex(-BS/3.,0,0, 0,0,0, c, 1,1), + video::S3DVertex(-BS/3.,0+BS*2./3.,0, 0,0,0, c, 1,0), + video::S3DVertex(BS/3.,0+BS*2./3.,0, 0,0,0, c, 0,0), + }; + u16 indices[] = {0,1,2,2,3,0}; + buf->append(vertices, 4, indices, 6); + // Set material + buf->getMaterial().setFlag(video::EMF_LIGHTING, false); + buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false); + // Initialize with a generated placeholder texture + buf->getMaterial().setTexture(0, tsrc->getTexture("")); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); + buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); + buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + // Add to mesh + mesh->addMeshBuffer(buf); + buf->drop(); + m_node = smgr->addMeshSceneNode(mesh, NULL); + mesh->drop(); + updateNodePos(); + + /* + Update image of node + */ + + updateTexture(); +} + +void ItemCAO::removeFromScene(bool permanent) +{ + if(m_node == NULL) + return; + + m_node->remove(); + m_node = NULL; +} + +void ItemCAO::updateLight(u8 light_at_pos) +{ + if(m_node == NULL) + return; + + u8 li = decode_light(light_at_pos); + video::SColor color(255,li,li,li); + setMeshColor(m_node->getMesh(), color); +} + +v3s16 ItemCAO::getLightPosition() +{ + return floatToInt(m_position + v3f(0,0.5*BS,0), BS); +} + +void ItemCAO::updateNodePos() +{ + if(m_node == NULL) + return; + + m_node->setPosition(m_position); +} + +void ItemCAO::updateInfoText() +{ + try{ + IItemDefManager *idef = m_gamedef->idef(); + ItemStack item; + item.deSerialize(m_itemstring, idef); + if(item.isKnown(idef)) + m_infotext = item.getDefinition(idef).description; + else + m_infotext = "Unknown item: '" + m_itemstring + "'"; + if(item.count >= 2) + m_infotext += " (" + itos(item.count) + ")"; + } + catch(SerializationError &e) + { + m_infotext = "Unknown item: '" + m_itemstring + "'"; + } +} + +void ItemCAO::updateTexture() +{ + if(m_node == NULL) + return; + + // Create an inventory item to see what is its image + std::istringstream is(m_itemstring, std::ios_base::binary); + video::ITexture *texture = NULL; + try{ + IItemDefManager *idef = m_gamedef->idef(); + ItemStack item; + item.deSerialize(is, idef); + texture = idef->getInventoryTexture(item.getDefinition(idef).name, m_gamedef); + } + catch(SerializationError &e) + { + infostream<<"WARNING: "<<__FUNCTION_NAME + <<": error deSerializing itemstring \"" + <getMaterial(0).setTexture(0, texture); +} + + +void ItemCAO::step(float dtime, ClientEnvironment *env) +{ + if(m_node) + { + /*v3f rot = m_node->getRotation(); + rot.Y += dtime * 120; + m_node->setRotation(rot);*/ + LocalPlayer *player = env->getLocalPlayer(); + assert(player); + v3f rot = m_node->getRotation(); + rot.Y = 180.0 - (player->getYaw()); + m_node->setRotation(rot); + } +} + +void ItemCAO::processMessage(const std::string &data) +{ + //infostream<<"ItemCAO: Got message"< >()), + m_attachment_bone(""), + m_attachment_position(v3f(0,0,0)), + m_attachment_rotation(v3f(0,0,0)), + m_attached_to_local(false), + m_anim_frame(0), + m_anim_num_frames(1), + m_anim_framelength(0.2), + m_anim_timer(0), + m_reset_textures_timer(-1), + m_visuals_expired(false), + m_step_distance_counter(0), + m_last_light(255), + m_is_visible(false) +{ + if(gamedef == NULL) + ClientActiveObject::registerType(getType(), create); +} + +bool GenericCAO::getCollisionBox(aabb3f *toset) +{ + if (m_prop.physical) + { + //update collision box + toset->MinEdge = m_prop.collisionbox.MinEdge * BS; + toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; + + toset->MinEdge += m_position; + toset->MaxEdge += m_position; + + return true; + } + + return false; +} + +bool GenericCAO::collideWithObjects() +{ + return m_prop.collideWithObjects; +} + +void GenericCAO::initialize(const std::string &data) +{ + infostream<<"GenericCAO: Got init data"<getPlayer(m_name.c_str()); + if(player && player->isLocal()) + { + m_is_local_player = true; + m_is_visible = false; + LocalPlayer* localplayer = dynamic_cast(player); + + assert( localplayer != NULL ); + localplayer->setCAO(this); + } + m_env->addPlayerName(m_name.c_str()); + } +} + +GenericCAO::~GenericCAO() +{ + if(m_is_player) + { + m_env->removePlayerName(m_name.c_str()); + } + removeFromScene(true); +} + +core::aabbox3d* GenericCAO::getSelectionBox() +{ + if(!m_prop.is_visible || !m_is_visible || m_is_local_player || getParent() != NULL) + return NULL; + return &m_selection_box; +} + +v3f GenericCAO::getPosition() +{ + if (getParent() != NULL) { + scene::ISceneNode *node = getSceneNode(); + if (node) + return node->getAbsolutePosition(); + else + return m_position; + } + return pos_translator.vect_show; +} + +scene::ISceneNode* GenericCAO::getSceneNode() +{ + if (m_meshnode) + return m_meshnode; + if (m_animated_meshnode) + return m_animated_meshnode; + if (m_wield_meshnode) + return m_wield_meshnode; + if (m_spritenode) + return m_spritenode; + return NULL; +} + +scene::IMeshSceneNode* GenericCAO::getMeshSceneNode() +{ + return m_meshnode; +} + +scene::IAnimatedMeshSceneNode* GenericCAO::getAnimatedMeshSceneNode() +{ + return m_animated_meshnode; +} + +WieldMeshSceneNode* GenericCAO::getWieldMeshSceneNode() +{ + return m_wield_meshnode; +} + +scene::IBillboardSceneNode* GenericCAO::getSpriteSceneNode() +{ + return m_spritenode; +} + +void GenericCAO::setAttachments() +{ + updateAttachments(); +} + +ClientActiveObject* GenericCAO::getParent() +{ + ClientActiveObject *obj = NULL; + + u16 attached_id = m_env->m_attachements[getId()]; + + if ((attached_id != 0) && + (attached_id != getId())) { + obj = m_env->getActiveObject(attached_id); + } + return obj; +} + +void GenericCAO::removeFromScene(bool permanent) +{ + // Should be true when removing the object permanently and false when refreshing (eg: updating visuals) + if((m_env != NULL) && (permanent)) + { + for(std::vector::iterator ci = m_children.begin(); + ci != m_children.end(); ci++) + { + if (m_env->m_attachements[*ci] == getId()) { + m_env->m_attachements[*ci] = 0; + } + } + + m_env->m_attachements[getId()] = 0; + + LocalPlayer* player = m_env->getLocalPlayer(); + if (this == player->parent) { + player->parent = NULL; + player->isAttached = false; + } + } + + if(m_meshnode) + { + m_meshnode->remove(); + m_meshnode->drop(); + m_meshnode = NULL; + } + if(m_animated_meshnode) + { + m_animated_meshnode->remove(); + m_animated_meshnode->drop(); + m_animated_meshnode = NULL; + } + if(m_wield_meshnode) + { + m_wield_meshnode->remove(); + m_wield_meshnode->drop(); + m_wield_meshnode = NULL; + } + if(m_spritenode) + { + m_spritenode->remove(); + m_spritenode->drop(); + m_spritenode = NULL; + } + if (m_textnode) + { + m_textnode->remove(); + m_textnode->drop(); + m_textnode = NULL; + } +} + +void GenericCAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, + IrrlichtDevice *irr) +{ + m_smgr = smgr; + m_irr = irr; + + if (getSceneNode() != NULL) + return; + + m_visuals_expired = false; + + if(!m_prop.is_visible) + return; + + //video::IVideoDriver* driver = smgr->getVideoDriver(); + + if(m_prop.visual == "sprite") + { + infostream<<"GenericCAO::addToScene(): single_sprite"<addBillboardSceneNode( + NULL, v2f(1, 1), v3f(0,0,0), -1); + m_spritenode->grab(); + m_spritenode->setMaterialTexture(0, + tsrc->getTexture("unknown_node.png")); + m_spritenode->setMaterialFlag(video::EMF_LIGHTING, false); + m_spritenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); + m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF); + m_spritenode->setMaterialFlag(video::EMF_FOG_ENABLE, true); + u8 li = m_last_light; + m_spritenode->setColor(video::SColor(255,li,li,li)); + m_spritenode->setSize(m_prop.visual_size*BS); + { + const float txs = 1.0 / 1; + const float tys = 1.0 / 1; + setBillboardTextureMatrix(m_spritenode, + txs, tys, 0, 0); + } + } + else if(m_prop.visual == "upright_sprite") { + scene::SMesh *mesh = new scene::SMesh(); + double dx = BS*m_prop.visual_size.X/2; + double dy = BS*m_prop.visual_size.Y/2; + { // Front + scene::IMeshBuffer *buf = new scene::SMeshBuffer(); + u8 li = m_last_light; + video::SColor c(255,li,li,li); + video::S3DVertex vertices[4] = + { + video::S3DVertex(-dx,-dy,0, 0,0,0, c, 0,1), + video::S3DVertex(dx,-dy,0, 0,0,0, c, 1,1), + video::S3DVertex(dx,dy,0, 0,0,0, c, 1,0), + video::S3DVertex(-dx,dy,0, 0,0,0, c, 0,0), + }; + u16 indices[] = {0,1,2,2,3,0}; + buf->append(vertices, 4, indices, 6); + // Set material + buf->getMaterial().setFlag(video::EMF_LIGHTING, false); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); + buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); + buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + // Add to mesh + mesh->addMeshBuffer(buf); + buf->drop(); + } + { // Back + scene::IMeshBuffer *buf = new scene::SMeshBuffer(); + u8 li = m_last_light; + video::SColor c(255,li,li,li); + video::S3DVertex vertices[4] = + { + video::S3DVertex(dx,-dy,0, 0,0,0, c, 1,1), + video::S3DVertex(-dx,-dy,0, 0,0,0, c, 0,1), + video::S3DVertex(-dx,dy,0, 0,0,0, c, 0,0), + video::S3DVertex(dx,dy,0, 0,0,0, c, 1,0), + }; + u16 indices[] = {0,1,2,2,3,0}; + buf->append(vertices, 4, indices, 6); + // Set material + buf->getMaterial().setFlag(video::EMF_LIGHTING, false); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); + buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); + buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; + // Add to mesh + mesh->addMeshBuffer(buf); + buf->drop(); + } + m_meshnode = smgr->addMeshSceneNode(mesh, NULL); + m_meshnode->grab(); + mesh->drop(); + // Set it to use the materials of the meshbuffers directly. + // This is needed for changing the texture in the future + m_meshnode->setReadOnlyMaterials(true); + } + else if(m_prop.visual == "cube") { + infostream<<"GenericCAO::addToScene(): cube"<addMeshSceneNode(mesh, NULL); + m_meshnode->grab(); + mesh->drop(); + + m_meshnode->setScale(v3f(m_prop.visual_size.X, + m_prop.visual_size.Y, + m_prop.visual_size.X)); + u8 li = m_last_light; + setMeshColor(m_meshnode->getMesh(), video::SColor(255,li,li,li)); + + m_meshnode->setMaterialFlag(video::EMF_LIGHTING, false); + m_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); + m_meshnode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF); + m_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true); + } + else if(m_prop.visual == "mesh") { + infostream<<"GenericCAO::addToScene(): mesh"<getMesh(m_prop.mesh); + if(mesh) + { + m_animated_meshnode = smgr->addAnimatedMeshSceneNode(mesh, NULL); + m_animated_meshnode->grab(); + mesh->drop(); // The scene node took hold of it + m_animated_meshnode->animateJoints(); // Needed for some animations + m_animated_meshnode->setScale(v3f(m_prop.visual_size.X, + m_prop.visual_size.Y, + m_prop.visual_size.X)); + u8 li = m_last_light; + setMeshColor(m_animated_meshnode->getMesh(), video::SColor(255,li,li,li)); + + m_animated_meshnode->setMaterialFlag(video::EMF_LIGHTING, false); + m_animated_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); + m_animated_meshnode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF); + m_animated_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true); + } + else + errorstream<<"GenericCAO::addToScene(): Could not load mesh "<= 1){ + infostream<<"textures[0]: "<idef(); + ItemStack item(m_prop.textures[0], 1, 0, "", idef); + + m_wield_meshnode = new WieldMeshSceneNode( + smgr->getRootSceneNode(), smgr, -1); + m_wield_meshnode->setItem(item, m_gamedef); + + m_wield_meshnode->setScale(v3f(m_prop.visual_size.X/2, + m_prop.visual_size.Y/2, + m_prop.visual_size.X/2)); + u8 li = m_last_light; + m_wield_meshnode->setColor(video::SColor(255,li,li,li)); + } + } else { + infostream<<"GenericCAO::addToScene(): \""<getGUIEnvironment(); + std::wstring wname = narrow_to_wide(m_name); + m_textnode = smgr->addTextSceneNode(gui->getSkin()->getFont(), + wname.c_str(), video::SColor(255,255,255,255), node); + m_textnode->grab(); + m_textnode->setPosition(v3f(0, BS*1.1, 0)); + } + + updateNodePos(); + updateAnimation(); + updateBonePosition(); + updateAttachments(); +} + +void GenericCAO::updateLight(u8 light_at_pos) +{ + u8 li = decode_light(light_at_pos); + if(li != m_last_light) + { + m_last_light = li; + video::SColor color(255,li,li,li); + if(m_meshnode) + setMeshColor(m_meshnode->getMesh(), color); + if(m_animated_meshnode) + setMeshColor(m_animated_meshnode->getMesh(), color); + if(m_wield_meshnode) + m_wield_meshnode->setColor(color); + if(m_spritenode) + m_spritenode->setColor(color); + } +} + +v3s16 GenericCAO::getLightPosition() +{ + return floatToInt(m_position, BS); +} + +void GenericCAO::updateNodePos() +{ + if (getParent() != NULL) + return; + + scene::ISceneNode *node = getSceneNode(); + + if (node) { + v3s16 camera_offset = m_env->getCameraOffset(); + node->setPosition(pos_translator.vect_show - intToFloat(camera_offset, BS)); + if (node != m_spritenode) { // rotate if not a sprite + v3f rot = node->getRotation(); + rot.Y = -m_yaw; + node->setRotation(rot); + } + } +} + +void GenericCAO::step(float dtime, ClientEnvironment *env) +{ + // Handel model of local player instantly to prevent lags + if(m_is_local_player) + { + LocalPlayer *player = m_env->getLocalPlayer(); + + if (m_is_visible) + { + int old_anim = player->last_animation; + float old_anim_speed = player->last_animation_speed; + m_position = player->getPosition() + v3f(0,BS,0); + m_velocity = v3f(0,0,0); + m_acceleration = v3f(0,0,0); + pos_translator.vect_show = m_position; + m_yaw = player->getYaw(); + PlayerControl controls = player->getPlayerControl(); + + bool walking = false; + if(controls.up || controls.down || controls.left || controls.right) + walking = true; + + f32 new_speed = player->local_animation_speed; + v2s32 new_anim = v2s32(0,0); + bool allow_update = false; + + // increase speed if using fast or flying fast + if((g_settings->getBool("fast_move") && + m_gamedef->checkLocalPrivilege("fast")) && + (controls.aux1 || + (!player->touching_ground && + g_settings->getBool("free_move") && + m_gamedef->checkLocalPrivilege("fly")))) + new_speed *= 1.5; + // slowdown speed if sneeking + if(controls.sneak && walking) + new_speed /= 2; + + if(walking && (controls.LMB || controls.RMB)) + { + new_anim = player->local_animations[3]; + player->last_animation = WD_ANIM; + } else if(walking) { + new_anim = player->local_animations[1]; + player->last_animation = WALK_ANIM; + } else if(controls.LMB || controls.RMB) { + new_anim = player->local_animations[2]; + player->last_animation = DIG_ANIM; + } + + // Apply animations if input detected and not attached + // or set idle animation + if ((new_anim.X + new_anim.Y) > 0 && !player->isAttached) + { + allow_update = true; + m_animation_range = new_anim; + m_animation_speed = new_speed; + player->last_animation_speed = m_animation_speed; + } else { + player->last_animation = NO_ANIM; + + if (old_anim != NO_ANIM) + { + m_animation_range = player->local_animations[0]; + updateAnimation(); + } + } + + // Update local player animations + if ((player->last_animation != old_anim || + m_animation_speed != old_anim_speed) && + player->last_animation != NO_ANIM && allow_update) + updateAnimation(); + + } + } + + if(m_visuals_expired && m_smgr && m_irr){ + m_visuals_expired = false; + + // Attachments, part 1: All attached objects must be unparented first, + // or Irrlicht causes a segmentation fault + for(std::vector::iterator ci = m_children.begin(); + ci != m_children.end();) + { + if (m_env->m_attachements[*ci] != getId()) { + ci = m_children.erase(ci); + continue; + } + ClientActiveObject *obj = m_env->getActiveObject(*ci); + if (obj) { + scene::ISceneNode *child_node = obj->getSceneNode(); + if (child_node) + child_node->setParent(m_smgr->getRootSceneNode()); + } + ++ci; + } + + removeFromScene(false); + addToScene(m_smgr, m_gamedef->tsrc(), m_irr); + + // Attachments, part 2: Now that the parent has been refreshed, put its attachments back + for(std::vector::iterator ci = m_children.begin(); + ci != m_children.end(); ci++) + { + // Get the object of the child + ClientActiveObject *obj = m_env->getActiveObject(*ci); + if (obj) + obj->setAttachments(); + } + } + + // Make sure m_is_visible is always applied + scene::ISceneNode *node = getSceneNode(); + if (node) + node->setVisible(m_is_visible); + + if(getParent() != NULL) // Attachments should be glued to their parent by Irrlicht + { + // Set these for later + m_position = getPosition(); + m_velocity = v3f(0,0,0); + m_acceleration = v3f(0,0,0); + pos_translator.vect_show = m_position; + + if(m_is_local_player) // Update local player attachment position + { + LocalPlayer *player = m_env->getLocalPlayer(); + player->overridePosition = getParent()->getPosition(); + m_env->getLocalPlayer()->parent = getParent(); + } + } else { + v3f lastpos = pos_translator.vect_show; + + if(m_prop.physical) + { + core::aabbox3d box = m_prop.collisionbox; + box.MinEdge *= BS; + box.MaxEdge *= BS; + collisionMoveResult moveresult; + f32 pos_max_d = BS*0.125; // Distance per iteration + v3f p_pos = m_position; + v3f p_velocity = m_velocity; + v3f p_acceleration = m_acceleration; + moveresult = collisionMoveSimple(env,env->getGameDef(), + pos_max_d, box, m_prop.stepheight, dtime, + p_pos, p_velocity, p_acceleration, + this, m_prop.collideWithObjects); + // Apply results + m_position = p_pos; + m_velocity = p_velocity; + m_acceleration = p_acceleration; + + bool is_end_position = moveresult.collides; + pos_translator.update(m_position, is_end_position, dtime); + pos_translator.translate(dtime); + updateNodePos(); + } else { + m_position += dtime * m_velocity + 0.5 * dtime * dtime * m_acceleration; + m_velocity += dtime * m_acceleration; + pos_translator.update(m_position, pos_translator.aim_is_end, + pos_translator.anim_time); + pos_translator.translate(dtime); + updateNodePos(); + } + + float moved = lastpos.getDistanceFrom(pos_translator.vect_show); + m_step_distance_counter += moved; + if(m_step_distance_counter > 1.5*BS) + { + m_step_distance_counter = 0; + if(!m_is_local_player && m_prop.makes_footstep_sound) + { + INodeDefManager *ndef = m_gamedef->ndef(); + v3s16 p = floatToInt(getPosition() + v3f(0, + (m_prop.collisionbox.MinEdge.Y-0.5)*BS, 0), BS); + MapNode n = m_env->getMap().getNodeNoEx(p); + SimpleSoundSpec spec = ndef->get(n).sound_footstep; + m_gamedef->sound()->playSoundAt(spec, false, getPosition()); + } + } + } + + m_anim_timer += dtime; + if(m_anim_timer >= m_anim_framelength) + { + m_anim_timer -= m_anim_framelength; + m_anim_frame++; + if(m_anim_frame >= m_anim_num_frames) + m_anim_frame = 0; + } + + updateTexturePos(); + + if(m_reset_textures_timer >= 0) + { + m_reset_textures_timer -= dtime; + if(m_reset_textures_timer <= 0){ + m_reset_textures_timer = -1; + updateTextures(""); + } + } + if(getParent() == NULL && fabs(m_prop.automatic_rotate) > 0.001) + { + m_yaw += dtime * m_prop.automatic_rotate * 180 / M_PI; + updateNodePos(); + } + + if (getParent() == NULL && m_prop.automatic_face_movement_dir && + (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) + { + m_yaw = atan2(m_velocity.Z,m_velocity.X) * 180 / M_PI + + m_prop.automatic_face_movement_dir_offset; + updateNodePos(); + } +} + +void GenericCAO::updateTexturePos() +{ + if(m_spritenode) + { + scene::ICameraSceneNode* camera = + m_spritenode->getSceneManager()->getActiveCamera(); + if(!camera) + return; + v3f cam_to_entity = m_spritenode->getAbsolutePosition() + - camera->getAbsolutePosition(); + cam_to_entity.normalize(); + + int row = m_tx_basepos.Y; + int col = m_tx_basepos.X; + + if(m_tx_select_horiz_by_yawpitch) + { + if(cam_to_entity.Y > 0.75) + col += 5; + else if(cam_to_entity.Y < -0.75) + col += 4; + else{ + float mob_dir = + atan2(cam_to_entity.Z, cam_to_entity.X) / M_PI * 180.; + float dir = mob_dir - m_yaw; + dir = wrapDegrees_180(dir); + //infostream<<"id="<getBool("bilinear_filter"); + bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter"); + + if(m_spritenode) + { + if(m_prop.visual == "sprite") + { + std::string texturestring = "unknown_node.png"; + if(m_prop.textures.size() >= 1) + texturestring = m_prop.textures[0]; + texturestring += mod; + m_spritenode->setMaterialTexture(0, + tsrc->getTexture(texturestring)); + + // This allows setting per-material colors. However, until a real lighting + // system is added, the code below will have no effect. Once MineTest + // has directional lighting, it should work automatically. + if(m_prop.colors.size() >= 1) + { + m_spritenode->getMaterial(0).AmbientColor = m_prop.colors[0]; + m_spritenode->getMaterial(0).DiffuseColor = m_prop.colors[0]; + m_spritenode->getMaterial(0).SpecularColor = m_prop.colors[0]; + } + + m_spritenode->getMaterial(0).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + m_spritenode->getMaterial(0).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + m_spritenode->getMaterial(0).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + } + } + if(m_animated_meshnode) + { + if(m_prop.visual == "mesh") + { + for (u32 i = 0; i < m_prop.textures.size() && + i < m_animated_meshnode->getMaterialCount(); ++i) + { + std::string texturestring = m_prop.textures[i]; + if(texturestring == "") + continue; // Empty texture string means don't modify that material + texturestring += mod; + video::ITexture* texture = tsrc->getTexture(texturestring); + if(!texture) + { + errorstream<<"GenericCAO::updateTextures(): Could not load texture "<getMaterial(i); + material.TextureLayer[0].Texture = texture; + material.setFlag(video::EMF_LIGHTING, false); + material.setFlag(video::EMF_BILINEAR_FILTER, false); + + m_animated_meshnode->getMaterial(i) + .setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + m_animated_meshnode->getMaterial(i) + .setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + m_animated_meshnode->getMaterial(i) + .setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + } + for (u32 i = 0; i < m_prop.colors.size() && + i < m_animated_meshnode->getMaterialCount(); ++i) + { + // This allows setting per-material colors. However, until a real lighting + // system is added, the code below will have no effect. Once MineTest + // has directional lighting, it should work automatically. + m_animated_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i]; + m_animated_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i]; + m_animated_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i]; + } + } + } + if(m_meshnode) + { + if(m_prop.visual == "cube") + { + for (u32 i = 0; i < 6; ++i) + { + std::string texturestring = "unknown_node.png"; + if(m_prop.textures.size() > i) + texturestring = m_prop.textures[i]; + texturestring += mod; + + + // Set material flags and texture + video::SMaterial& material = m_meshnode->getMaterial(i); + material.setFlag(video::EMF_LIGHTING, false); + material.setFlag(video::EMF_BILINEAR_FILTER, false); + material.setTexture(0, + tsrc->getTexture(texturestring)); + material.getTextureMatrix(0).makeIdentity(); + + // This allows setting per-material colors. However, until a real lighting + // system is added, the code below will have no effect. Once MineTest + // has directional lighting, it should work automatically. + if(m_prop.colors.size() > i) + { + m_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i]; + m_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i]; + m_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i]; + } + + m_meshnode->getMaterial(i).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + m_meshnode->getMaterial(i).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + m_meshnode->getMaterial(i).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + } + } + else if(m_prop.visual == "upright_sprite") + { + scene::IMesh *mesh = m_meshnode->getMesh(); + { + std::string tname = "unknown_object.png"; + if(m_prop.textures.size() >= 1) + tname = m_prop.textures[0]; + tname += mod; + scene::IMeshBuffer *buf = mesh->getMeshBuffer(0); + buf->getMaterial().setTexture(0, + tsrc->getTexture(tname)); + + // This allows setting per-material colors. However, until a real lighting + // system is added, the code below will have no effect. Once MineTest + // has directional lighting, it should work automatically. + if(m_prop.colors.size() >= 1) + { + buf->getMaterial().AmbientColor = m_prop.colors[0]; + buf->getMaterial().DiffuseColor = m_prop.colors[0]; + buf->getMaterial().SpecularColor = m_prop.colors[0]; + } + + buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + } + { + std::string tname = "unknown_object.png"; + if(m_prop.textures.size() >= 2) + tname = m_prop.textures[1]; + else if(m_prop.textures.size() >= 1) + tname = m_prop.textures[0]; + tname += mod; + scene::IMeshBuffer *buf = mesh->getMeshBuffer(1); + buf->getMaterial().setTexture(0, + tsrc->getTexture(tname)); + + // This allows setting per-material colors. However, until a real lighting + // system is added, the code below will have no effect. Once MineTest + // has directional lighting, it should work automatically. + if(m_prop.colors.size() >= 2) + { + buf->getMaterial().AmbientColor = m_prop.colors[1]; + buf->getMaterial().DiffuseColor = m_prop.colors[1]; + buf->getMaterial().SpecularColor = m_prop.colors[1]; + } + else if(m_prop.colors.size() >= 1) + { + buf->getMaterial().AmbientColor = m_prop.colors[0]; + buf->getMaterial().DiffuseColor = m_prop.colors[0]; + buf->getMaterial().SpecularColor = m_prop.colors[0]; + } + + buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter); + buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter); + buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter); + } + } + } +} + +void GenericCAO::updateAnimation() +{ + if(m_animated_meshnode == NULL) + return; + m_animated_meshnode->setFrameLoop(m_animation_range.X, m_animation_range.Y); + m_animated_meshnode->setAnimationSpeed(m_animation_speed); + m_animated_meshnode->setTransitionTime(m_animation_blend); +} + +void GenericCAO::updateBonePosition() +{ + if(m_bone_position.empty() || m_animated_meshnode == NULL) + return; + + m_animated_meshnode->setJointMode(irr::scene::EJUOR_CONTROL); // To write positions to the mesh on render + for(std::map >::const_iterator ii = m_bone_position.begin(); + ii != m_bone_position.end(); ++ii) + { + std::string bone_name = (*ii).first; + v3f bone_pos = (*ii).second.X; + v3f bone_rot = (*ii).second.Y; + irr::scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str()); + if(bone) + { + bone->setPosition(bone_pos); + bone->setRotation(bone_rot); + } + } +} + +void GenericCAO::updateAttachments() +{ + + // localplayer itself can't be attached to localplayer + if (!m_is_local_player) + { + m_attached_to_local = getParent() != NULL && getParent()->isLocalPlayer(); + // Objects attached to the local player should always be hidden + m_is_visible = !m_attached_to_local; + } + + if(getParent() == NULL || m_attached_to_local) // Detach or don't attach + { + scene::ISceneNode *node = getSceneNode(); + if (node) { + v3f old_position = node->getAbsolutePosition(); + v3f old_rotation = node->getRotation(); + node->setParent(m_smgr->getRootSceneNode()); + node->setPosition(old_position); + node->setRotation(old_rotation); + node->updateAbsolutePosition(); + } + if (m_is_local_player) { + LocalPlayer *player = m_env->getLocalPlayer(); + player->isAttached = false; + } + } + else // Attach + { + scene::ISceneNode *my_node = getSceneNode(); + + scene::ISceneNode *parent_node = getParent()->getSceneNode(); + scene::IAnimatedMeshSceneNode *parent_animated_mesh_node = + getParent()->getAnimatedMeshSceneNode(); + if (parent_animated_mesh_node && m_attachment_bone != "") { + parent_node = parent_animated_mesh_node->getJointNode(m_attachment_bone.c_str()); + } + + if (my_node && parent_node) { + my_node->setParent(parent_node); + my_node->setPosition(m_attachment_position); + my_node->setRotation(m_attachment_rotation); + my_node->updateAbsolutePosition(); + } + if (m_is_local_player) { + LocalPlayer *player = m_env->getLocalPlayer(); + player->isAttached = true; + } + } +} + +void GenericCAO::processMessage(const std::string &data) +{ + //infostream<<"GenericCAO: Got message"<getLocalPlayer(); + player->physics_override_speed = override_speed; + player->physics_override_jump = override_jump; + player->physics_override_gravity = override_gravity; + player->physics_override_sneak = sneak; + player->physics_override_sneak_glitch = sneak_glitch; + } + } + else if(cmd == GENERIC_CMD_SET_ANIMATION) { + // TODO: change frames send as v2s32 value + v2f range = readV2F1000(is); + if (!m_is_local_player) { + m_animation_range = v2s32((s32)range.X, (s32)range.Y); + m_animation_speed = readF1000(is); + m_animation_blend = readF1000(is); + updateAnimation(); + } else { + LocalPlayer *player = m_env->getLocalPlayer(); + if(player->last_animation == NO_ANIM) + { + m_animation_range = v2s32((s32)range.X, (s32)range.Y); + m_animation_speed = readF1000(is); + m_animation_blend = readF1000(is); + } + // update animation only if local animations present + // and received animation is unknown (except idle animation) + bool is_known = false; + for (int i = 1;i<4;i++) + { + if(m_animation_range.Y == player->local_animations[i].Y) + is_known = true; + } + if(!is_known || + (player->local_animations[1].Y + player->local_animations[2].Y < 1)) + { + updateAnimation(); + } + } + } + else if(cmd == GENERIC_CMD_SET_BONE_POSITION) { + std::string bone = deSerializeString(is); + v3f position = readV3F1000(is); + v3f rotation = readV3F1000(is); + m_bone_position[bone] = core::vector2d(position, rotation); + + updateBonePosition(); + } + else if(cmd == GENERIC_CMD_SET_ATTACHMENT) { + m_env->m_attachements[getId()] = readS16(is); + m_children.push_back(m_env->m_attachements[getId()]); + m_attachment_bone = deSerializeString(is); + m_attachment_position = readV3F1000(is); + m_attachment_rotation = readV3F1000(is); + + updateAttachments(); + } + else if(cmd == GENERIC_CMD_PUNCHED) { + /*s16 damage =*/ readS16(is); + s16 result_hp = readS16(is); + + // Use this instead of the send damage to not interfere with prediction + s16 damage = m_hp - result_hp; + + m_hp = result_hp; + + if (damage > 0) + { + if (m_hp <= 0) + { + // TODO: Execute defined fast response + // As there is no definition, make a smoke puff + ClientSimpleObject *simple = createSmokePuff( + m_smgr, m_env, m_position, + m_prop.visual_size * BS); + m_env->addSimpleObject(simple); + } else { + // TODO: Execute defined fast response + // Flashing shall suffice as there is no definition + m_reset_textures_timer = 0.05; + if(damage >= 2) + m_reset_textures_timer += 0.05 * damage; + updateTextures("^[brighten"); + } + } + } + else if(cmd == GENERIC_CMD_UPDATE_ARMOR_GROUPS) { + m_armor_groups.clear(); + int armor_groups_size = readU16(is); + for(int i=0; igetToolCapabilities(m_gamedef->idef()); + PunchDamageResult result = getPunchDamage( + m_armor_groups, + toolcap, + punchitem, + time_from_last_punch); + + if(result.did_punch && result.damage != 0) + { + if(result.damage < m_hp) + { + m_hp -= result.damage; + } else { + m_hp = 0; + // TODO: Execute defined fast response + // As there is no definition, make a smoke puff + ClientSimpleObject *simple = createSmokePuff( + m_smgr, m_env, m_position, + m_prop.visual_size * BS); + m_env->addSimpleObject(simple); + } + // TODO: Execute defined fast response + // Flashing shall suffice as there is no definition + m_reset_textures_timer = 0.05; + if(result.damage >= 2) + m_reset_textures_timer += 0.05 * result.damage; + updateTextures("^[brighten"); + } + + return false; +} + +std::string GenericCAO::debugInfoText() +{ + std::ostringstream os(std::ios::binary); + os<<"GenericCAO hp="<first<<"="<second<<", "; + } + os<<"}"; + return os.str(); +} + +// Prototype +GenericCAO proto_GenericCAO(NULL, NULL); diff --git a/src/content_cao.h b/src/content_cao.h new file mode 100644 index 0000000..31ccd04 --- /dev/null +++ b/src/content_cao.h @@ -0,0 +1,203 @@ +/* +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. +*/ + +#ifndef CONTENT_CAO_HEADER +#define CONTENT_CAO_HEADER + +#include +#include "irrlichttypes_extrabloated.h" +#include "clientobject.h" +#include "object_properties.h" +#include "itemgroup.h" + +/* + SmoothTranslator +*/ + +struct SmoothTranslator +{ + v3f vect_old; + v3f vect_show; + v3f vect_aim; + f32 anim_counter; + f32 anim_time; + f32 anim_time_counter; + bool aim_is_end; + + SmoothTranslator(); + + void init(v3f vect); + + void sharpen(); + + void update(v3f vect_new, bool is_end_position=false, float update_interval=-1); + + void translate(f32 dtime); + + bool is_moving(); +}; + +class GenericCAO : public ClientActiveObject +{ +private: + // Only set at initialization + std::string m_name; + bool m_is_player; + bool m_is_local_player; + int m_id; + // Property-ish things + ObjectProperties m_prop; + // + scene::ISceneManager *m_smgr; + IrrlichtDevice *m_irr; + core::aabbox3d m_selection_box; + scene::IMeshSceneNode *m_meshnode; + scene::IAnimatedMeshSceneNode *m_animated_meshnode; + WieldMeshSceneNode *m_wield_meshnode; + scene::IBillboardSceneNode *m_spritenode; + scene::ITextSceneNode* m_textnode; + v3f m_position; + v3f m_velocity; + v3f m_acceleration; + float m_yaw; + s16 m_hp; + SmoothTranslator pos_translator; + // Spritesheet/animation stuff + v2f m_tx_size; + v2s16 m_tx_basepos; + bool m_initial_tx_basepos_set; + bool m_tx_select_horiz_by_yawpitch; + v2s32 m_animation_range; + int m_animation_speed; + int m_animation_blend; + std::map > m_bone_position; // stores position and rotation for each bone name + std::string m_attachment_bone; + v3f m_attachment_position; + v3f m_attachment_rotation; + bool m_attached_to_local; + int m_anim_frame; + int m_anim_num_frames; + float m_anim_framelength; + float m_anim_timer; + ItemGroupList m_armor_groups; + float m_reset_textures_timer; + bool m_visuals_expired; + float m_step_distance_counter; + u8 m_last_light; + bool m_is_visible; + + std::vector m_children; + +public: + GenericCAO(IGameDef *gamedef, ClientEnvironment *env); + + ~GenericCAO(); + + static ClientActiveObject* create(IGameDef *gamedef, ClientEnvironment *env) + { + return new GenericCAO(gamedef, env); + } + + inline ActiveObjectType getType() const + { + return ACTIVEOBJECT_TYPE_GENERIC; + } + + void initialize(const std::string &data); + + ClientActiveObject *getParent(); + + bool getCollisionBox(aabb3f *toset); + + bool collideWithObjects(); + + core::aabbox3d* getSelectionBox(); + + v3f getPosition(); + + scene::ISceneNode *getSceneNode(); + + scene::IMeshSceneNode *getMeshSceneNode(); + + scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode(); + + WieldMeshSceneNode *getWieldMeshSceneNode(); + + scene::IBillboardSceneNode *getSpriteSceneNode(); + + inline bool isPlayer() const + { + return m_is_player; + } + + inline bool isLocalPlayer() const + { + return m_is_local_player; + } + + inline bool isVisible() const + { + return m_is_visible; + } + + inline void setVisible(bool toset) + { + m_is_visible = toset; + } + + void setAttachments(); + + void removeFromScene(bool permanent); + + void addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, + IrrlichtDevice *irr); + + inline void expireVisuals() + { + m_visuals_expired = true; + } + + void updateLight(u8 light_at_pos); + + v3s16 getLightPosition(); + + void updateNodePos(); + + void step(float dtime, ClientEnvironment *env); + + void updateTexturePos(); + + void updateTextures(const std::string &mod); + + void updateAnimation(); + + void updateBonePosition(); + + void updateAttachments(); + + void processMessage(const std::string &data); + + bool directReportPunch(v3f dir, const ItemStack *punchitem=NULL, + float time_from_last_punch=1000000); + + std::string debugInfoText(); +}; + + +#endif diff --git a/src/content_cso.cpp b/src/content_cso.cpp new file mode 100644 index 0000000..c337472 --- /dev/null +++ b/src/content_cso.cpp @@ -0,0 +1,91 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#include "content_cso.h" +#include +#include "client/tile.h" +#include "environment.h" +#include "gamedef.h" +#include "log.h" +#include "map.h" + +/* +static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill, + float txs, float tys, int col, int row) +{ + video::SMaterial& material = bill->getMaterial(0); + core::matrix4& matrix = material.getTextureMatrix(0); + matrix.setTextureTranslate(txs*col, tys*row); + matrix.setTextureScale(txs, tys); +} +*/ + +class SmokePuffCSO: public ClientSimpleObject +{ + float m_age; + scene::IBillboardSceneNode *m_spritenode; +public: + SmokePuffCSO(scene::ISceneManager *smgr, + ClientEnvironment *env, v3f pos, v2f size): + m_age(0), + m_spritenode(NULL) + { + infostream<<"SmokePuffCSO: constructing"<addBillboardSceneNode( + NULL, v2f(1,1), pos, -1); + m_spritenode->setMaterialTexture(0, + env->getGameDef()->tsrc()->getTexture("smoke_puff.png")); + m_spritenode->setMaterialFlag(video::EMF_LIGHTING, false); + m_spritenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); + //m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF); + m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL); + m_spritenode->setMaterialFlag(video::EMF_FOG_ENABLE, true); + m_spritenode->setColor(video::SColor(255,0,0,0)); + m_spritenode->setVisible(true); + m_spritenode->setSize(size); + /* Update brightness */ + u8 light; + bool pos_ok; + MapNode n = env->getMap().getNodeNoEx(floatToInt(pos, BS), &pos_ok); + light = pos_ok ? decode_light(n.getLightBlend(env->getDayNightRatio(), + env->getGameDef()->ndef())) + : 64; + video::SColor color(255,light,light,light); + m_spritenode->setColor(color); + } + virtual ~SmokePuffCSO() + { + infostream<<"SmokePuffCSO: destructing"<remove(); + } + void step(float dtime) + { + m_age += dtime; + if(m_age > 1.0){ + m_to_be_removed = true; + } + } +}; + +ClientSimpleObject* createSmokePuff(scene::ISceneManager *smgr, + ClientEnvironment *env, v3f pos, v2f size) +{ + return new SmokePuffCSO(smgr, env, pos, size); +} + diff --git a/src/content_cso.h b/src/content_cso.h new file mode 100644 index 0000000..5007d25 --- /dev/null +++ b/src/content_cso.h @@ -0,0 +1,30 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef CONTENT_CSO_HEADER +#define CONTENT_CSO_HEADER + +#include "irrlichttypes_extrabloated.h" +#include "clientsimpleobject.h" + +ClientSimpleObject* createSmokePuff(scene::ISceneManager *smgr, + ClientEnvironment *env, v3f pos, v2f size); + +#endif + diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp new file mode 100644 index 0000000..2f13687 --- /dev/null +++ b/src/content_mapblock.cpp @@ -0,0 +1,1760 @@ +/* +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. +*/ + +#include "content_mapblock.h" +#include "util/numeric.h" +#include "util/directiontables.h" +#include "main.h" // For g_settings +#include "mapblock_mesh.h" // For MapBlock_LightColor() and MeshCollector +#include "settings.h" +#include "nodedef.h" +#include "client/tile.h" +#include "mesh.h" +#include +#include "gamedef.h" +#include "log.h" + + +// Create a cuboid. +// collector - the MeshCollector for the resulting polygons +// box - the position and size of the box +// tiles - the tiles (materials) to use (for all 6 faces) +// tilecount - number of entries in tiles, 1<=tilecount<=6 +// c - vertex colour - used for all +// txc - texture coordinates - this is a list of texture coordinates +// for the opposite corners of each face - therefore, there +// should be (2+2)*6=24 values in the list. Alternatively, pass +// NULL to use the entire texture for each face. The order of +// the faces in the list is up-down-right-left-back-front +// (compatible with ContentFeatures). If you specified 0,0,1,1 +// for each face, that would be the same as passing NULL. +void makeCuboid(MeshCollector *collector, const aabb3f &box, + TileSpec *tiles, int tilecount, video::SColor &c, const f32* txc) +{ + assert(tilecount >= 1 && tilecount <= 6); // pre-condition + + v3f min = box.MinEdge; + v3f max = box.MaxEdge; + + + + if(txc == NULL) { + static const f32 txc_default[24] = { + 0,0,1,1, + 0,0,1,1, + 0,0,1,1, + 0,0,1,1, + 0,0,1,1, + 0,0,1,1 + }; + txc = txc_default; + } + + video::S3DVertex vertices[24] = + { + // up + video::S3DVertex(min.X,max.Y,max.Z, 0,1,0, c, txc[0],txc[1]), + video::S3DVertex(max.X,max.Y,max.Z, 0,1,0, c, txc[2],txc[1]), + video::S3DVertex(max.X,max.Y,min.Z, 0,1,0, c, txc[2],txc[3]), + video::S3DVertex(min.X,max.Y,min.Z, 0,1,0, c, txc[0],txc[3]), + // down + video::S3DVertex(min.X,min.Y,min.Z, 0,-1,0, c, txc[4],txc[5]), + video::S3DVertex(max.X,min.Y,min.Z, 0,-1,0, c, txc[6],txc[5]), + video::S3DVertex(max.X,min.Y,max.Z, 0,-1,0, c, txc[6],txc[7]), + video::S3DVertex(min.X,min.Y,max.Z, 0,-1,0, c, txc[4],txc[7]), + // right + video::S3DVertex(max.X,max.Y,min.Z, 1,0,0, c, txc[ 8],txc[9]), + video::S3DVertex(max.X,max.Y,max.Z, 1,0,0, c, txc[10],txc[9]), + video::S3DVertex(max.X,min.Y,max.Z, 1,0,0, c, txc[10],txc[11]), + video::S3DVertex(max.X,min.Y,min.Z, 1,0,0, c, txc[ 8],txc[11]), + // left + video::S3DVertex(min.X,max.Y,max.Z, -1,0,0, c, txc[12],txc[13]), + video::S3DVertex(min.X,max.Y,min.Z, -1,0,0, c, txc[14],txc[13]), + video::S3DVertex(min.X,min.Y,min.Z, -1,0,0, c, txc[14],txc[15]), + video::S3DVertex(min.X,min.Y,max.Z, -1,0,0, c, txc[12],txc[15]), + // back + video::S3DVertex(max.X,max.Y,max.Z, 0,0,1, c, txc[16],txc[17]), + video::S3DVertex(min.X,max.Y,max.Z, 0,0,1, c, txc[18],txc[17]), + video::S3DVertex(min.X,min.Y,max.Z, 0,0,1, c, txc[18],txc[19]), + video::S3DVertex(max.X,min.Y,max.Z, 0,0,1, c, txc[16],txc[19]), + // front + video::S3DVertex(min.X,max.Y,min.Z, 0,0,-1, c, txc[20],txc[21]), + video::S3DVertex(max.X,max.Y,min.Z, 0,0,-1, c, txc[22],txc[21]), + video::S3DVertex(max.X,min.Y,min.Z, 0,0,-1, c, txc[22],txc[23]), + video::S3DVertex(min.X,min.Y,min.Z, 0,0,-1, c, txc[20],txc[23]), + }; + + for(int i = 0; i < 6; i++) + { + switch (tiles[MYMIN(i, tilecount-1)].rotation) + { + case 0: + break; + case 1: //R90 + for (int x = 0; x < 4; x++) + vertices[i*4+x].TCoords.rotateBy(90,irr::core::vector2df(0, 0)); + break; + case 2: //R180 + for (int x = 0; x < 4; x++) + vertices[i*4+x].TCoords.rotateBy(180,irr::core::vector2df(0, 0)); + break; + case 3: //R270 + for (int x = 0; x < 4; x++) + vertices[i*4+x].TCoords.rotateBy(270,irr::core::vector2df(0, 0)); + break; + case 4: //FXR90 + for (int x = 0; x < 4; x++){ + vertices[i*4+x].TCoords.X = 1.0 - vertices[i*4+x].TCoords.X; + vertices[i*4+x].TCoords.rotateBy(90,irr::core::vector2df(0, 0)); + } + break; + case 5: //FXR270 + for (int x = 0; x < 4; x++){ + vertices[i*4+x].TCoords.X = 1.0 - vertices[i*4+x].TCoords.X; + vertices[i*4+x].TCoords.rotateBy(270,irr::core::vector2df(0, 0)); + } + break; + case 6: //FYR90 + for (int x = 0; x < 4; x++){ + vertices[i*4+x].TCoords.Y = 1.0 - vertices[i*4+x].TCoords.Y; + vertices[i*4+x].TCoords.rotateBy(90,irr::core::vector2df(0, 0)); + } + break; + case 7: //FYR270 + for (int x = 0; x < 4; x++){ + vertices[i*4+x].TCoords.Y = 1.0 - vertices[i*4+x].TCoords.Y; + vertices[i*4+x].TCoords.rotateBy(270,irr::core::vector2df(0, 0)); + } + break; + case 8: //FX + for (int x = 0; x < 4; x++){ + vertices[i*4+x].TCoords.X = 1.0 - vertices[i*4+x].TCoords.X; + } + break; + case 9: //FY + for (int x = 0; x < 4; x++){ + vertices[i*4+x].TCoords.Y = 1.0 - vertices[i*4+x].TCoords.Y; + } + break; + default: + break; + } + } + u16 indices[] = {0,1,2,2,3,0}; + // Add to mesh collector + for (s32 j = 0; j < 24; j += 4) { + int tileindex = MYMIN(j / 4, tilecount - 1); + collector->append(tiles[tileindex], vertices + j, 4, indices, 6); + } +} + +/* + TODO: Fix alpha blending for special nodes + Currently only the last element rendered is blended correct +*/ +void mapblock_mesh_generate_special(MeshMakeData *data, + MeshCollector &collector) +{ + INodeDefManager *nodedef = data->m_gamedef->ndef(); + ITextureSource *tsrc = data->m_gamedef->tsrc(); + scene::ISceneManager* smgr = data->m_gamedef->getSceneManager(); + scene::IMeshManipulator* meshmanip = smgr->getMeshManipulator(); + + // 0ms + //TimeTaker timer("mapblock_mesh_generate_special()"); + + /* + Some settings + */ + bool enable_mesh_cache = g_settings->getBool("enable_mesh_cache"); + bool new_style_water = g_settings->getBool("new_style_water"); + + float node_liquid_level = 1.0; + if (new_style_water) + node_liquid_level = 0.85; + + v3s16 blockpos_nodes = data->m_blockpos*MAP_BLOCKSIZE; + + for(s16 z = 0; z < MAP_BLOCKSIZE; z++) + for(s16 y = 0; y < MAP_BLOCKSIZE; y++) + for(s16 x = 0; x < MAP_BLOCKSIZE; x++) + { + v3s16 p(x,y,z); + + MapNode n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p); + const ContentFeatures &f = nodedef->get(n); + + // Only solidness=0 stuff is drawn here + if(f.solidness != 0) + continue; + + switch(f.drawtype){ + default: + infostream << "Got " << f.drawtype << std::endl; + FATAL_ERROR("Unknown drawtype"); + break; + case NDT_AIRLIKE: + break; + case NDT_LIQUID: + { + /* + Add water sources to mesh if using new style + */ + TileSpec tile_liquid = f.special_tiles[0]; + TileSpec tile_liquid_bfculled = getNodeTile(n, p, v3s16(0,0,0), data); + + bool top_is_same_liquid = false; + MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y+1,z)); + content_t c_flowing = nodedef->getId(f.liquid_alternative_flowing); + content_t c_source = nodedef->getId(f.liquid_alternative_source); + if(ntop.getContent() == c_flowing || ntop.getContent() == c_source) + top_is_same_liquid = true; + + u16 l = getInteriorLight(n, 0, nodedef); + video::SColor c = MapBlock_LightColor(f.alpha, l, f.light_source); + + /* + Generate sides + */ + v3s16 side_dirs[4] = { + v3s16(1,0,0), + v3s16(-1,0,0), + v3s16(0,0,1), + v3s16(0,0,-1), + }; + for(u32 i=0; i<4; i++) + { + v3s16 dir = side_dirs[i]; + + MapNode neighbor = data->m_vmanip.getNodeNoEx(blockpos_nodes + p + dir); + content_t neighbor_content = neighbor.getContent(); + const ContentFeatures &n_feat = nodedef->get(neighbor_content); + MapNode n_top = data->m_vmanip.getNodeNoEx(blockpos_nodes + p + dir+ v3s16(0,1,0)); + content_t n_top_c = n_top.getContent(); + + if(neighbor_content == CONTENT_IGNORE) + continue; + + /* + If our topside is liquid and neighbor's topside + is liquid, don't draw side face + */ + if(top_is_same_liquid && (n_top_c == c_flowing || + n_top_c == c_source || n_top_c == CONTENT_IGNORE)) + continue; + + // Don't draw face if neighbor is blocking the view + if(n_feat.solidness == 2) + continue; + + bool neighbor_is_same_liquid = (neighbor_content == c_source + || neighbor_content == c_flowing); + + // Don't draw any faces if neighbor same is liquid and top is + // same liquid + if(neighbor_is_same_liquid && !top_is_same_liquid) + continue; + + // Use backface culled material if neighbor doesn't have a + // solidness of 0 + const TileSpec *current_tile = &tile_liquid; + if(n_feat.solidness != 0 || n_feat.visual_solidness != 0) + current_tile = &tile_liquid_bfculled; + + video::S3DVertex vertices[4] = + { + video::S3DVertex(-BS/2,0,BS/2,0,0,0, c, 0,1), + video::S3DVertex(BS/2,0,BS/2,0,0,0, c, 1,1), + video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,0), + video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,0), + }; + + /* + If our topside is liquid, set upper border of face + at upper border of node + */ + if(top_is_same_liquid) + { + vertices[2].Pos.Y = 0.5*BS; + vertices[3].Pos.Y = 0.5*BS; + } + /* + Otherwise upper position of face is liquid level + */ + else + { + vertices[2].Pos.Y = (node_liquid_level-0.5)*BS; + vertices[3].Pos.Y = (node_liquid_level-0.5)*BS; + } + /* + If neighbor is liquid, lower border of face is liquid level + */ + if(neighbor_is_same_liquid) + { + vertices[0].Pos.Y = (node_liquid_level-0.5)*BS; + vertices[1].Pos.Y = (node_liquid_level-0.5)*BS; + } + /* + If neighbor is not liquid, lower border of face is + lower border of node + */ + else + { + vertices[0].Pos.Y = -0.5*BS; + vertices[1].Pos.Y = -0.5*BS; + } + + for(s32 j=0; j<4; j++) + { + if(dir == v3s16(0,0,1)) + vertices[j].Pos.rotateXZBy(0); + if(dir == v3s16(0,0,-1)) + vertices[j].Pos.rotateXZBy(180); + if(dir == v3s16(-1,0,0)) + vertices[j].Pos.rotateXZBy(90); + if(dir == v3s16(1,0,-0)) + vertices[j].Pos.rotateXZBy(-90); + + // Do this to not cause glitches when two liquids are + // side-by-side + /*if(neighbor_is_same_liquid == false){ + vertices[j].Pos.X *= 0.98; + vertices[j].Pos.Z *= 0.98; + }*/ + + vertices[j].Pos += intToFloat(p, BS); + } + + u16 indices[] = {0,1,2,2,3,0}; + // Add to mesh collector + collector.append(*current_tile, vertices, 4, indices, 6); + } + + /* + Generate top + */ + if(top_is_same_liquid) + continue; + + video::S3DVertex vertices[4] = + { + video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,1), + video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,1), + video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c, 1,0), + video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c, 0,0), + }; + + v3f offset(p.X*BS, p.Y*BS + (-0.5+node_liquid_level)*BS, p.Z*BS); + for(s32 i=0; i<4; i++) + { + vertices[i].Pos += offset; + } + + u16 indices[] = {0,1,2,2,3,0}; + // Add to mesh collector + collector.append(tile_liquid, vertices, 4, indices, 6); + break;} + case NDT_FLOWINGLIQUID: + { + /* + Add flowing liquid to mesh + */ + TileSpec tile_liquid = f.special_tiles[0]; + TileSpec tile_liquid_bfculled = f.special_tiles[1]; + + bool top_is_same_liquid = false; + MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y+1,z)); + content_t c_flowing = nodedef->getId(f.liquid_alternative_flowing); + content_t c_source = nodedef->getId(f.liquid_alternative_source); + if(ntop.getContent() == c_flowing || ntop.getContent() == c_source) + top_is_same_liquid = true; + + u16 l = 0; + // If this liquid emits light and doesn't contain light, draw + // it at what it emits, for an increased effect + u8 light_source = nodedef->get(n).light_source; + if(light_source != 0){ + l = decode_light(light_source); + l = l | (l<<8); + } + // Use the light of the node on top if possible + else if(nodedef->get(ntop).param_type == CPT_LIGHT) + l = getInteriorLight(ntop, 0, nodedef); + // Otherwise use the light of this node (the liquid) + else + l = getInteriorLight(n, 0, nodedef); + video::SColor c = MapBlock_LightColor(f.alpha, l, f.light_source); + + u8 range = rangelim(nodedef->get(c_flowing).liquid_range, 1, 8); + + // Neighbor liquid levels (key = relative position) + // Includes current node + std::map neighbor_levels; + std::map neighbor_contents; + std::map neighbor_flags; + const u8 neighborflag_top_is_same_liquid = 0x01; + v3s16 neighbor_dirs[9] = { + v3s16(0,0,0), + v3s16(0,0,1), + v3s16(0,0,-1), + v3s16(1,0,0), + v3s16(-1,0,0), + v3s16(1,0,1), + v3s16(-1,0,-1), + v3s16(1,0,-1), + v3s16(-1,0,1), + }; + for(u32 i=0; i<9; i++) + { + content_t content = CONTENT_AIR; + float level = -0.5 * BS; + u8 flags = 0; + // Check neighbor + v3s16 p2 = p + neighbor_dirs[i]; + MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); + if(n2.getContent() != CONTENT_IGNORE) + { + content = n2.getContent(); + + if(n2.getContent() == c_source) + level = (-0.5+node_liquid_level) * BS; + else if(n2.getContent() == c_flowing){ + u8 liquid_level = (n2.param2&LIQUID_LEVEL_MASK); + if (liquid_level <= LIQUID_LEVEL_MAX+1-range) + liquid_level = 0; + else + liquid_level -= (LIQUID_LEVEL_MAX+1-range); + level = (-0.5 + ((float)liquid_level+ 0.5) / (float)range * node_liquid_level) * BS; + } + + // Check node above neighbor. + // NOTE: This doesn't get executed if neighbor + // doesn't exist + p2.Y += 1; + n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); + if(n2.getContent() == c_source || + n2.getContent() == c_flowing) + flags |= neighborflag_top_is_same_liquid; + } + + neighbor_levels[neighbor_dirs[i]] = level; + neighbor_contents[neighbor_dirs[i]] = content; + neighbor_flags[neighbor_dirs[i]] = flags; + } + + // Corner heights (average between four liquids) + f32 corner_levels[4]; + + v3s16 halfdirs[4] = { + v3s16(0,0,0), + v3s16(1,0,0), + v3s16(1,0,1), + v3s16(0,0,1), + }; + for(u32 i=0; i<4; i++) + { + v3s16 cornerdir = halfdirs[i]; + float cornerlevel = 0; + u32 valid_count = 0; + u32 air_count = 0; + for(u32 j=0; j<4; j++) + { + v3s16 neighbordir = cornerdir - halfdirs[j]; + content_t content = neighbor_contents[neighbordir]; + // If top is liquid, draw starting from top of node + if(neighbor_flags[neighbordir] & + neighborflag_top_is_same_liquid) + { + cornerlevel = 0.5*BS; + valid_count = 1; + break; + } + // Source is always the same height + else if(content == c_source) + { + cornerlevel = (-0.5+node_liquid_level)*BS; + valid_count = 1; + break; + } + // Flowing liquid has level information + else if(content == c_flowing) + { + cornerlevel += neighbor_levels[neighbordir]; + valid_count++; + } + else if(content == CONTENT_AIR) + { + air_count++; + } + } + if(air_count >= 2) + cornerlevel = -0.5*BS+0.2; + else if(valid_count > 0) + cornerlevel /= valid_count; + corner_levels[i] = cornerlevel; + } + + /* + Generate sides + */ + + v3s16 side_dirs[4] = { + v3s16(1,0,0), + v3s16(-1,0,0), + v3s16(0,0,1), + v3s16(0,0,-1), + }; + s16 side_corners[4][2] = { + {1, 2}, + {3, 0}, + {2, 3}, + {0, 1}, + }; + for(u32 i=0; i<4; i++) + { + v3s16 dir = side_dirs[i]; + + /* + If our topside is liquid and neighbor's topside + is liquid, don't draw side face + */ + if(top_is_same_liquid && + neighbor_flags[dir] & neighborflag_top_is_same_liquid) + continue; + + content_t neighbor_content = neighbor_contents[dir]; + const ContentFeatures &n_feat = nodedef->get(neighbor_content); + + // Don't draw face if neighbor is blocking the view + if(n_feat.solidness == 2) + continue; + + bool neighbor_is_same_liquid = (neighbor_content == c_source + || neighbor_content == c_flowing); + + // Don't draw any faces if neighbor same is liquid and top is + // same liquid + if(neighbor_is_same_liquid == true + && top_is_same_liquid == false) + continue; + + // Use backface culled material if neighbor doesn't have a + // solidness of 0 + const TileSpec *current_tile = &tile_liquid; + if(n_feat.solidness != 0 || n_feat.visual_solidness != 0) + current_tile = &tile_liquid_bfculled; + + video::S3DVertex vertices[4] = + { + video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,1), + video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,1), + video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,0), + video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,0), + }; + + /* + If our topside is liquid, set upper border of face + at upper border of node + */ + if(top_is_same_liquid) + { + vertices[2].Pos.Y = 0.5*BS; + vertices[3].Pos.Y = 0.5*BS; + } + /* + Otherwise upper position of face is corner levels + */ + else + { + vertices[2].Pos.Y = corner_levels[side_corners[i][0]]; + vertices[3].Pos.Y = corner_levels[side_corners[i][1]]; + } + + /* + If neighbor is liquid, lower border of face is corner + liquid levels + */ + if(neighbor_is_same_liquid) + { + vertices[0].Pos.Y = corner_levels[side_corners[i][1]]; + vertices[1].Pos.Y = corner_levels[side_corners[i][0]]; + } + /* + If neighbor is not liquid, lower border of face is + lower border of node + */ + else + { + vertices[0].Pos.Y = -0.5*BS; + vertices[1].Pos.Y = -0.5*BS; + } + + for(s32 j=0; j<4; j++) + { + if(dir == v3s16(0,0,1)) + vertices[j].Pos.rotateXZBy(0); + if(dir == v3s16(0,0,-1)) + vertices[j].Pos.rotateXZBy(180); + if(dir == v3s16(-1,0,0)) + vertices[j].Pos.rotateXZBy(90); + if(dir == v3s16(1,0,-0)) + vertices[j].Pos.rotateXZBy(-90); + + // Do this to not cause glitches when two liquids are + // side-by-side + /*if(neighbor_is_same_liquid == false){ + vertices[j].Pos.X *= 0.98; + vertices[j].Pos.Z *= 0.98; + }*/ + + vertices[j].Pos += intToFloat(p, BS); + } + + u16 indices[] = {0,1,2,2,3,0}; + // Add to mesh collector + collector.append(*current_tile, vertices, 4, indices, 6); + } + + /* + Generate top side, if appropriate + */ + + if(top_is_same_liquid == false) + { + video::S3DVertex vertices[4] = + { + video::S3DVertex(-BS/2,0,BS/2, 0,0,0, c, 0,1), + video::S3DVertex(BS/2,0,BS/2, 0,0,0, c, 1,1), + video::S3DVertex(BS/2,0,-BS/2, 0,0,0, c, 1,0), + video::S3DVertex(-BS/2,0,-BS/2, 0,0,0, c, 0,0), + }; + + // To get backface culling right, the vertices need to go + // clockwise around the front of the face. And we happened to + // calculate corner levels in exact reverse order. + s32 corner_resolve[4] = {3,2,1,0}; + + for(s32 i=0; i<4; i++) + { + //vertices[i].Pos.Y += liquid_level; + //vertices[i].Pos.Y += neighbor_levels[v3s16(0,0,0)]; + s32 j = corner_resolve[i]; + vertices[i].Pos.Y += corner_levels[j]; + vertices[i].Pos += intToFloat(p, BS); + } + + // Default downwards-flowing texture animation goes from + // -Z towards +Z, thus the direction is +Z. + // Rotate texture to make animation go in flow direction + // Positive if liquid moves towards +Z + f32 dz = (corner_levels[side_corners[3][0]] + + corner_levels[side_corners[3][1]]) - + (corner_levels[side_corners[2][0]] + + corner_levels[side_corners[2][1]]); + // Positive if liquid moves towards +X + f32 dx = (corner_levels[side_corners[1][0]] + + corner_levels[side_corners[1][1]]) - + (corner_levels[side_corners[0][0]] + + corner_levels[side_corners[0][1]]); + f32 tcoord_angle = atan2(dz, dx) * core::RADTODEG ; + v2f tcoord_center(0.5, 0.5); + v2f tcoord_translate( + blockpos_nodes.Z + z, + blockpos_nodes.X + x); + tcoord_translate.rotateBy(tcoord_angle); + tcoord_translate.X -= floor(tcoord_translate.X); + tcoord_translate.Y -= floor(tcoord_translate.Y); + + for(s32 i=0; i<4; i++) + { + vertices[i].TCoords.rotateBy( + tcoord_angle, + tcoord_center); + vertices[i].TCoords += tcoord_translate; + } + + v2f t = vertices[0].TCoords; + vertices[0].TCoords = vertices[2].TCoords; + vertices[2].TCoords = t; + + u16 indices[] = {0,1,2,2,3,0}; + // Add to mesh collector + collector.append(tile_liquid, vertices, 4, indices, 6); + } + break;} + case NDT_GLASSLIKE: + { + TileSpec tile = getNodeTile(n, p, v3s16(0,0,0), data); + + u16 l = getInteriorLight(n, 1, nodedef); + video::SColor c = MapBlock_LightColor(255, l, f.light_source); + + for(u32 j=0; j<6; j++) + { + // Check this neighbor + v3s16 dir = g_6dirs[j]; + v3s16 n2p = blockpos_nodes + p + dir; + MapNode n2 = data->m_vmanip.getNodeNoEx(n2p); + // Don't make face if neighbor is of same type + if(n2.getContent() == n.getContent()) + continue; + + // The face at Z+ + video::S3DVertex vertices[4] = { + video::S3DVertex(-BS/2,-BS/2,BS/2, dir.X,dir.Y,dir.Z, c, 1,1), + video::S3DVertex(BS/2,-BS/2,BS/2, dir.X,dir.Y,dir.Z, c, 0,1), + video::S3DVertex(BS/2,BS/2,BS/2, dir.X,dir.Y,dir.Z, c, 0,0), + video::S3DVertex(-BS/2,BS/2,BS/2, dir.X,dir.Y,dir.Z, c, 1,0), + }; + + // Rotations in the g_6dirs format + if(j == 0) // Z+ + for(u16 i=0; i<4; i++) + vertices[i].Pos.rotateXZBy(0); + else if(j == 1) // Y+ + for(u16 i=0; i<4; i++) + vertices[i].Pos.rotateYZBy(-90); + else if(j == 2) // X+ + for(u16 i=0; i<4; i++) + vertices[i].Pos.rotateXZBy(-90); + else if(j == 3) // Z- + for(u16 i=0; i<4; i++) + vertices[i].Pos.rotateXZBy(180); + else if(j == 4) // Y- + for(u16 i=0; i<4; i++) + vertices[i].Pos.rotateYZBy(90); + else if(j == 5) // X- + for(u16 i=0; i<4; i++) + vertices[i].Pos.rotateXZBy(90); + + for(u16 i=0; i<4; i++){ + vertices[i].Pos += intToFloat(p, BS); + } + + u16 indices[] = {0,1,2,2,3,0}; + // Add to mesh collector + collector.append(tile, vertices, 4, indices, 6); + } + break;} + case NDT_GLASSLIKE_FRAMED_OPTIONAL: + // This is always pre-converted to something else + FATAL_ERROR("NDT_GLASSLIKE_FRAMED_OPTIONAL not pre-converted as expected"); + break; + case NDT_GLASSLIKE_FRAMED: + { + static const v3s16 dirs[6] = { + v3s16( 0, 1, 0), + v3s16( 0,-1, 0), + v3s16( 1, 0, 0), + v3s16(-1, 0, 0), + v3s16( 0, 0, 1), + v3s16( 0, 0,-1) + }; + + u8 i; + TileSpec tiles[6]; + for (i = 0; i < 6; i++) + tiles[i] = getNodeTile(n, p, dirs[i], data); + + TileSpec glass_tiles[6]; + if (tiles[1].texture && tiles[2].texture && tiles[3].texture) { + glass_tiles[0] = tiles[2]; + glass_tiles[1] = tiles[3]; + glass_tiles[2] = tiles[1]; + glass_tiles[3] = tiles[1]; + glass_tiles[4] = tiles[1]; + glass_tiles[5] = tiles[1]; + } else { + for (i = 0; i < 6; i++) + glass_tiles[i] = tiles[1]; + } + + u8 param2 = n.getParam2(); + bool H_merge = ! bool(param2 & 128); + bool V_merge = ! bool(param2 & 64); + param2 = param2 & 63; + + u16 l = getInteriorLight(n, 1, nodedef); + video::SColor c = MapBlock_LightColor(255, l, f.light_source); + v3f pos = intToFloat(p, BS); + static const float a = BS / 2; + static const float g = a - 0.003; + static const float b = .876 * ( BS / 2 ); + + static const aabb3f frame_edges[12] = { + aabb3f( b, b,-a, a, a, a), // y+ + aabb3f(-a, b,-a,-b, a, a), // y+ + aabb3f( b,-a,-a, a,-b, a), // y- + aabb3f(-a,-a,-a,-b,-b, a), // y- + aabb3f( b,-a, b, a, a, a), // x+ + aabb3f( b,-a,-a, a, a,-b), // x+ + aabb3f(-a,-a, b,-b, a, a), // x- + aabb3f(-a,-a,-a,-b, a,-b), // x- + aabb3f(-a, b, b, a, a, a), // z+ + aabb3f(-a,-a, b, a,-b, a), // z+ + aabb3f(-a,-a,-a, a,-b,-b), // z- + aabb3f(-a, b,-a, a, a,-b) // z- + }; + static const aabb3f glass_faces[6] = { + aabb3f(-g, g,-g, g, g, g), // y+ + aabb3f(-g,-g,-g, g,-g, g), // y- + aabb3f( g,-g,-g, g, g, g), // x+ + aabb3f(-g,-g,-g,-g, g, g), // x- + aabb3f(-g,-g, g, g, g, g), // z+ + aabb3f(-g,-g,-g, g, g,-g) // z- + }; + + // table of node visible faces, 0 = invisible + int visible_faces[6] = {0,0,0,0,0,0}; + + // table of neighbours, 1 = same type, checked with g_26dirs + int nb[18] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + + // g_26dirs to check when only horizontal merge is allowed + int nb_H_dirs[8] = {0,2,3,5,10,11,12,13}; + + content_t current = n.getContent(); + content_t n2c; + MapNode n2; + v3s16 n2p; + + // neighbours checks for frames visibility + + if (!H_merge && V_merge) { + n2p = blockpos_nodes + p + g_26dirs[1]; + n2 = data->m_vmanip.getNodeNoEx(n2p); + n2c = n2.getContent(); + if (n2c == current || n2c == CONTENT_IGNORE) + nb[1] = 1; + n2p = blockpos_nodes + p + g_26dirs[4]; + n2 = data->m_vmanip.getNodeNoEx(n2p); + n2c = n2.getContent(); + if (n2c == current || n2c == CONTENT_IGNORE) + nb[4] = 1; + } else if (H_merge && !V_merge) { + for(i = 0; i < 8; i++) { + n2p = blockpos_nodes + p + g_26dirs[nb_H_dirs[i]]; + n2 = data->m_vmanip.getNodeNoEx(n2p); + n2c = n2.getContent(); + if (n2c == current || n2c == CONTENT_IGNORE) + nb[nb_H_dirs[i]] = 1; + } + } else if (H_merge && V_merge) { + for(i = 0; i < 18; i++) { + n2p = blockpos_nodes + p + g_26dirs[i]; + n2 = data->m_vmanip.getNodeNoEx(n2p); + n2c = n2.getContent(); + if (n2c == current || n2c == CONTENT_IGNORE) + nb[i] = 1; + } + } + + // faces visibility checks + + if (!V_merge) { + visible_faces[0] = 1; + visible_faces[1] = 1; + } else { + for(i = 0; i < 2; i++) { + n2p = blockpos_nodes + p + dirs[i]; + n2 = data->m_vmanip.getNodeNoEx(n2p); + n2c = n2.getContent(); + if (n2c != current) + visible_faces[i] = 1; + } + } + + if (!H_merge) { + visible_faces[2] = 1; + visible_faces[3] = 1; + visible_faces[4] = 1; + visible_faces[5] = 1; + } else { + for(i = 2; i < 6; i++) { + n2p = blockpos_nodes + p + dirs[i]; + n2 = data->m_vmanip.getNodeNoEx(n2p); + n2c = n2.getContent(); + if (n2c != current) + visible_faces[i] = 1; + } + } + + static const u8 nb_triplet[12*3] = { + 1,2, 7, 1,5, 6, 4,2,15, 4,5,14, + 2,0,11, 2,3,13, 5,0,10, 5,3,12, + 0,1, 8, 0,4,16, 3,4,17, 3,1, 9 + }; + + f32 tx1, ty1, tz1, tx2, ty2, tz2; + aabb3f box; + + for(i = 0; i < 12; i++) + { + int edge_invisible; + if (nb[nb_triplet[i*3+2]]) + edge_invisible = nb[nb_triplet[i*3]] & nb[nb_triplet[i*3+1]]; + else + edge_invisible = nb[nb_triplet[i*3]] ^ nb[nb_triplet[i*3+1]]; + if (edge_invisible) + continue; + box = frame_edges[i]; + box.MinEdge += pos; + box.MaxEdge += pos; + tx1 = (box.MinEdge.X / BS) + 0.5; + ty1 = (box.MinEdge.Y / BS) + 0.5; + tz1 = (box.MinEdge.Z / BS) + 0.5; + tx2 = (box.MaxEdge.X / BS) + 0.5; + ty2 = (box.MaxEdge.Y / BS) + 0.5; + tz2 = (box.MaxEdge.Z / BS) + 0.5; + f32 txc1[24] = { + tx1, 1-tz2, tx2, 1-tz1, + tx1, tz1, tx2, tz2, + tz1, 1-ty2, tz2, 1-ty1, + 1-tz2, 1-ty2, 1-tz1, 1-ty1, + 1-tx2, 1-ty2, 1-tx1, 1-ty1, + tx1, 1-ty2, tx2, 1-ty1, + }; + makeCuboid(&collector, box, &tiles[0], 1, c, txc1); + } + + for(i = 0; i < 6; i++) + { + if (!visible_faces[i]) + continue; + box = glass_faces[i]; + box.MinEdge += pos; + box.MaxEdge += pos; + tx1 = (box.MinEdge.X / BS) + 0.5; + ty1 = (box.MinEdge.Y / BS) + 0.5; + tz1 = (box.MinEdge.Z / BS) + 0.5; + tx2 = (box.MaxEdge.X / BS) + 0.5; + ty2 = (box.MaxEdge.Y / BS) + 0.5; + tz2 = (box.MaxEdge.Z / BS) + 0.5; + f32 txc2[24] = { + tx1, 1-tz2, tx2, 1-tz1, + tx1, tz1, tx2, tz2, + tz1, 1-ty2, tz2, 1-ty1, + 1-tz2, 1-ty2, 1-tz1, 1-ty1, + 1-tx2, 1-ty2, 1-tx1, 1-ty1, + tx1, 1-ty2, tx2, 1-ty1, + }; + makeCuboid(&collector, box, &glass_tiles[i], 1, c, txc2); + } + + if (param2 > 0 && f.special_tiles[0].texture) { + // Interior volume level is in range 0 .. 63, + // convert it to -0.5 .. 0.5 + float vlev = (((float)param2 / 63.0 ) * 2.0 - 1.0); + TileSpec interior_tiles[6]; + for (i = 0; i < 6; i++) + interior_tiles[i] = f.special_tiles[0]; + float offset = 0.003; + box = aabb3f(visible_faces[3] ? -b : -a + offset, + visible_faces[1] ? -b : -a + offset, + visible_faces[5] ? -b : -a + offset, + visible_faces[2] ? b : a - offset, + visible_faces[0] ? b * vlev : a * vlev - offset, + visible_faces[4] ? b : a - offset); + box.MinEdge += pos; + box.MaxEdge += pos; + tx1 = (box.MinEdge.X / BS) + 0.5; + ty1 = (box.MinEdge.Y / BS) + 0.5; + tz1 = (box.MinEdge.Z / BS) + 0.5; + tx2 = (box.MaxEdge.X / BS) + 0.5; + ty2 = (box.MaxEdge.Y / BS) + 0.5; + tz2 = (box.MaxEdge.Z / BS) + 0.5; + f32 txc3[24] = { + tx1, 1-tz2, tx2, 1-tz1, + tx1, tz1, tx2, tz2, + tz1, 1-ty2, tz2, 1-ty1, + 1-tz2, 1-ty2, 1-tz1, 1-ty1, + 1-tx2, 1-ty2, 1-tx1, 1-ty1, + tx1, 1-ty2, tx2, 1-ty1, + }; + makeCuboid(&collector, box, interior_tiles, 6, c, txc3); + } + break;} + case NDT_ALLFACES: + { + TileSpec tile_leaves = getNodeTile(n, p, + v3s16(0,0,0), data); + + u16 l = getInteriorLight(n, 1, nodedef); + video::SColor c = MapBlock_LightColor(255, l, f.light_source); + + v3f pos = intToFloat(p, BS); + aabb3f box(-BS/2,-BS/2,-BS/2,BS/2,BS/2,BS/2); + box.MinEdge += pos; + box.MaxEdge += pos; + makeCuboid(&collector, box, &tile_leaves, 1, c, NULL); + break;} + case NDT_ALLFACES_OPTIONAL: + // This is always pre-converted to something else + FATAL_ERROR("NDT_ALLFACES_OPTIONAL not pre-converted"); + break; + case NDT_TORCHLIKE: + { + v3s16 dir = n.getWallMountedDir(nodedef); + + u8 tileindex = 0; + if(dir == v3s16(0,-1,0)){ + tileindex = 0; // floor + } else if(dir == v3s16(0,1,0)){ + tileindex = 1; // ceiling + // For backwards compatibility + } else if(dir == v3s16(0,0,0)){ + tileindex = 0; // floor + } else { + tileindex = 2; // side + } + + TileSpec tile = getNodeTileN(n, p, tileindex, data); + tile.material_flags &= ~MATERIAL_FLAG_BACKFACE_CULLING; + tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY; + + u16 l = getInteriorLight(n, 1, nodedef); + video::SColor c = MapBlock_LightColor(255, l, f.light_source); + + float s = BS/2*f.visual_scale; + // Wall at X+ of node + video::S3DVertex vertices[4] = + { + video::S3DVertex(-s,-s,0, 0,0,0, c, 0,1), + video::S3DVertex( s,-s,0, 0,0,0, c, 1,1), + video::S3DVertex( s, s,0, 0,0,0, c, 1,0), + video::S3DVertex(-s, s,0, 0,0,0, c, 0,0), + }; + + for(s32 i=0; i<4; i++) + { + if(dir == v3s16(1,0,0)) + vertices[i].Pos.rotateXZBy(0); + if(dir == v3s16(-1,0,0)) + vertices[i].Pos.rotateXZBy(180); + if(dir == v3s16(0,0,1)) + vertices[i].Pos.rotateXZBy(90); + if(dir == v3s16(0,0,-1)) + vertices[i].Pos.rotateXZBy(-90); + if(dir == v3s16(0,-1,0)) + vertices[i].Pos.rotateXZBy(45); + if(dir == v3s16(0,1,0)) + vertices[i].Pos.rotateXZBy(-45); + + vertices[i].Pos += intToFloat(p, BS); + } + + u16 indices[] = {0,1,2,2,3,0}; + // Add to mesh collector + collector.append(tile, vertices, 4, indices, 6); + break;} + case NDT_SIGNLIKE: + { + TileSpec tile = getNodeTileN(n, p, 0, data); + tile.material_flags &= ~MATERIAL_FLAG_BACKFACE_CULLING; + tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY; + + u16 l = getInteriorLight(n, 0, nodedef); + video::SColor c = MapBlock_LightColor(255, l, f.light_source); + + float d = (float)BS/16; + float s = BS/2*f.visual_scale; + // Wall at X+ of node + video::S3DVertex vertices[4] = + { + video::S3DVertex(BS/2-d, s, s, 0,0,0, c, 0,0), + video::S3DVertex(BS/2-d, s, -s, 0,0,0, c, 1,0), + video::S3DVertex(BS/2-d, -s, -s, 0,0,0, c, 1,1), + video::S3DVertex(BS/2-d, -s, s, 0,0,0, c, 0,1), + }; + + v3s16 dir = n.getWallMountedDir(nodedef); + + for(s32 i=0; i<4; i++) + { + if(dir == v3s16(1,0,0)) + vertices[i].Pos.rotateXZBy(0); + if(dir == v3s16(-1,0,0)) + vertices[i].Pos.rotateXZBy(180); + if(dir == v3s16(0,0,1)) + vertices[i].Pos.rotateXZBy(90); + if(dir == v3s16(0,0,-1)) + vertices[i].Pos.rotateXZBy(-90); + if(dir == v3s16(0,-1,0)) + vertices[i].Pos.rotateXYBy(-90); + if(dir == v3s16(0,1,0)) + vertices[i].Pos.rotateXYBy(90); + + vertices[i].Pos += intToFloat(p, BS); + } + + u16 indices[] = {0,1,2,2,3,0}; + // Add to mesh collector + collector.append(tile, vertices, 4, indices, 6); + break;} + case NDT_PLANTLIKE: + { + TileSpec tile = getNodeTileN(n, p, 0, data); + tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY; + + u16 l = getInteriorLight(n, 1, nodedef); + video::SColor c = MapBlock_LightColor(255, l, f.light_source); + + float s = BS / 2 * f.visual_scale; + + for (int j = 0; j < 2; j++) + { + video::S3DVertex vertices[4] = + { + video::S3DVertex(-s,-BS/2, 0, 0,0,0, c, 0,1), + video::S3DVertex( s,-BS/2, 0, 0,0,0, c, 1,1), + video::S3DVertex( s,-BS/2 + s*2,0, 0,0,0, c, 1,0), + video::S3DVertex(-s,-BS/2 + s*2,0, 0,0,0, c, 0,0), + }; + + if(j == 0) + { + for(u16 i = 0; i < 4; i++) + vertices[i].Pos.rotateXZBy(46 + n.param2 * 2); + } + else if(j == 1) + { + for(u16 i = 0; i < 4; i++) + vertices[i].Pos.rotateXZBy(-44 + n.param2 * 2); + } + + for (int i = 0; i < 4; i++) + { + vertices[i].Pos *= f.visual_scale; + vertices[i].Pos.Y += BS/2 * (f.visual_scale - 1); + vertices[i].Pos += intToFloat(p, BS); + } + + u16 indices[] = {0, 1, 2, 2, 3, 0}; + // Add to mesh collector + collector.append(tile, vertices, 4, indices, 6); + } + break;} + case NDT_FIRELIKE: + { + TileSpec tile = getNodeTileN(n, p, 0, data); + tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY; + + u16 l = getInteriorLight(n, 1, nodedef); + video::SColor c = MapBlock_LightColor(255, l, f.light_source); + + float s = BS/2*f.visual_scale; + + content_t current = n.getContent(); + content_t n2c; + MapNode n2; + v3s16 n2p; + + static const v3s16 dirs[6] = { + v3s16( 0, 1, 0), + v3s16( 0,-1, 0), + v3s16( 1, 0, 0), + v3s16(-1, 0, 0), + v3s16( 0, 0, 1), + v3s16( 0, 0,-1) + }; + + int doDraw[6] = {0,0,0,0,0,0}; + + bool drawAllFaces = true; + + bool drawBottomFacesOnly = false; // Currently unused + + // Check for adjacent nodes + for(int i = 0; i < 6; i++) + { + n2p = blockpos_nodes + p + dirs[i]; + n2 = data->m_vmanip.getNodeNoEx(n2p); + n2c = n2.getContent(); + if (n2c != CONTENT_IGNORE && n2c != CONTENT_AIR && n2c != current) { + doDraw[i] = 1; + if(drawAllFaces) + drawAllFaces = false; + + } + } + + for(int j = 0; j < 6; j++) + { + int vOffset = 0; // Vertical offset of faces after rotation + int hOffset = 4; // Horizontal offset of faces to reach the edge + + video::S3DVertex vertices[4] = + { + video::S3DVertex(-s,-BS/2, 0, 0,0,0, c, 0,1), + video::S3DVertex( s,-BS/2, 0, 0,0,0, c, 1,1), + video::S3DVertex( s,-BS/2 + s*2,0, 0,0,0, c, 1,0), + video::S3DVertex(-s,-BS/2 + s*2,0, 0,0,0, c, 0,0), + }; + + // Calculate which faces should be drawn, (top or sides) + if(j == 0 && (drawAllFaces || (doDraw[3] == 1 || doDraw[1] == 1))) + { + for(int i = 0; i < 4; i++) { + vertices[i].Pos.rotateXZBy(90 + n.param2 * 2); + vertices[i].Pos.rotateXYBy(-10); + vertices[i].Pos.Y -= vOffset; + vertices[i].Pos.X -= hOffset; + } + } + else if(j == 1 && (drawAllFaces || (doDraw[5] == 1 || doDraw[1] == 1))) + { + for(int i = 0; i < 4; i++) { + vertices[i].Pos.rotateXZBy(180 + n.param2 * 2); + vertices[i].Pos.rotateYZBy(10); + vertices[i].Pos.Y -= vOffset; + vertices[i].Pos.Z -= hOffset; + } + } + else if(j == 2 && (drawAllFaces || (doDraw[2] == 1 || doDraw[1] == 1))) + { + for(int i = 0; i < 4; i++) { + vertices[i].Pos.rotateXZBy(270 + n.param2 * 2); + vertices[i].Pos.rotateXYBy(10); + vertices[i].Pos.Y -= vOffset; + vertices[i].Pos.X += hOffset; + } + } + else if(j == 3 && (drawAllFaces || (doDraw[4] == 1 || doDraw[1] == 1))) + { + for(int i = 0; i < 4; i++) { + vertices[i].Pos.rotateYZBy(-10); + vertices[i].Pos.Y -= vOffset; + vertices[i].Pos.Z += hOffset; + } + } + + // Center cross-flames + else if(j == 4 && (drawAllFaces || doDraw[1] == 1)) + { + for(int i=0; i<4; i++) { + vertices[i].Pos.rotateXZBy(45 + n.param2 * 2); + vertices[i].Pos.Y -= vOffset; + } + } + else if(j == 5 && (drawAllFaces || doDraw[1] == 1)) + { + for(int i=0; i<4; i++) { + vertices[i].Pos.rotateXZBy(-45 + n.param2 * 2); + vertices[i].Pos.Y -= vOffset; + } + } + + // Render flames on bottom + else if(j == 0 && (drawBottomFacesOnly || (doDraw[0] == 1 && doDraw[1] == 0))) + { + for(int i = 0; i < 4; i++) { + vertices[i].Pos.rotateYZBy(70); + vertices[i].Pos.rotateXZBy(90 + n.param2 * 2); + vertices[i].Pos.Y += 4.84; + vertices[i].Pos.X -= hOffset+0.7; + } + } + else if(j == 1 && (drawBottomFacesOnly || (doDraw[0] == 1 && doDraw[1] == 0))) + { + for(int i = 0; i < 4; i++) { + vertices[i].Pos.rotateYZBy(70); + vertices[i].Pos.rotateXZBy(180 + n.param2 * 2); + vertices[i].Pos.Y += 4.84; + vertices[i].Pos.Z -= hOffset+0.7; + } + } + else if(j == 2 && (drawBottomFacesOnly || (doDraw[0] == 1 && doDraw[1] == 0))) + { + for(int i = 0; i < 4; i++) { + vertices[i].Pos.rotateYZBy(70); + vertices[i].Pos.rotateXZBy(270 + n.param2 * 2); + vertices[i].Pos.Y += 4.84; + vertices[i].Pos.X += hOffset+0.7; + } + } + else if(j == 3 && (drawBottomFacesOnly || (doDraw[0] == 1 && doDraw[1] == 0))) + { + for(int i = 0; i < 4; i++) { + vertices[i].Pos.rotateYZBy(70); + vertices[i].Pos.Y += 4.84; + vertices[i].Pos.Z += hOffset+0.7; + } + } + else { + // Skip faces that aren't adjacent to a node + continue; + } + + for(int i=0; i<4; i++) + { + vertices[i].Pos *= f.visual_scale; + vertices[i].Pos += intToFloat(p, BS); + } + + u16 indices[] = {0,1,2,2,3,0}; + // Add to mesh collector + collector.append(tile, vertices, 4, indices, 6); + } + break;} + case NDT_FENCELIKE: + { + TileSpec tile = getNodeTile(n, p, v3s16(0,0,0), data); + TileSpec tile_nocrack = tile; + tile_nocrack.material_flags &= ~MATERIAL_FLAG_CRACK; + + // Put wood the right way around in the posts + TileSpec tile_rot = tile; + tile_rot.rotation = 1; + + u16 l = getInteriorLight(n, 1, nodedef); + video::SColor c = MapBlock_LightColor(255, l, f.light_source); + + const f32 post_rad=(f32)BS/8; + const f32 bar_rad=(f32)BS/16; + const f32 bar_len=(f32)(BS/2)-post_rad; + + v3f pos = intToFloat(p, BS); + + // The post - always present + aabb3f post(-post_rad,-BS/2,-post_rad,post_rad,BS/2,post_rad); + post.MinEdge += pos; + post.MaxEdge += pos; + f32 postuv[24]={ + 6/16.,6/16.,10/16.,10/16., + 6/16.,6/16.,10/16.,10/16., + 0/16.,0,4/16.,1, + 4/16.,0,8/16.,1, + 8/16.,0,12/16.,1, + 12/16.,0,16/16.,1}; + makeCuboid(&collector, post, &tile_rot, 1, c, postuv); + + // Now a section of fence, +X, if there's a post there + v3s16 p2 = p; + p2.X++; + MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); + const ContentFeatures *f2 = &nodedef->get(n2); + if(f2->drawtype == NDT_FENCELIKE) + { + aabb3f bar(-bar_len+BS/2,-bar_rad+BS/4,-bar_rad, + bar_len+BS/2,bar_rad+BS/4,bar_rad); + bar.MinEdge += pos; + bar.MaxEdge += pos; + f32 xrailuv[24]={ + 0/16.,2/16.,16/16.,4/16., + 0/16.,4/16.,16/16.,6/16., + 6/16.,6/16.,8/16.,8/16., + 10/16.,10/16.,12/16.,12/16., + 0/16.,8/16.,16/16.,10/16., + 0/16.,14/16.,16/16.,16/16.}; + makeCuboid(&collector, bar, &tile_nocrack, 1, + c, xrailuv); + bar.MinEdge.Y -= BS/2; + bar.MaxEdge.Y -= BS/2; + makeCuboid(&collector, bar, &tile_nocrack, 1, + c, xrailuv); + } + + // Now a section of fence, +Z, if there's a post there + p2 = p; + p2.Z++; + n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2); + f2 = &nodedef->get(n2); + if(f2->drawtype == NDT_FENCELIKE) + { + aabb3f bar(-bar_rad,-bar_rad+BS/4,-bar_len+BS/2, + bar_rad,bar_rad+BS/4,bar_len+BS/2); + bar.MinEdge += pos; + bar.MaxEdge += pos; + f32 zrailuv[24]={ + 3/16.,1/16.,5/16.,5/16., // cannot rotate; stretch + 4/16.,1/16.,6/16.,5/16., // for wood texture instead + 0/16.,9/16.,16/16.,11/16., + 0/16.,6/16.,16/16.,8/16., + 6/16.,6/16.,8/16.,8/16., + 10/16.,10/16.,12/16.,12/16.}; + makeCuboid(&collector, bar, &tile_nocrack, 1, + c, zrailuv); + bar.MinEdge.Y -= BS/2; + bar.MaxEdge.Y -= BS/2; + makeCuboid(&collector, bar, &tile_nocrack, 1, + c, zrailuv); + } + break;} + case NDT_RAILLIKE: + { + bool is_rail_x [] = { false, false }; /* x-1, x+1 */ + bool is_rail_z [] = { false, false }; /* z-1, z+1 */ + + bool is_rail_z_minus_y [] = { false, false }; /* z-1, z+1; y-1 */ + bool is_rail_x_minus_y [] = { false, false }; /* x-1, z+1; y-1 */ + bool is_rail_z_plus_y [] = { false, false }; /* z-1, z+1; y+1 */ + bool is_rail_x_plus_y [] = { false, false }; /* x-1, x+1; y+1 */ + + MapNode n_minus_x = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x-1,y,z)); + MapNode n_plus_x = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x+1,y,z)); + MapNode n_minus_z = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y,z-1)); + MapNode n_plus_z = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y,z+1)); + MapNode n_plus_x_plus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x+1, y+1, z)); + MapNode n_plus_x_minus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x+1, y-1, z)); + MapNode n_minus_x_plus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x-1, y+1, z)); + MapNode n_minus_x_minus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x-1, y-1, z)); + MapNode n_plus_z_plus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y+1, z+1)); + MapNode n_minus_z_plus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y+1, z-1)); + MapNode n_plus_z_minus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y-1, z+1)); + MapNode n_minus_z_minus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y-1, z-1)); + + content_t thiscontent = n.getContent(); + std::string groupname = "connect_to_raillike"; // name of the group that enables connecting to raillike nodes of different kind + int self_group = ((ItemGroupList) nodedef->get(n).groups)[groupname]; + + if ((nodedef->get(n_minus_x).drawtype == NDT_RAILLIKE + && ((ItemGroupList) nodedef->get(n_minus_x).groups)[groupname] != self_group) + || n_minus_x.getContent() == thiscontent) + is_rail_x[0] = true; + + if ((nodedef->get(n_minus_x_minus_y).drawtype == NDT_RAILLIKE + && ((ItemGroupList) nodedef->get(n_minus_x_minus_y).groups)[groupname] != self_group) + || n_minus_x_minus_y.getContent() == thiscontent) + is_rail_x_minus_y[0] = true; + + if ((nodedef->get(n_minus_x_plus_y).drawtype == NDT_RAILLIKE + && ((ItemGroupList) nodedef->get(n_minus_x_plus_y).groups)[groupname] != self_group) + || n_minus_x_plus_y.getContent() == thiscontent) + is_rail_x_plus_y[0] = true; + + if ((nodedef->get(n_plus_x).drawtype == NDT_RAILLIKE + && ((ItemGroupList) nodedef->get(n_plus_x).groups)[groupname] != self_group) + || n_plus_x.getContent() == thiscontent) + is_rail_x[1] = true; + + if ((nodedef->get(n_plus_x_minus_y).drawtype == NDT_RAILLIKE + && ((ItemGroupList) nodedef->get(n_plus_x_minus_y).groups)[groupname] != self_group) + || n_plus_x_minus_y.getContent() == thiscontent) + is_rail_x_minus_y[1] = true; + + if ((nodedef->get(n_plus_x_plus_y).drawtype == NDT_RAILLIKE + && ((ItemGroupList) nodedef->get(n_plus_x_plus_y).groups)[groupname] != self_group) + || n_plus_x_plus_y.getContent() == thiscontent) + is_rail_x_plus_y[1] = true; + + if ((nodedef->get(n_minus_z).drawtype == NDT_RAILLIKE + && ((ItemGroupList) nodedef->get(n_minus_z).groups)[groupname] != self_group) + || n_minus_z.getContent() == thiscontent) + is_rail_z[0] = true; + + if ((nodedef->get(n_minus_z_minus_y).drawtype == NDT_RAILLIKE + && ((ItemGroupList) nodedef->get(n_minus_z_minus_y).groups)[groupname] != self_group) + || n_minus_z_minus_y.getContent() == thiscontent) + is_rail_z_minus_y[0] = true; + + if ((nodedef->get(n_minus_z_plus_y).drawtype == NDT_RAILLIKE + && ((ItemGroupList) nodedef->get(n_minus_z_plus_y).groups)[groupname] != self_group) + || n_minus_z_plus_y.getContent() == thiscontent) + is_rail_z_plus_y[0] = true; + + if ((nodedef->get(n_plus_z).drawtype == NDT_RAILLIKE + && ((ItemGroupList) nodedef->get(n_plus_z).groups)[groupname] != self_group) + || n_plus_z.getContent() == thiscontent) + is_rail_z[1] = true; + + if ((nodedef->get(n_plus_z_minus_y).drawtype == NDT_RAILLIKE + && ((ItemGroupList) nodedef->get(n_plus_z_minus_y).groups)[groupname] != self_group) + || n_plus_z_minus_y.getContent() == thiscontent) + is_rail_z_minus_y[1] = true; + + if ((nodedef->get(n_plus_z_plus_y).drawtype == NDT_RAILLIKE + && ((ItemGroupList) nodedef->get(n_plus_z_plus_y).groups)[groupname] != self_group) + || n_plus_z_plus_y.getContent() == thiscontent) + is_rail_z_plus_y[1] = true; + + bool is_rail_x_all[] = {false, false}; + bool is_rail_z_all[] = {false, false}; + is_rail_x_all[0]=is_rail_x[0] || is_rail_x_minus_y[0] || is_rail_x_plus_y[0]; + is_rail_x_all[1]=is_rail_x[1] || is_rail_x_minus_y[1] || is_rail_x_plus_y[1]; + is_rail_z_all[0]=is_rail_z[0] || is_rail_z_minus_y[0] || is_rail_z_plus_y[0]; + is_rail_z_all[1]=is_rail_z[1] || is_rail_z_minus_y[1] || is_rail_z_plus_y[1]; + + // reasonable default, flat straight unrotated rail + bool is_straight = true; + int adjacencies = 0; + int angle = 0; + u8 tileindex = 0; + + // check for sloped rail + if (is_rail_x_plus_y[0] || is_rail_x_plus_y[1] || is_rail_z_plus_y[0] || is_rail_z_plus_y[1]) + { + adjacencies = 5; //5 means sloped + is_straight = true; // sloped is always straight + } + else + { + // is really straight, rails on both sides + is_straight = (is_rail_x_all[0] && is_rail_x_all[1]) || (is_rail_z_all[0] && is_rail_z_all[1]); + adjacencies = is_rail_x_all[0] + is_rail_x_all[1] + is_rail_z_all[0] + is_rail_z_all[1]; + } + + switch (adjacencies) { + case 1: + if(is_rail_x_all[0] || is_rail_x_all[1]) + angle = 90; + break; + case 2: + if(!is_straight) + tileindex = 1; // curved + if(is_rail_x_all[0] && is_rail_x_all[1]) + angle = 90; + if(is_rail_z_all[0] && is_rail_z_all[1]){ + if (is_rail_z_plus_y[0]) + angle = 180; + } + else if(is_rail_x_all[0] && is_rail_z_all[0]) + angle = 270; + else if(is_rail_x_all[0] && is_rail_z_all[1]) + angle = 180; + else if(is_rail_x_all[1] && is_rail_z_all[1]) + angle = 90; + break; + case 3: + // here is where the potential to 'switch' a junction is, but not implemented at present + tileindex = 2; // t-junction + if(!is_rail_x_all[1]) + angle=180; + if(!is_rail_z_all[0]) + angle=90; + if(!is_rail_z_all[1]) + angle=270; + break; + case 4: + tileindex = 3; // crossing + break; + case 5: //sloped + if(is_rail_z_plus_y[0]) + angle = 180; + if(is_rail_x_plus_y[0]) + angle = 90; + if(is_rail_x_plus_y[1]) + angle = -90; + break; + default: + break; + } + + TileSpec tile = getNodeTileN(n, p, tileindex, data); + tile.material_flags &= ~MATERIAL_FLAG_BACKFACE_CULLING; + tile.material_flags |= MATERIAL_FLAG_CRACK_OVERLAY; + + u16 l = getInteriorLight(n, 0, nodedef); + video::SColor c = MapBlock_LightColor(255, l, f.light_source); + + float d = (float)BS/64; + float s = BS/2; + + short g = -1; + if (is_rail_x_plus_y[0] || is_rail_x_plus_y[1] || is_rail_z_plus_y[0] || is_rail_z_plus_y[1]) + g = 1; //Object is at a slope + + video::S3DVertex vertices[4] = + { + video::S3DVertex(-s, -s+d,-s, 0,0,0, c,0,1), + video::S3DVertex( s, -s+d,-s, 0,0,0, c,1,1), + video::S3DVertex( s, g*s+d, s, 0,0,0, c,1,0), + video::S3DVertex(-s, g*s+d, s, 0,0,0, c,0,0), + }; + + for(s32 i=0; i<4; i++) + { + if(angle != 0) + vertices[i].Pos.rotateXZBy(angle); + vertices[i].Pos += intToFloat(p, BS); + } + + u16 indices[] = {0,1,2,2,3,0}; + collector.append(tile, vertices, 4, indices, 6); + break;} + case NDT_NODEBOX: + { + static const v3s16 tile_dirs[6] = { + v3s16(0, 1, 0), + v3s16(0, -1, 0), + v3s16(1, 0, 0), + v3s16(-1, 0, 0), + v3s16(0, 0, 1), + v3s16(0, 0, -1) + }; + TileSpec tiles[6]; + + u16 l = getInteriorLight(n, 1, nodedef); + video::SColor c = MapBlock_LightColor(255, l, f.light_source); + + v3f pos = intToFloat(p, BS); + + std::vector boxes = n.getNodeBoxes(nodedef); + for(std::vector::iterator + i = boxes.begin(); + i != boxes.end(); i++) + { + for(int j = 0; j < 6; j++) + { + // Handles facedir rotation for textures + tiles[j] = getNodeTile(n, p, tile_dirs[j], data); + } + aabb3f box = *i; + box.MinEdge += pos; + box.MaxEdge += pos; + + f32 temp; + if (box.MinEdge.X > box.MaxEdge.X) + { + temp=box.MinEdge.X; + box.MinEdge.X=box.MaxEdge.X; + box.MaxEdge.X=temp; + } + if (box.MinEdge.Y > box.MaxEdge.Y) + { + temp=box.MinEdge.Y; + box.MinEdge.Y=box.MaxEdge.Y; + box.MaxEdge.Y=temp; + } + if (box.MinEdge.Z > box.MaxEdge.Z) + { + temp=box.MinEdge.Z; + box.MinEdge.Z=box.MaxEdge.Z; + box.MaxEdge.Z=temp; + } + + // + // Compute texture coords + f32 tx1 = (box.MinEdge.X/BS)+0.5; + f32 ty1 = (box.MinEdge.Y/BS)+0.5; + f32 tz1 = (box.MinEdge.Z/BS)+0.5; + f32 tx2 = (box.MaxEdge.X/BS)+0.5; + f32 ty2 = (box.MaxEdge.Y/BS)+0.5; + f32 tz2 = (box.MaxEdge.Z/BS)+0.5; + f32 txc[24] = { + // up + tx1, 1-tz2, tx2, 1-tz1, + // down + tx1, tz1, tx2, tz2, + // right + tz1, 1-ty2, tz2, 1-ty1, + // left + 1-tz2, 1-ty2, 1-tz1, 1-ty1, + // back + 1-tx2, 1-ty2, 1-tx1, 1-ty1, + // front + tx1, 1-ty2, tx2, 1-ty1, + }; + makeCuboid(&collector, box, tiles, 6, c, txc); + } + break;} + case NDT_MESH: + { + v3f pos = intToFloat(p, BS); + video::SColor c = MapBlock_LightColor(255, getInteriorLight(n, 1, nodedef), f.light_source); + + u8 facedir = 0; + if (f.param_type_2 == CPT2_FACEDIR) { + facedir = n.getFaceDir(nodedef); + } else if (f.param_type_2 == CPT2_WALLMOUNTED) { + //convert wallmounted to 6dfacedir. + //when cache enabled, it is already converted + facedir = n.getWallMounted(nodedef); + if (!enable_mesh_cache) { + static const u8 wm_to_6d[6] = {20, 0, 16+1, 12+3, 8, 4+2}; + facedir = wm_to_6d[facedir]; + } + } + + if (f.mesh_ptr[facedir]) { + // use cached meshes + for(u16 j = 0; j < f.mesh_ptr[0]->getMeshBufferCount(); j++) { + scene::IMeshBuffer *buf = f.mesh_ptr[facedir]->getMeshBuffer(j); + collector.append(getNodeTileN(n, p, j, data), + (video::S3DVertex *)buf->getVertices(), buf->getVertexCount(), + buf->getIndices(), buf->getIndexCount(), pos, c); + } + } else if (f.mesh_ptr[0]) { + // no cache, clone and rotate mesh + scene::IMesh* mesh = cloneMesh(f.mesh_ptr[0]); + rotateMeshBy6dFacedir(mesh, facedir); + recalculateBoundingBox(mesh); + meshmanip->recalculateNormals(mesh, true, false); + for(u16 j = 0; j < mesh->getMeshBufferCount(); j++) { + scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); + collector.append(getNodeTileN(n, p, j, data), + (video::S3DVertex *)buf->getVertices(), buf->getVertexCount(), + buf->getIndices(), buf->getIndexCount(), pos, c); + } + mesh->drop(); + } + break;} + } + } + + /* + Caused by incorrect alpha blending, selection mesh needs to be created as + last element to ensure it gets blended correct over nodes with alpha channel + */ + // Create selection mesh + v3s16 p = data->m_highlighted_pos_relative; + if (data->m_show_hud && + (p.X >= 0) && (p.X < MAP_BLOCKSIZE) && + (p.Y >= 0) && (p.Y < MAP_BLOCKSIZE) && + (p.Z >= 0) && (p.Z < MAP_BLOCKSIZE)) { + + MapNode n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p); + if(n.getContent() != CONTENT_AIR) { + // Get selection mesh light level + static const v3s16 dirs[7] = { + v3s16( 0, 0, 0), + v3s16( 0, 1, 0), + v3s16( 0,-1, 0), + v3s16( 1, 0, 0), + v3s16(-1, 0, 0), + v3s16( 0, 0, 1), + v3s16( 0, 0,-1) + }; + + u16 l = 0; + u16 l1 = 0; + for (u8 i = 0; i < 7; i++) { + MapNode n1 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p + dirs[i]); + l1 = getInteriorLight(n1, -4, nodedef); + if (l1 > l) + l = l1; + } + video::SColor c = MapBlock_LightColor(255, l, 0); + data->m_highlight_mesh_color = c; + std::vector boxes = n.getSelectionBoxes(nodedef); + TileSpec h_tile; + h_tile.material_flags |= MATERIAL_FLAG_HIGHLIGHTED; + h_tile.texture = tsrc->getTexture("halo.png",&h_tile.texture_id); + v3f pos = intToFloat(p, BS); + f32 d = 0.05 * BS; + for (std::vector::iterator i = boxes.begin(); + i != boxes.end(); i++) { + aabb3f box = *i; + box.MinEdge += v3f(-d, -d, -d) + pos; + box.MaxEdge += v3f(d, d, d) + pos; + makeCuboid(&collector, box, &h_tile, 1, c, NULL); + } + } + } +} + diff --git a/src/content_mapblock.h b/src/content_mapblock.h new file mode 100644 index 0000000..bb1e129 --- /dev/null +++ b/src/content_mapblock.h @@ -0,0 +1,29 @@ +/* +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. +*/ + +#ifndef CONTENT_MAPBLOCK_HEADER +#define CONTENT_MAPBLOCK_HEADER + +struct MeshMakeData; +struct MeshCollector; +void mapblock_mesh_generate_special(MeshMakeData *data, + MeshCollector &collector); + +#endif + diff --git a/src/content_mapnode.cpp b/src/content_mapnode.cpp new file mode 100644 index 0000000..44d0b8e --- /dev/null +++ b/src/content_mapnode.cpp @@ -0,0 +1,249 @@ +/* +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. +*/ + +#include "content_mapnode.h" + +#include "irrlichttypes_bloated.h" +#include "mapnode.h" +#include "nodedef.h" +#include "nameidmapping.h" +#include + +/* + Legacy node content type IDs + Ranges: + 0x000...0x07f (0...127): param2 is fully usable + 126 and 127 are reserved (CONTENT_AIR and CONTENT_IGNORE). + 0x800...0xfff (2048...4095): higher 4 bits of param2 are not usable +*/ +#define CONTENT_STONE 0 +#define CONTENT_WATER 2 +#define CONTENT_TORCH 3 +#define CONTENT_WATERSOURCE 9 +#define CONTENT_SIGN_WALL 14 +#define CONTENT_CHEST 15 +#define CONTENT_FURNACE 16 +#define CONTENT_LOCKABLE_CHEST 17 +#define CONTENT_FENCE 21 +#define CONTENT_RAIL 30 +#define CONTENT_LADDER 31 +#define CONTENT_LAVA 32 +#define CONTENT_LAVASOURCE 33 +#define CONTENT_GRASS 0x800 //1 +#define CONTENT_TREE 0x801 //4 +#define CONTENT_LEAVES 0x802 //5 +#define CONTENT_GRASS_FOOTSTEPS 0x803 //6 +#define CONTENT_MESE 0x804 //7 +#define CONTENT_MUD 0x805 //8 +#define CONTENT_CLOUD 0x806 //10 +#define CONTENT_COALSTONE 0x807 //11 +#define CONTENT_WOOD 0x808 //12 +#define CONTENT_SAND 0x809 //13 +#define CONTENT_COBBLE 0x80a //18 +#define CONTENT_STEEL 0x80b //19 +#define CONTENT_GLASS 0x80c //20 +#define CONTENT_MOSSYCOBBLE 0x80d //22 +#define CONTENT_GRAVEL 0x80e //23 +#define CONTENT_SANDSTONE 0x80f //24 +#define CONTENT_CACTUS 0x810 //25 +#define CONTENT_BRICK 0x811 //26 +#define CONTENT_CLAY 0x812 //27 +#define CONTENT_PAPYRUS 0x813 //28 +#define CONTENT_BOOKSHELF 0x814 //29 +#define CONTENT_JUNGLETREE 0x815 +#define CONTENT_JUNGLEGRASS 0x816 +#define CONTENT_NC 0x817 +#define CONTENT_NC_RB 0x818 +#define CONTENT_APPLE 0x819 +#define CONTENT_SAPLING 0x820 + +/* + A conversion table for backwards compatibility. + Maps <=v19 content types to current ones. + Should never be touched. +*/ +content_t trans_table_19[21][2] = { + {CONTENT_GRASS, 1}, + {CONTENT_TREE, 4}, + {CONTENT_LEAVES, 5}, + {CONTENT_GRASS_FOOTSTEPS, 6}, + {CONTENT_MESE, 7}, + {CONTENT_MUD, 8}, + {CONTENT_CLOUD, 10}, + {CONTENT_COALSTONE, 11}, + {CONTENT_WOOD, 12}, + {CONTENT_SAND, 13}, + {CONTENT_COBBLE, 18}, + {CONTENT_STEEL, 19}, + {CONTENT_GLASS, 20}, + {CONTENT_MOSSYCOBBLE, 22}, + {CONTENT_GRAVEL, 23}, + {CONTENT_SANDSTONE, 24}, + {CONTENT_CACTUS, 25}, + {CONTENT_BRICK, 26}, + {CONTENT_CLAY, 27}, + {CONTENT_PAPYRUS, 28}, + {CONTENT_BOOKSHELF, 29}, +}; + +MapNode mapnode_translate_to_internal(MapNode n_from, u8 version) +{ + MapNode result = n_from; + if(version <= 19) + { + content_t c_from = n_from.getContent(); + for(u32 i=0; iset(0, "default:stone"); + nimap->set(2, "default:water_flowing"); + nimap->set(3, "default:torch"); + nimap->set(9, "default:water_source"); + nimap->set(14, "default:sign_wall"); + nimap->set(15, "default:chest"); + nimap->set(16, "default:furnace"); + nimap->set(17, "default:chest_locked"); + nimap->set(21, "default:fence_wood"); + nimap->set(30, "default:rail"); + nimap->set(31, "default:ladder"); + nimap->set(32, "default:lava_flowing"); + nimap->set(33, "default:lava_source"); + nimap->set(0x800, "default:dirt_with_grass"); + nimap->set(0x801, "default:tree"); + nimap->set(0x802, "default:leaves"); + nimap->set(0x803, "default:dirt_with_grass_footsteps"); + nimap->set(0x804, "default:mese"); + nimap->set(0x805, "default:dirt"); + nimap->set(0x806, "default:cloud"); + nimap->set(0x807, "default:coalstone"); + nimap->set(0x808, "default:wood"); + nimap->set(0x809, "default:sand"); + nimap->set(0x80a, "default:cobble"); + nimap->set(0x80b, "default:steelblock"); + nimap->set(0x80c, "default:glass"); + nimap->set(0x80d, "default:mossycobble"); + nimap->set(0x80e, "default:gravel"); + nimap->set(0x80f, "default:sandstone"); + nimap->set(0x810, "default:cactus"); + nimap->set(0x811, "default:brick"); + nimap->set(0x812, "default:clay"); + nimap->set(0x813, "default:papyrus"); + nimap->set(0x814, "default:bookshelf"); + nimap->set(0x815, "default:jungletree"); + nimap->set(0x816, "default:junglegrass"); + nimap->set(0x817, "default:nyancat"); + nimap->set(0x818, "default:nyancat_rainbow"); + nimap->set(0x819, "default:apple"); + nimap->set(0x820, "default:sapling"); + // Static types + nimap->set(CONTENT_IGNORE, "ignore"); + nimap->set(CONTENT_AIR, "air"); +} + +class NewNameGetter +{ +public: + NewNameGetter() + { + old_to_new["CONTENT_STONE"] = "default:stone"; + old_to_new["CONTENT_WATER"] = "default:water_flowing"; + old_to_new["CONTENT_TORCH"] = "default:torch"; + old_to_new["CONTENT_WATERSOURCE"] = "default:water_source"; + old_to_new["CONTENT_SIGN_WALL"] = "default:sign_wall"; + old_to_new["CONTENT_CHEST"] = "default:chest"; + old_to_new["CONTENT_FURNACE"] = "default:furnace"; + old_to_new["CONTENT_LOCKABLE_CHEST"] = "default:locked_chest"; + old_to_new["CONTENT_FENCE"] = "default:wooden_fence"; + old_to_new["CONTENT_RAIL"] = "default:rail"; + old_to_new["CONTENT_LADDER"] = "default:ladder"; + old_to_new["CONTENT_LAVA"] = "default:lava_flowing"; + old_to_new["CONTENT_LAVASOURCE"] = "default:lava_source"; + old_to_new["CONTENT_GRASS"] = "default:dirt_with_grass"; + old_to_new["CONTENT_TREE"] = "default:tree"; + old_to_new["CONTENT_LEAVES"] = "default:leaves"; + old_to_new["CONTENT_GRASS_FOOTSTEPS"] = "default:dirt_with_grass_footsteps"; + old_to_new["CONTENT_MESE"] = "default:mese"; + old_to_new["CONTENT_MUD"] = "default:dirt"; + old_to_new["CONTENT_CLOUD"] = "default:cloud"; + old_to_new["CONTENT_COALSTONE"] = "default:coalstone"; + old_to_new["CONTENT_WOOD"] = "default:wood"; + old_to_new["CONTENT_SAND"] = "default:sand"; + old_to_new["CONTENT_COBBLE"] = "default:cobble"; + old_to_new["CONTENT_STEEL"] = "default:steel"; + old_to_new["CONTENT_GLASS"] = "default:glass"; + old_to_new["CONTENT_MOSSYCOBBLE"] = "default:mossycobble"; + old_to_new["CONTENT_GRAVEL"] = "default:gravel"; + old_to_new["CONTENT_SANDSTONE"] = "default:sandstone"; + old_to_new["CONTENT_CACTUS"] = "default:cactus"; + old_to_new["CONTENT_BRICK"] = "default:brick"; + old_to_new["CONTENT_CLAY"] = "default:clay"; + old_to_new["CONTENT_PAPYRUS"] = "default:papyrus"; + old_to_new["CONTENT_BOOKSHELF"] = "default:bookshelf"; + old_to_new["CONTENT_JUNGLETREE"] = "default:jungletree"; + old_to_new["CONTENT_JUNGLEGRASS"] = "default:junglegrass"; + old_to_new["CONTENT_NC"] = "default:nyancat"; + old_to_new["CONTENT_NC_RB"] = "default:nyancat_rainbow"; + old_to_new["CONTENT_APPLE"] = "default:apple"; + old_to_new["CONTENT_SAPLING"] = "default:sapling"; + // Just in case + old_to_new["CONTENT_IGNORE"] = "ignore"; + old_to_new["CONTENT_AIR"] = "air"; + } + std::string get(const std::string &old) + { + std::map::const_iterator i; + i = old_to_new.find(old); + if(i == old_to_new.end()) + return ""; + return i->second; + } +private: + std::map old_to_new; +}; + +NewNameGetter newnamegetter; + +std::string content_mapnode_get_new_name(const std::string &oldname) +{ + return newnamegetter.get(oldname); +} + +content_t legacy_get_id(const std::string &oldname, INodeDefManager *ndef) +{ + std::string newname = content_mapnode_get_new_name(oldname); + if(newname == "") + return CONTENT_IGNORE; + content_t id; + bool found = ndef->getId(newname, id); + if(!found) + return CONTENT_IGNORE; + return id; +} + diff --git a/src/content_mapnode.h b/src/content_mapnode.h new file mode 100644 index 0000000..5c9c0b6 --- /dev/null +++ b/src/content_mapnode.h @@ -0,0 +1,44 @@ +/* +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. +*/ + +#ifndef CONTENT_MAPNODE_HEADER +#define CONTENT_MAPNODE_HEADER + +#include "mapnode.h" + +/* + Legacy node definitions +*/ + +// Backwards compatibility for non-extended content types in v19 +extern content_t trans_table_19[21][2]; +MapNode mapnode_translate_to_internal(MapNode n_from, u8 version); + +// Get legacy node name mapping for loading old blocks +class NameIdMapping; +void content_mapnode_get_name_id_mapping(NameIdMapping *nimap); + +// Convert "CONTENT_STONE"-style names to dynamic ids +std::string content_mapnode_get_new_name(const std::string &oldname); +class INodeDefManager; +content_t legacy_get_id(const std::string &oldname, INodeDefManager *ndef); +#define LEGN(ndef, oldname) legacy_get_id(oldname, ndef) + +#endif + diff --git a/src/content_nodemeta.cpp b/src/content_nodemeta.cpp new file mode 100644 index 0000000..f504924 --- /dev/null +++ b/src/content_nodemeta.cpp @@ -0,0 +1,200 @@ +/* +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. +*/ + +#include "content_nodemeta.h" +#include "nodemetadata.h" +#include "nodetimer.h" +#include "inventory.h" +#include "log.h" +#include "serialization.h" +#include "util/serialize.h" +#include "util/string.h" +#include "constants.h" // MAP_BLOCKSIZE +#include + +#define NODEMETA_GENERIC 1 +#define NODEMETA_SIGN 14 +#define NODEMETA_CHEST 15 +#define NODEMETA_FURNACE 16 +#define NODEMETA_LOCKABLE_CHEST 17 + +// Returns true if node timer must be set +static bool content_nodemeta_deserialize_legacy_body( + std::istream &is, s16 id, NodeMetadata *meta) +{ + meta->clear(); + + if(id == NODEMETA_GENERIC) // GenericNodeMetadata (0.4-dev) + { + meta->getInventory()->deSerialize(is); + deSerializeLongString(is); // m_text + deSerializeString(is); // m_owner + + meta->setString("infotext",deSerializeString(is)); + meta->setString("formspec",deSerializeString(is)); + readU8(is); // m_allow_text_input + readU8(is); // m_allow_removal + readU8(is); // m_enforce_owner + + int num_vars = readU32(is); + for(int i=0; isetString(name, var); + } + return false; + } + else if(id == NODEMETA_SIGN) // SignNodeMetadata + { + meta->setString("text", deSerializeString(is)); + //meta->setString("infotext","\"${text}\""); + meta->setString("infotext", + std::string("\"") + meta->getString("text") + "\""); + meta->setString("formspec","field[text;;${text}]"); + return false; + } + else if(id == NODEMETA_CHEST) // ChestNodeMetadata + { + meta->getInventory()->deSerialize(is); + + // Rename inventory list "0" to "main" + Inventory *inv = meta->getInventory(); + if(!inv->getList("main") && inv->getList("0")){ + inv->getList("0")->setName("main"); + } + assert(inv->getList("main") && !inv->getList("0")); + + meta->setString("formspec","size[8,9]" + "list[current_name;main;0,0;8,4;]" + "list[current_player;main;0,5;8,4;]"); + return false; + } + else if(id == NODEMETA_LOCKABLE_CHEST) // LockingChestNodeMetadata + { + meta->setString("owner", deSerializeString(is)); + meta->getInventory()->deSerialize(is); + + // Rename inventory list "0" to "main" + Inventory *inv = meta->getInventory(); + if(!inv->getList("main") && inv->getList("0")){ + inv->getList("0")->setName("main"); + } + assert(inv->getList("main") && !inv->getList("0")); + + meta->setString("formspec","size[8,9]" + "list[current_name;main;0,0;8,4;]" + "list[current_player;main;0,5;8,4;]"); + return false; + } + else if(id == NODEMETA_FURNACE) // FurnaceNodeMetadata + { + meta->getInventory()->deSerialize(is); + int temp = 0; + is>>temp; + meta->setString("fuel_totaltime", ftos((float)temp/10)); + temp = 0; + is>>temp; + meta->setString("fuel_time", ftos((float)temp/10)); + temp = 0; + is>>temp; + //meta->setString("src_totaltime", ftos((float)temp/10)); + temp = 0; + is>>temp; + meta->setString("src_time", ftos((float)temp/10)); + + meta->setString("formspec","size[8,9]" + "list[current_name;fuel;2,3;1,1;]" + "list[current_name;src;2,1;1,1;]" + "list[current_name;dst;5,1;2,2;]" + "list[current_player;main;0,5;8,4;]"); + return true; + } + else + { + throw SerializationError("Unknown legacy node metadata"); + } +} + +static bool content_nodemeta_deserialize_legacy_meta( + std::istream &is, NodeMetadata *meta) +{ + // Read id + s16 id = readS16(is); + + // Read data + std::string data = deSerializeString(is); + std::istringstream tmp_is(data, std::ios::binary); + return content_nodemeta_deserialize_legacy_body(tmp_is, id, meta); +} + +void content_nodemeta_deserialize_legacy(std::istream &is, + NodeMetadataList *meta, NodeTimerList *timers, + IGameDef *gamedef) +{ + meta->clear(); + timers->clear(); + + u16 version = readU16(is); + + if(version > 1) + { + infostream<<__FUNCTION_NAME<<": version "<get(p) != NULL) + { + infostream<<"WARNING: "<<__FUNCTION_NAME<<": " + <<"already set data at position" + <<"("<set(p, data); + + if(need_timer) + timers->set(p, NodeTimer(1., 0.)); + } +} + +void content_nodemeta_serialize_legacy(std::ostream &os, NodeMetadataList *meta) +{ + // Sorry, I was too lazy to implement this. --kahrl + writeU16(os, 1); // version + writeU16(os, 0); // count +} + +// END diff --git a/src/content_nodemeta.h b/src/content_nodemeta.h new file mode 100644 index 0000000..0b08bc6 --- /dev/null +++ b/src/content_nodemeta.h @@ -0,0 +1,40 @@ +/* +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. +*/ + +#ifndef CONTENT_NODEMETA_HEADER +#define CONTENT_NODEMETA_HEADER + +#include + +class NodeMetadataList; +class NodeTimerList; +class IGameDef; + +/* + Legacy nodemeta definitions +*/ + +void content_nodemeta_deserialize_legacy(std::istream &is, + NodeMetadataList *meta, NodeTimerList *timers, + IGameDef *gamedef); + +void content_nodemeta_serialize_legacy(std::ostream &os, NodeMetadataList *meta); + +#endif + diff --git a/src/content_sao.cpp b/src/content_sao.cpp new file mode 100644 index 0000000..e58aa4b --- /dev/null +++ b/src/content_sao.cpp @@ -0,0 +1,1259 @@ +/* +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. +*/ + +#include "content_sao.h" +#include "util/serialize.h" +#include "util/mathconstants.h" +#include "collision.h" +#include "environment.h" +#include "settings.h" +#include "main.h" // For g_profiler +#include "profiler.h" +#include "serialization.h" // For compressZlib +#include "tool.h" // For ToolCapabilities +#include "gamedef.h" +#include "player.h" +#include "server.h" +#include "scripting_game.h" +#include "genericobject.h" +#include "log.h" + +std::map ServerActiveObject::m_types; + +/* + TestSAO +*/ + +class TestSAO : public ServerActiveObject +{ +public: + TestSAO(ServerEnvironment *env, v3f pos): + ServerActiveObject(env, pos), + m_timer1(0), + m_age(0) + { + ServerActiveObject::registerType(getType(), create); + } + ActiveObjectType getType() const + { return ACTIVEOBJECT_TYPE_TEST; } + + static ServerActiveObject* create(ServerEnvironment *env, v3f pos, + const std::string &data) + { + return new TestSAO(env, pos); + } + + void step(float dtime, bool send_recommended) + { + m_age += dtime; + if(m_age > 10) + { + m_removed = true; + return; + } + + m_base_position.Y += dtime * BS * 2; + if(m_base_position.Y > 8*BS) + m_base_position.Y = 2*BS; + + if(send_recommended == false) + return; + + m_timer1 -= dtime; + if(m_timer1 < 0.0) + { + m_timer1 += 0.125; + + std::string data; + + data += itos(0); // 0 = position + data += " "; + data += itos(m_base_position.X); + data += " "; + data += itos(m_base_position.Y); + data += " "; + data += itos(m_base_position.Z); + + ActiveObjectMessage aom(getId(), false, data); + m_messages_out.push(aom); + } + } + + bool getCollisionBox(aabb3f *toset) { + return false; + } + + bool collideWithObjects() { + return false; + } + +private: + float m_timer1; + float m_age; +}; + +// Prototype (registers item for deserialization) +TestSAO proto_TestSAO(NULL, v3f(0,0,0)); + +/* + LuaEntitySAO +*/ + +// Prototype (registers item for deserialization) +LuaEntitySAO proto_LuaEntitySAO(NULL, v3f(0,0,0), "_prototype", ""); + +LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, + const std::string &name, const std::string &state): + ServerActiveObject(env, pos), + m_init_name(name), + m_init_state(state), + m_registered(false), + m_hp(-1), + m_velocity(0,0,0), + m_acceleration(0,0,0), + m_yaw(0), + m_properties_sent(true), + m_last_sent_yaw(0), + m_last_sent_position(0,0,0), + m_last_sent_velocity(0,0,0), + m_last_sent_position_timer(0), + m_last_sent_move_precision(0), + m_armor_groups_sent(false), + m_animation_speed(0), + m_animation_blend(0), + m_animation_sent(false), + m_bone_position_sent(false), + m_attachment_parent_id(0), + m_attachment_sent(false) +{ + // Only register type if no environment supplied + if(env == NULL){ + ServerActiveObject::registerType(getType(), create); + return; + } + + // Initialize something to armor groups + m_armor_groups["fleshy"] = 100; +} + +LuaEntitySAO::~LuaEntitySAO() +{ + if(m_registered){ + m_env->getScriptIface()->luaentity_Remove(m_id); + } +} + +void LuaEntitySAO::addedToEnvironment(u32 dtime_s) +{ + ServerActiveObject::addedToEnvironment(dtime_s); + + // Create entity from name + m_registered = m_env->getScriptIface()-> + luaentity_Add(m_id, m_init_name.c_str()); + + if(m_registered){ + // Get properties + m_env->getScriptIface()-> + luaentity_GetProperties(m_id, &m_prop); + // Initialize HP from properties + m_hp = m_prop.hp_max; + // Activate entity, supplying serialized state + m_env->getScriptIface()-> + luaentity_Activate(m_id, m_init_state.c_str(), dtime_s); + } +} + +ServerActiveObject* LuaEntitySAO::create(ServerEnvironment *env, v3f pos, + const std::string &data) +{ + std::string name; + std::string state; + s16 hp = 1; + v3f velocity; + float yaw = 0; + if(data != ""){ + std::istringstream is(data, std::ios::binary); + // read version + u8 version = readU8(is); + // check if version is supported + if(version == 0){ + name = deSerializeString(is); + state = deSerializeLongString(is); + } + else if(version == 1){ + name = deSerializeString(is); + state = deSerializeLongString(is); + hp = readS16(is); + velocity = readV3F1000(is); + yaw = readF1000(is); + } + } + // create object + infostream<<"LuaEntitySAO::create(name=\""<m_hp = hp; + sao->m_velocity = velocity; + sao->m_yaw = yaw; + return sao; +} + +bool LuaEntitySAO::isAttached() +{ + if(!m_attachment_parent_id) + return false; + // Check if the parent still exists + ServerActiveObject *obj = m_env->getActiveObject(m_attachment_parent_id); + if(obj) + return true; + return false; +} + +void LuaEntitySAO::step(float dtime, bool send_recommended) +{ + if(!m_properties_sent) + { + m_properties_sent = true; + std::string str = getPropertyPacket(); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } + + // If attached, check that our parent is still there. If it isn't, detach. + if(m_attachment_parent_id && !isAttached()) + { + m_attachment_parent_id = 0; + m_attachment_bone = ""; + m_attachment_position = v3f(0,0,0); + m_attachment_rotation = v3f(0,0,0); + sendPosition(false, true); + } + + m_last_sent_position_timer += dtime; + + // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally + // If the object gets detached this comes into effect automatically from the last known origin + if(isAttached()) + { + v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition(); + m_base_position = pos; + m_velocity = v3f(0,0,0); + m_acceleration = v3f(0,0,0); + } + else + { + if(m_prop.physical){ + core::aabbox3d box = m_prop.collisionbox; + box.MinEdge *= BS; + box.MaxEdge *= BS; + collisionMoveResult moveresult; + f32 pos_max_d = BS*0.25; // Distance per iteration + v3f p_pos = m_base_position; + v3f p_velocity = m_velocity; + v3f p_acceleration = m_acceleration; + moveresult = collisionMoveSimple(m_env,m_env->getGameDef(), + pos_max_d, box, m_prop.stepheight, dtime, + p_pos, p_velocity, p_acceleration, + this, m_prop.collideWithObjects); + + // Apply results + m_base_position = p_pos; + m_velocity = p_velocity; + m_acceleration = p_acceleration; + } else { + m_base_position += dtime * m_velocity + 0.5 * dtime + * dtime * m_acceleration; + m_velocity += dtime * m_acceleration; + } + + if((m_prop.automatic_face_movement_dir) && + (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)){ + m_yaw = atan2(m_velocity.Z,m_velocity.X) * 180 / M_PI + m_prop.automatic_face_movement_dir_offset; + } + } + + if(m_registered){ + m_env->getScriptIface()->luaentity_Step(m_id, dtime); + } + + if(send_recommended == false) + return; + + if(!isAttached()) + { + // TODO: force send when acceleration changes enough? + float minchange = 0.2*BS; + if(m_last_sent_position_timer > 1.0){ + minchange = 0.01*BS; + } else if(m_last_sent_position_timer > 0.2){ + minchange = 0.05*BS; + } + float move_d = m_base_position.getDistanceFrom(m_last_sent_position); + move_d += m_last_sent_move_precision; + float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity); + if(move_d > minchange || vel_d > minchange || + fabs(m_yaw - m_last_sent_yaw) > 1.0){ + sendPosition(true, false); + } + } + + if(m_armor_groups_sent == false){ + m_armor_groups_sent = true; + std::string str = gob_cmd_update_armor_groups( + m_armor_groups); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } + + if(m_animation_sent == false){ + m_animation_sent = true; + std::string str = gob_cmd_update_animation(m_animation_range, m_animation_speed, m_animation_blend); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } + + if(m_bone_position_sent == false){ + m_bone_position_sent = true; + for(std::map >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ + std::string str = gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } + } + + if(m_attachment_sent == false){ + m_attachment_sent = true; + std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } +} + +std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) +{ + std::ostringstream os(std::ios::binary); + + if(protocol_version >= 14) + { + writeU8(os, 1); // version + os< >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ + os<getScriptIface()-> + luaentity_GetStaticdata(m_id); + os<getWieldedItem(); + punchitem = &punchitem_static; + } + + PunchDamageResult result = getPunchDamage( + m_armor_groups, + toolcap, + punchitem, + time_from_last_punch); + + if(result.did_punch) + { + setHP(getHP() - result.damage); + + + std::string punchername = "nil"; + + if ( puncher != 0 ) + punchername = puncher->getDescription(); + + actionstream<getScriptIface()->luaentity_Punch(m_id, puncher, + time_from_last_punch, toolcap, dir); + + return result.wear; +} + +void LuaEntitySAO::rightClick(ServerActiveObject *clicker) +{ + if(!m_registered) + return; + // It's best that attachments cannot be clicked + if(isAttached()) + return; + m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker); +} + +void LuaEntitySAO::setPos(v3f pos) +{ + if(isAttached()) + return; + m_base_position = pos; + sendPosition(false, true); +} + +void LuaEntitySAO::moveTo(v3f pos, bool continuous) +{ + if(isAttached()) + return; + m_base_position = pos; + if(!continuous) + sendPosition(true, true); +} + +float LuaEntitySAO::getMinimumSavedMovement() +{ + return 0.1 * BS; +} + +std::string LuaEntitySAO::getDescription() +{ + std::ostringstream os(std::ios::binary); + os<<"LuaEntitySAO at ("; + os<<(m_base_position.X/BS)<<","; + os<<(m_base_position.Y/BS)<<","; + os<<(m_base_position.Z/BS); + os<<")"; + return os.str(); +} + +void LuaEntitySAO::setHP(s16 hp) +{ + if(hp < 0) hp = 0; + m_hp = hp; +} + +s16 LuaEntitySAO::getHP() const +{ + return m_hp; +} + +void LuaEntitySAO::setArmorGroups(const ItemGroupList &armor_groups) +{ + m_armor_groups = armor_groups; + m_armor_groups_sent = false; +} + +void LuaEntitySAO::setAnimation(v2f frame_range, float frame_speed, float frame_blend) +{ + m_animation_range = frame_range; + m_animation_speed = frame_speed; + m_animation_blend = frame_blend; + m_animation_sent = false; +} + +void LuaEntitySAO::setBonePosition(std::string bone, v3f position, v3f rotation) +{ + m_bone_position[bone] = core::vector2d(position, rotation); + m_bone_position_sent = false; +} + +void LuaEntitySAO::setAttachment(int parent_id, std::string bone, v3f position, v3f rotation) +{ + // Attachments need to be handled on both the server and client. + // If we just attach on the server, we can only copy the position of the parent. Attachments + // are still sent to clients at an interval so players might see them lagging, plus we can't + // read and attach to skeletal bones. + // If we just attach on the client, the server still sees the child at its original location. + // This breaks some things so we also give the server the most accurate representation + // even if players only see the client changes. + + m_attachment_parent_id = parent_id; + m_attachment_bone = bone; + m_attachment_position = position; + m_attachment_rotation = rotation; + m_attachment_sent = false; +} + +ObjectProperties* LuaEntitySAO::accessObjectProperties() +{ + return &m_prop; +} + +void LuaEntitySAO::notifyObjectPropertiesModified() +{ + m_properties_sent = false; +} + +void LuaEntitySAO::setVelocity(v3f velocity) +{ + m_velocity = velocity; +} + +v3f LuaEntitySAO::getVelocity() +{ + return m_velocity; +} + +void LuaEntitySAO::setAcceleration(v3f acceleration) +{ + m_acceleration = acceleration; +} + +v3f LuaEntitySAO::getAcceleration() +{ + return m_acceleration; +} + +void LuaEntitySAO::setYaw(float yaw) +{ + m_yaw = yaw; +} + +float LuaEntitySAO::getYaw() +{ + return m_yaw; +} + +void LuaEntitySAO::setTextureMod(const std::string &mod) +{ + std::string str = gob_cmd_set_texture_mod(mod); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); +} + +void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength, + bool select_horiz_by_yawpitch) +{ + std::string str = gob_cmd_set_sprite( + p, + num_frames, + framelength, + select_horiz_by_yawpitch + ); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); +} + +std::string LuaEntitySAO::getName() +{ + return m_init_name; +} + +std::string LuaEntitySAO::getPropertyPacket() +{ + return gob_cmd_set_properties(m_prop); +} + +void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) +{ + // If the object is attached client-side, don't waste bandwidth sending its position to clients + if(isAttached()) + return; + + m_last_sent_move_precision = m_base_position.getDistanceFrom( + m_last_sent_position); + m_last_sent_position_timer = 0; + m_last_sent_yaw = m_yaw; + m_last_sent_position = m_base_position; + m_last_sent_velocity = m_velocity; + //m_last_sent_acceleration = m_acceleration; + + float update_interval = m_env->getSendRecommendedInterval(); + + std::string str = gob_cmd_update_position( + m_base_position, + m_velocity, + m_acceleration, + m_yaw, + do_interpolate, + is_movement_end, + update_interval + ); + // create message and add to list + ActiveObjectMessage aom(getId(), false, str); + m_messages_out.push(aom); +} + +bool LuaEntitySAO::getCollisionBox(aabb3f *toset) { + if (m_prop.physical) + { + //update collision box + toset->MinEdge = m_prop.collisionbox.MinEdge * BS; + toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; + + toset->MinEdge += m_base_position; + toset->MaxEdge += m_base_position; + + return true; + } + + return false; +} + +bool LuaEntitySAO::collideWithObjects(){ + return m_prop.collideWithObjects; +} + +/* + PlayerSAO +*/ + +// No prototype, PlayerSAO does not need to be deserialized + +PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_, + const std::set &privs, bool is_singleplayer): + ServerActiveObject(env_, v3f(0,0,0)), + m_player(player_), + m_peer_id(peer_id_), + m_inventory(NULL), + m_damage(0), + m_last_good_position(0,0,0), + m_time_from_last_punch(0), + m_nocheat_dig_pos(32767, 32767, 32767), + m_nocheat_dig_time(0), + m_wield_index(0), + m_position_not_sent(false), + m_armor_groups_sent(false), + m_properties_sent(true), + m_privs(privs), + m_is_singleplayer(is_singleplayer), + m_animation_speed(0), + m_animation_blend(0), + m_animation_sent(false), + m_bone_position_sent(false), + m_attachment_parent_id(0), + m_attachment_sent(false), + // public + m_physics_override_speed(1), + m_physics_override_jump(1), + m_physics_override_gravity(1), + m_physics_override_sneak(true), + m_physics_override_sneak_glitch(true), + m_physics_override_sent(false) +{ + assert(m_player); // pre-condition + assert(m_peer_id != 0); // pre-condition + setBasePosition(m_player->getPosition()); + m_inventory = &m_player->inventory; + m_armor_groups["fleshy"] = 100; + + m_prop.hp_max = PLAYER_MAX_HP; + m_prop.physical = false; + m_prop.weight = 75; + m_prop.collisionbox = core::aabbox3d(-1/3.,-1.0,-1/3., 1/3.,1.0,1/3.); + // start of default appearance, this should be overwritten by LUA + m_prop.visual = "upright_sprite"; + m_prop.visual_size = v2f(1, 2); + m_prop.textures.clear(); + m_prop.textures.push_back("player.png"); + m_prop.textures.push_back("player_back.png"); + m_prop.colors.clear(); + m_prop.colors.push_back(video::SColor(255, 255, 255, 255)); + m_prop.spritediv = v2s16(1,1); + // end of default appearance + m_prop.is_visible = true; + m_prop.makes_footstep_sound = true; +} + +PlayerSAO::~PlayerSAO() +{ + if(m_inventory != &m_player->inventory) + delete m_inventory; + +} + +std::string PlayerSAO::getDescription() +{ + return std::string("player ") + m_player->getName(); +} + +// Called after id has been set and has been inserted in environment +void PlayerSAO::addedToEnvironment(u32 dtime_s) +{ + ServerActiveObject::addedToEnvironment(dtime_s); + ServerActiveObject::setBasePosition(m_player->getPosition()); + m_player->setPlayerSAO(this); + m_player->peer_id = m_peer_id; + m_last_good_position = m_player->getPosition(); +} + +// Called before removing from environment +void PlayerSAO::removingFromEnvironment() +{ + ServerActiveObject::removingFromEnvironment(); + if(m_player->getPlayerSAO() == this) + { + m_player->setPlayerSAO(NULL); + m_player->peer_id = 0; + m_env->savePlayer(m_player->getName()); + m_env->removePlayer(m_player->getName()); + } +} + +bool PlayerSAO::isStaticAllowed() const +{ + return false; +} + +std::string PlayerSAO::getClientInitializationData(u16 protocol_version) +{ + std::ostringstream os(std::ios::binary); + + if(protocol_version >= 15) + { + writeU8(os, 1); // version + os<getName()); // name + writeU8(os, 1); // is_player + writeS16(os, getId()); //id + writeV3F1000(os, m_player->getPosition() + v3f(0,BS*1,0)); + writeF1000(os, m_player->getYaw()); + writeS16(os, getHP()); + + writeU8(os, 5 + m_bone_position.size()); // number of messages stuffed in here + os< >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ + os<getName()); // name + writeU8(os, 1); // is_player + writeV3F1000(os, m_player->getPosition() + v3f(0,BS*1,0)); + writeF1000(os, m_player->getYaw()); + writeS16(os, getHP()); + writeU8(os, 2); // number of messages stuffed in here + os<getActiveObject(m_attachment_parent_id); + if(obj) + return true; + return false; +} + +void PlayerSAO::step(float dtime, bool send_recommended) +{ + if(!m_properties_sent) + { + m_properties_sent = true; + std::string str = getPropertyPacket(); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } + + // If attached, check that our parent is still there. If it isn't, detach. + if(m_attachment_parent_id && !isAttached()) + { + m_attachment_parent_id = 0; + m_attachment_bone = ""; + m_attachment_position = v3f(0,0,0); + m_attachment_rotation = v3f(0,0,0); + m_player->setPosition(m_last_good_position); + ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); + } + + //dstream<<"PlayerSAO::step: dtime: "<getMaxLagEstimate() * 2.0; + if(lag_pool_max < LAG_POOL_MIN) + lag_pool_max = LAG_POOL_MIN; + m_dig_pool.setMax(lag_pool_max); + m_move_pool.setMax(lag_pool_max); + + // Increment cheat prevention timers + m_dig_pool.add(dtime); + m_move_pool.add(dtime); + m_time_from_last_punch += dtime; + m_nocheat_dig_time += dtime; + + // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally + // If the object gets detached this comes into effect automatically from the last known origin + if(isAttached()) + { + v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition(); + m_last_good_position = pos; + m_player->setPosition(pos); + } + + if(send_recommended == false) + return; + + // If the object is attached client-side, don't waste bandwidth sending its position to clients + if(m_position_not_sent && !isAttached()) + { + m_position_not_sent = false; + float update_interval = m_env->getSendRecommendedInterval(); + v3f pos; + if(isAttached()) // Just in case we ever do send attachment position too + pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition(); + else + pos = m_player->getPosition() + v3f(0,BS*1,0); + std::string str = gob_cmd_update_position( + pos, + v3f(0,0,0), + v3f(0,0,0), + m_player->getYaw(), + true, + false, + update_interval + ); + // create message and add to list + ActiveObjectMessage aom(getId(), false, str); + m_messages_out.push(aom); + } + + if(m_armor_groups_sent == false) { + m_armor_groups_sent = true; + std::string str = gob_cmd_update_armor_groups( + m_armor_groups); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } + + if(m_physics_override_sent == false){ + m_physics_override_sent = true; + std::string str = gob_cmd_update_physics_override(m_physics_override_speed, + m_physics_override_jump, m_physics_override_gravity, + m_physics_override_sneak, m_physics_override_sneak_glitch); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } + + if(m_animation_sent == false){ + m_animation_sent = true; + std::string str = gob_cmd_update_animation(m_animation_range, m_animation_speed, m_animation_blend); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } + + if(m_bone_position_sent == false){ + m_bone_position_sent = true; + for(std::map >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ + std::string str = gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } + } + + if(m_attachment_sent == false){ + m_attachment_sent = true; + std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } +} + +void PlayerSAO::setBasePosition(const v3f &position) +{ + // This needs to be ran for attachments too + ServerActiveObject::setBasePosition(position); + m_position_not_sent = true; +} + +void PlayerSAO::setPos(v3f pos) +{ + if(isAttached()) + return; + m_player->setPosition(pos); + // Movement caused by this command is always valid + m_last_good_position = pos; + ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); +} + +void PlayerSAO::moveTo(v3f pos, bool continuous) +{ + if(isAttached()) + return; + m_player->setPosition(pos); + // Movement caused by this command is always valid + m_last_good_position = pos; + ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); +} + +void PlayerSAO::setYaw(float yaw) +{ + m_player->setYaw(yaw); + ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); +} + +void PlayerSAO::setPitch(float pitch) +{ + m_player->setPitch(pitch); + ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); +} + +int PlayerSAO::punch(v3f dir, + const ToolCapabilities *toolcap, + ServerActiveObject *puncher, + float time_from_last_punch) +{ + // It's best that attachments cannot be punched + if(isAttached()) + return 0; + + if(!toolcap) + return 0; + + // No effect if PvP disabled + if(g_settings->getBool("enable_pvp") == false){ + if(puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER){ + std::string str = gob_cmd_punched(0, getHP()); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + return 0; + } + } + + HitParams hitparams = getHitParams(m_armor_groups, toolcap, + time_from_last_punch); + + std::string punchername = "nil"; + + if ( puncher != 0 ) + punchername = puncher->getDescription(); + + actionstream<<"Player "<getName()<<" punched by " + <hp; +} + +s16 PlayerSAO::readDamage() +{ + s16 damage = m_damage; + m_damage = 0; + return damage; +} + +void PlayerSAO::setHP(s16 hp) +{ + s16 oldhp = m_player->hp; + + if (hp < 0) + hp = 0; + else if (hp > PLAYER_MAX_HP) + hp = PLAYER_MAX_HP; + + m_player->hp = hp; + + if (oldhp > hp) + m_damage += (oldhp - hp); + + // Update properties on death + if ((hp == 0) != (oldhp == 0)) + m_properties_sent = false; +} + +u16 PlayerSAO::getBreath() const +{ + return m_player->getBreath(); +} + +void PlayerSAO::setBreath(u16 breath) +{ + m_player->setBreath(breath); +} + +void PlayerSAO::setArmorGroups(const ItemGroupList &armor_groups) +{ + m_armor_groups = armor_groups; + m_armor_groups_sent = false; +} + +void PlayerSAO::setAnimation(v2f frame_range, float frame_speed, float frame_blend) +{ + // store these so they can be updated to clients + m_animation_range = frame_range; + m_animation_speed = frame_speed; + m_animation_blend = frame_blend; + m_animation_sent = false; +} + +void PlayerSAO::setBonePosition(std::string bone, v3f position, v3f rotation) +{ + // store these so they can be updated to clients + m_bone_position[bone] = core::vector2d(position, rotation); + m_bone_position_sent = false; +} + +void PlayerSAO::setAttachment(int parent_id, std::string bone, v3f position, v3f rotation) +{ + // Attachments need to be handled on both the server and client. + // If we just attach on the server, we can only copy the position of the parent. Attachments + // are still sent to clients at an interval so players might see them lagging, plus we can't + // read and attach to skeletal bones. + // If we just attach on the client, the server still sees the child at its original location. + // This breaks some things so we also give the server the most accurate representation + // even if players only see the client changes. + + m_attachment_parent_id = parent_id; + m_attachment_bone = bone; + m_attachment_position = position; + m_attachment_rotation = rotation; + m_attachment_sent = false; +} + +ObjectProperties* PlayerSAO::accessObjectProperties() +{ + return &m_prop; +} + +void PlayerSAO::notifyObjectPropertiesModified() +{ + m_properties_sent = false; +} + +Inventory* PlayerSAO::getInventory() +{ + return m_inventory; +} +const Inventory* PlayerSAO::getInventory() const +{ + return m_inventory; +} + +InventoryLocation PlayerSAO::getInventoryLocation() const +{ + InventoryLocation loc; + loc.setPlayer(m_player->getName()); + return loc; +} + +std::string PlayerSAO::getWieldList() const +{ + return "main"; +} + +int PlayerSAO::getWieldIndex() const +{ + return m_wield_index; +} + +void PlayerSAO::setWieldIndex(int i) +{ + if(i != m_wield_index) { + m_wield_index = i; + } +} + +void PlayerSAO::disconnected() +{ + m_peer_id = 0; + m_removed = true; + if(m_player->getPlayerSAO() == this) + { + m_player->setPlayerSAO(NULL); + m_player->peer_id = 0; + } +} + +std::string PlayerSAO::getPropertyPacket() +{ + m_prop.is_visible = (true); + return gob_cmd_set_properties(m_prop); +} + +bool PlayerSAO::checkMovementCheat() +{ + bool cheated = false; + if(isAttached() || m_is_singleplayer || + g_settings->getBool("disable_anticheat")) + { + m_last_good_position = m_player->getPosition(); + } + else + { + /* + Check player movements + + NOTE: Actually the server should handle player physics like the + client does and compare player's position to what is calculated + on our side. This is required when eg. players fly due to an + explosion. Altough a node-based alternative might be possible + too, and much more lightweight. + */ + + float player_max_speed = 0; + if(m_privs.count("fast") != 0){ + // Fast speed + player_max_speed = m_player->movement_speed_fast; + } else { + // Normal speed + player_max_speed = m_player->movement_speed_walk; + } + // Tolerance. With the lag pool we shouldn't need it. + //player_max_speed *= 2.5; + //player_max_speed_up *= 2.5; + + v3f diff = (m_player->getPosition() - m_last_good_position); + float d_vert = diff.Y; + diff.Y = 0; + float d_horiz = diff.getLength(); + float required_time = d_horiz/player_max_speed; + if(d_vert > 0 && d_vert/player_max_speed > required_time) + required_time = d_vert/player_max_speed; + if(m_move_pool.grab(required_time)){ + m_last_good_position = m_player->getPosition(); + } else { + actionstream<<"Player "<getName() + <<" moved too fast; resetting position" + <setPosition(m_last_good_position); + cheated = true; + } + } + return cheated; +} + +bool PlayerSAO::getCollisionBox(aabb3f *toset) { + //update collision box + *toset = m_player->getCollisionbox(); + + toset->MinEdge += m_base_position; + toset->MaxEdge += m_base_position; + + return true; +} + +bool PlayerSAO::collideWithObjects(){ + return true; +} + diff --git a/src/content_sao.h b/src/content_sao.h new file mode 100644 index 0000000..cc372ff --- /dev/null +++ b/src/content_sao.h @@ -0,0 +1,326 @@ +/* +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. +*/ + +#ifndef CONTENT_SAO_HEADER +#define CONTENT_SAO_HEADER + +#include "serverobject.h" +#include "itemgroup.h" +#include "player.h" +#include "object_properties.h" + +/* + LuaEntitySAO needs some internals exposed. +*/ + +class LuaEntitySAO : public ServerActiveObject +{ +public: + LuaEntitySAO(ServerEnvironment *env, v3f pos, + const std::string &name, const std::string &state); + ~LuaEntitySAO(); + ActiveObjectType getType() const + { return ACTIVEOBJECT_TYPE_LUAENTITY; } + ActiveObjectType getSendType() const + { return ACTIVEOBJECT_TYPE_GENERIC; } + virtual void addedToEnvironment(u32 dtime_s); + static ServerActiveObject* create(ServerEnvironment *env, v3f pos, + const std::string &data); + bool isAttached(); + void step(float dtime, bool send_recommended); + std::string getClientInitializationData(u16 protocol_version); + std::string getStaticData(); + int punch(v3f dir, + const ToolCapabilities *toolcap=NULL, + ServerActiveObject *puncher=NULL, + float time_from_last_punch=1000000); + void rightClick(ServerActiveObject *clicker); + void setPos(v3f pos); + void moveTo(v3f pos, bool continuous); + float getMinimumSavedMovement(); + std::string getDescription(); + void setHP(s16 hp); + s16 getHP() const; + void setArmorGroups(const ItemGroupList &armor_groups); + void setAnimation(v2f frame_range, float frame_speed, float frame_blend); + void setBonePosition(std::string bone, v3f position, v3f rotation); + void setAttachment(int parent_id, std::string bone, v3f position, v3f rotation); + ObjectProperties* accessObjectProperties(); + void notifyObjectPropertiesModified(); + /* LuaEntitySAO-specific */ + void setVelocity(v3f velocity); + v3f getVelocity(); + void setAcceleration(v3f acceleration); + v3f getAcceleration(); + void setYaw(float yaw); + float getYaw(); + void setTextureMod(const std::string &mod); + void setSprite(v2s16 p, int num_frames, float framelength, + bool select_horiz_by_yawpitch); + std::string getName(); + bool getCollisionBox(aabb3f *toset); + bool collideWithObjects(); +private: + std::string getPropertyPacket(); + void sendPosition(bool do_interpolate, bool is_movement_end); + + std::string m_init_name; + std::string m_init_state; + bool m_registered; + struct ObjectProperties m_prop; + + s16 m_hp; + v3f m_velocity; + v3f m_acceleration; + float m_yaw; + ItemGroupList m_armor_groups; + + bool m_properties_sent; + float m_last_sent_yaw; + v3f m_last_sent_position; + v3f m_last_sent_velocity; + float m_last_sent_position_timer; + float m_last_sent_move_precision; + bool m_armor_groups_sent; + + v2f m_animation_range; + float m_animation_speed; + float m_animation_blend; + bool m_animation_sent; + + std::map > m_bone_position; + bool m_bone_position_sent; + + int m_attachment_parent_id; + std::string m_attachment_bone; + v3f m_attachment_position; + v3f m_attachment_rotation; + bool m_attachment_sent; +}; + +/* + PlayerSAO needs some internals exposed. +*/ + +class LagPool +{ + float m_pool; + float m_max; +public: + LagPool(): m_pool(15), m_max(15) + {} + void setMax(float new_max) + { + m_max = new_max; + if(m_pool > new_max) + m_pool = new_max; + } + void add(float dtime) + { + m_pool -= dtime; + if(m_pool < 0) + m_pool = 0; + } + bool grab(float dtime) + { + if(dtime <= 0) + return true; + if(m_pool + dtime > m_max) + return false; + m_pool += dtime; + return true; + } +}; + +class PlayerSAO : public ServerActiveObject +{ +public: + PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_, + const std::set &privs, bool is_singleplayer); + ~PlayerSAO(); + ActiveObjectType getType() const + { return ACTIVEOBJECT_TYPE_PLAYER; } + ActiveObjectType getSendType() const + { return ACTIVEOBJECT_TYPE_GENERIC; } + std::string getDescription(); + + /* + Active object <-> environment interface + */ + + void addedToEnvironment(u32 dtime_s); + void removingFromEnvironment(); + bool isStaticAllowed() const; + std::string getClientInitializationData(u16 protocol_version); + std::string getStaticData(); + bool isAttached(); + void step(float dtime, bool send_recommended); + void setBasePosition(const v3f &position); + void setPos(v3f pos); + void moveTo(v3f pos, bool continuous); + void setYaw(float); + void setPitch(float); + + /* + Interaction interface + */ + + int punch(v3f dir, + const ToolCapabilities *toolcap, + ServerActiveObject *puncher, + float time_from_last_punch); + void rightClick(ServerActiveObject *clicker); + s16 getHP() const; + void setHP(s16 hp); + s16 readDamage(); + u16 getBreath() const; + void setBreath(u16 breath); + void setArmorGroups(const ItemGroupList &armor_groups); + void setAnimation(v2f frame_range, float frame_speed, float frame_blend); + void setBonePosition(std::string bone, v3f position, v3f rotation); + void setAttachment(int parent_id, std::string bone, v3f position, v3f rotation); + ObjectProperties* accessObjectProperties(); + void notifyObjectPropertiesModified(); + + /* + Inventory interface + */ + + Inventory* getInventory(); + const Inventory* getInventory() const; + InventoryLocation getInventoryLocation() const; + std::string getWieldList() const; + int getWieldIndex() const; + void setWieldIndex(int i); + + /* + PlayerSAO-specific + */ + + void disconnected(); + + Player* getPlayer() + { + return m_player; + } + u16 getPeerID() const + { + return m_peer_id; + } + + // Cheat prevention + + v3f getLastGoodPosition() const + { + return m_last_good_position; + } + float resetTimeFromLastPunch() + { + float r = m_time_from_last_punch; + m_time_from_last_punch = 0.0; + return r; + } + void noCheatDigStart(v3s16 p) + { + m_nocheat_dig_pos = p; + m_nocheat_dig_time = 0; + } + v3s16 getNoCheatDigPos() + { + return m_nocheat_dig_pos; + } + float getNoCheatDigTime() + { + return m_nocheat_dig_time; + } + void noCheatDigEnd() + { + m_nocheat_dig_pos = v3s16(32767, 32767, 32767); + } + LagPool& getDigPool() + { + return m_dig_pool; + } + // Returns true if cheated + bool checkMovementCheat(); + + // Other + + void updatePrivileges(const std::set &privs, + bool is_singleplayer) + { + m_privs = privs; + m_is_singleplayer = is_singleplayer; + } + + bool getCollisionBox(aabb3f *toset); + bool collideWithObjects(); + +private: + std::string getPropertyPacket(); + + Player *m_player; + u16 m_peer_id; + Inventory *m_inventory; + s16 m_damage; + + // Cheat prevention + LagPool m_dig_pool; + LagPool m_move_pool; + v3f m_last_good_position; + float m_time_from_last_punch; + v3s16 m_nocheat_dig_pos; + float m_nocheat_dig_time; + + int m_wield_index; + bool m_position_not_sent; + ItemGroupList m_armor_groups; + bool m_armor_groups_sent; + + bool m_properties_sent; + struct ObjectProperties m_prop; + // Cached privileges for enforcement + std::set m_privs; + bool m_is_singleplayer; + + v2f m_animation_range; + float m_animation_speed; + float m_animation_blend; + bool m_animation_sent; + + std::map > m_bone_position; // Stores position and rotation for each bone name + bool m_bone_position_sent; + + int m_attachment_parent_id; + std::string m_attachment_bone; + v3f m_attachment_position; + v3f m_attachment_rotation; + bool m_attachment_sent; + +public: + float m_physics_override_speed; + float m_physics_override_jump; + float m_physics_override_gravity; + bool m_physics_override_sneak; + bool m_physics_override_sneak_glitch; + bool m_physics_override_sent; +}; + +#endif + diff --git a/src/convert_json.cpp b/src/convert_json.cpp new file mode 100644 index 0000000..cea0896 --- /dev/null +++ b/src/convert_json.cpp @@ -0,0 +1,390 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#include +#include +#include + +#include "convert_json.h" +#include "mods.h" +#include "config.h" +#include "log.h" +#include "main.h" // for g_settings +#include "settings.h" +#include "httpfetch.h" +#include "porting.h" + +Json::Value fetchJsonValue(const std::string &url, + std::vector *extra_headers) +{ + HTTPFetchRequest fetch_request; + HTTPFetchResult fetch_result; + fetch_request.url = url; + fetch_request.caller = HTTPFETCH_SYNC; + + if (extra_headers != NULL) + fetch_request.extra_headers = *extra_headers; + + httpfetch_sync(fetch_request, fetch_result); + + if (!fetch_result.succeeded) { + return Json::Value(); + } + Json::Value root; + Json::Reader reader; + std::istringstream stream(fetch_result.data); + + if (!reader.parse(stream, root)) { + errorstream << "URL: " << url << std::endl; + errorstream << "Failed to parse json data " << reader.getFormattedErrorMessages(); + errorstream << "data: \"" << fetch_result.data << "\"" << std::endl; + return Json::Value(); + } + + return root; +} + +std::vector readModStoreList(Json::Value& modlist) { + std::vector retval; + + if (modlist.isArray()) { + for (unsigned int i = 0; i < modlist.size(); i++) + { + ModStoreMod toadd; + toadd.valid = true; + + //id + if (modlist[i]["id"].asString().size()) { + std::string id_raw = modlist[i]["id"].asString(); + char* endptr = 0; + int numbervalue = strtol(id_raw.c_str(),&endptr,10); + + if ((id_raw != "") && (*endptr == 0)) { + toadd.id = numbervalue; + } + else { + errorstream << "readModStoreList: missing id" << std::endl; + toadd.valid = false; + } + } + else { + errorstream << "readModStoreList: missing id" << std::endl; + toadd.valid = false; + } + + //title + if (modlist[i]["title"].asString().size()) { + toadd.title = modlist[i]["title"].asString(); + } + else { + errorstream << "readModStoreList: missing title" << std::endl; + toadd.valid = false; + } + + //basename + if (modlist[i]["basename"].asString().size()) { + toadd.basename = modlist[i]["basename"].asString(); + } + else { + errorstream << "readModStoreList: missing basename" << std::endl; + toadd.valid = false; + } + + //author + + //rating + + //version + + if (toadd.valid) { + retval.push_back(toadd); + } + } + } + return retval; +} + +ModStoreModDetails readModStoreModDetails(Json::Value& details) { + + ModStoreModDetails retval; + + retval.valid = true; + + //version set + if (details["version_set"].isArray()) { + for (unsigned int i = 0; i < details["version_set"].size(); i++) + { + ModStoreVersionEntry toadd; + + if (details["version_set"][i]["id"].asString().size()) { + std::string id_raw = details["version_set"][i]["id"].asString(); + char* endptr = 0; + int numbervalue = strtol(id_raw.c_str(),&endptr,10); + + if ((id_raw != "") && (*endptr == 0)) { + toadd.id = numbervalue; + } + } + else { + errorstream << "readModStoreModDetails: missing version_set id" << std::endl; + retval.valid = false; + } + + //date + if (details["version_set"][i]["date"].asString().size()) { + toadd.date = details["version_set"][i]["date"].asString(); + } + + //file + if (details["version_set"][i]["file"].asString().size()) { + toadd.file = details["version_set"][i]["file"].asString(); + } + else { + errorstream << "readModStoreModDetails: missing version_set file" << std::endl; + retval.valid = false; + } + + //approved + + //mtversion + + if( retval.valid ) { + retval.versions.push_back(toadd); + } + else { + break; + } + } + } + + if (retval.versions.size() < 1) { + infostream << "readModStoreModDetails: not a single version specified!" << std::endl; + retval.valid = false; + } + + //categories + if (details["categories"].isObject()) { + for (unsigned int i = 0; i < details["categories"].size(); i++) { + ModStoreCategoryInfo toadd; + + if (details["categories"][i]["id"].asString().size()) { + + std::string id_raw = details["categories"][i]["id"].asString(); + char* endptr = 0; + int numbervalue = strtol(id_raw.c_str(),&endptr,10); + + if ((id_raw != "") && (*endptr == 0)) { + toadd.id = numbervalue; + } + } + else { + errorstream << "readModStoreModDetails: missing categories id" << std::endl; + retval.valid = false; + } + if (details["categories"][i]["title"].asString().size()) { + toadd.name = details["categories"][i]["title"].asString(); + } + else { + errorstream << "readModStoreModDetails: missing categories title" << std::endl; + retval.valid = false; + } + + if( retval.valid ) { + retval.categories.push_back(toadd); + } + else { + break; + } + } + } + + //author + if (details["author"].isObject()) { + if (details["author"]["id"].asString().size()) { + + std::string id_raw = details["author"]["id"].asString(); + char* endptr = 0; + int numbervalue = strtol(id_raw.c_str(),&endptr,10); + + if ((id_raw != "") && (*endptr == 0)) { + retval.author.id = numbervalue; + } + else { + errorstream << "readModStoreModDetails: missing author id (convert)" << std::endl; + retval.valid = false; + } + } + else { + errorstream << "readModStoreModDetails: missing author id" << std::endl; + retval.valid = false; + } + + if (details["author"]["username"].asString().size()) { + retval.author.username = details["author"]["username"].asString(); + } + else { + errorstream << "readModStoreModDetails: missing author username" << std::endl; + retval.valid = false; + } + } + else { + errorstream << "readModStoreModDetails: missing author" << std::endl; + retval.valid = false; + } + + //license + if (details["license"].isObject()) { + if (details["license"]["id"].asString().size()) { + + std::string id_raw = details["license"]["id"].asString(); + char* endptr = 0; + int numbervalue = strtol(id_raw.c_str(),&endptr,10); + + if ((id_raw != "") && (*endptr == 0)) { + retval.license.id = numbervalue; + } + } + else { + errorstream << "readModStoreModDetails: missing license id" << std::endl; + retval.valid = false; + } + + if (details["license"]["short"].asString().size()) { + retval.license.shortinfo = details["license"]["short"].asString(); + } + else { + errorstream << "readModStoreModDetails: missing license short" << std::endl; + retval.valid = false; + } + + if (details["license"]["link"].asString().size()) { + retval.license.url = details["license"]["link"].asString(); + } + + } + + //titlepic + if (details["titlepic"].isObject()) { + if (details["titlepic"]["id"].asString().size()) { + + std::string id_raw = details["titlepic"]["id"].asString(); + char* endptr = 0; + int numbervalue = strtol(id_raw.c_str(),&endptr,10); + + if ((id_raw != "") && (*endptr == 0)) { + retval.titlepic.id = numbervalue; + } + } + + if (details["titlepic"]["file"].asString().size()) { + retval.titlepic.file = details["titlepic"]["file"].asString(); + } + + if (details["titlepic"]["desc"].asString().size()) { + retval.titlepic.description = details["titlepic"]["desc"].asString(); + } + + if (details["titlepic"]["mod"].asString().size()) { + + std::string mod_raw = details["titlepic"]["mod"].asString(); + char* endptr = 0; + int numbervalue = strtol(mod_raw.c_str(),&endptr,10); + + if ((mod_raw != "") && (*endptr == 0)) { + retval.titlepic.mod = numbervalue; + } + } + } + + //id + if (details["id"].asString().size()) { + + std::string id_raw = details["id"].asString(); + char* endptr = 0; + int numbervalue = strtol(id_raw.c_str(),&endptr,10); + + if ((id_raw != "") && (*endptr == 0)) { + retval.id = numbervalue; + } + } + else { + errorstream << "readModStoreModDetails: missing id" << std::endl; + retval.valid = false; + } + + //title + if (details["title"].asString().size()) { + retval.title = details["title"].asString(); + } + else { + errorstream << "readModStoreModDetails: missing title" << std::endl; + retval.valid = false; + } + + //basename + if (details["basename"].asString().size()) { + retval.basename = details["basename"].asString(); + } + else { + errorstream << "readModStoreModDetails: missing basename" << std::endl; + retval.valid = false; + } + + //description + if (details["desc"].asString().size()) { + retval.description = details["desc"].asString(); + } + + //repository + if (details["replink"].asString().size()) { + retval.repository = details["replink"].asString(); + } + + //value + if (details["rating"].asString().size()) { + + std::string id_raw = details["rating"].asString(); + char* endptr = 0; + float numbervalue = strtof(id_raw.c_str(),&endptr); + + if ((id_raw != "") && (*endptr == 0)) { + retval.rating = numbervalue; + } + } + else { + retval.rating = 0.0; + } + + //depends + if (details["depends"].isArray()) { + //TODO + } + + //softdepends + if (details["softdep"].isArray()) { + //TODO + } + + //screenshot url + if (details["screenshot_url"].asString().size()) { + retval.screenshot_url = details["screenshot_url"].asString(); + } + + return retval; +} diff --git a/src/convert_json.h b/src/convert_json.h new file mode 100644 index 0000000..6732fcf --- /dev/null +++ b/src/convert_json.h @@ -0,0 +1,34 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef __CONVERT_JSON_H__ +#define __CONVERT_JSON_H__ + +#include "json/json.h" + +struct ModStoreMod; +struct ModStoreModDetails; + +std::vector readModStoreList(Json::Value& modlist); +ModStoreModDetails readModStoreModDetails(Json::Value& details); + +Json::Value fetchJsonValue(const std::string &url, + std::vector *extra_headers); + +#endif diff --git a/src/craftdef.cpp b/src/craftdef.cpp new file mode 100644 index 0000000..80937cc --- /dev/null +++ b/src/craftdef.cpp @@ -0,0 +1,1081 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#include "craftdef.h" + +#include "irrlichttypes.h" +#include "log.h" +#include +#include +#include +#include "gamedef.h" +#include "inventory.h" +#include "util/serialize.h" +#include "strfnd.h" +#include "exceptions.h" + +// Check if input matches recipe +// Takes recipe groups into account +static bool inputItemMatchesRecipe(const std::string &inp_name, + const std::string &rec_name, IItemDefManager *idef) +{ + // Exact name + if(inp_name == rec_name) + return true; + + // Group + if(rec_name.substr(0,6) == "group:" && idef->isKnown(inp_name)){ + const struct ItemDefinition &def = idef->get(inp_name); + Strfnd f(rec_name.substr(6)); + bool all_groups_match = true; + do{ + std::string check_group = f.next(","); + if(itemgroup_get(def.groups, check_group) == 0){ + all_groups_match = false; + break; + } + }while(!f.atend()); + if(all_groups_match) + return true; + } + + // Didn't match + return false; +} + +// Deserialize an itemstring then return the name of the item +static std::string craftGetItemName(const std::string &itemstring, IGameDef *gamedef) +{ + ItemStack item; + item.deSerialize(itemstring, gamedef->idef()); + return item.name; +} + +// (mapcar craftGetItemName itemstrings) +static std::vector craftGetItemNames( + const std::vector &itemstrings, IGameDef *gamedef) +{ + std::vector result; + for(std::vector::const_iterator + i = itemstrings.begin(); + i != itemstrings.end(); i++) + { + result.push_back(craftGetItemName(*i, gamedef)); + } + return result; +} + +// Get name of each item, and return them as a new list. +static std::vector craftGetItemNames( + const std::vector &items, IGameDef *gamedef) +{ + std::vector result; + for(std::vector::const_iterator + i = items.begin(); + i != items.end(); i++) + { + result.push_back(i->name); + } + return result; +} + +// convert a list of item names, to ItemStacks. +static std::vector craftGetItems( + const std::vector &items, IGameDef *gamedef) +{ + std::vector result; + for(std::vector::const_iterator + i = items.begin(); + i != items.end(); i++) + { + result.push_back(ItemStack(std::string(*i),(u16)1,(u16)0,"",gamedef->getItemDefManager())); + } + return result; +} + +// Compute bounding rectangle given a matrix of items +// Returns false if every item is "" +static bool craftGetBounds(const std::vector &items, unsigned int width, + unsigned int &min_x, unsigned int &max_x, + unsigned int &min_y, unsigned int &max_y) +{ + bool success = false; + unsigned int x = 0; + unsigned int y = 0; + for(std::vector::const_iterator + i = items.begin(); + i != items.end(); i++) + { + if(*i != "") // Is this an actual item? + { + if(!success) + { + // This is the first nonempty item + min_x = max_x = x; + min_y = max_y = y; + success = true; + } + else + { + if(x < min_x) min_x = x; + if(x > max_x) max_x = x; + if(y < min_y) min_y = y; + if(y > max_y) max_y = y; + } + } + + // Step coordinate + x++; + if(x == width) + { + x = 0; + y++; + } + } + return success; +} + +// Removes 1 from each item stack +static void craftDecrementInput(CraftInput &input, IGameDef *gamedef) +{ + for(std::vector::iterator + i = input.items.begin(); + i != input.items.end(); i++) + { + if(i->count != 0) + i->remove(1); + } +} + +// Removes 1 from each item stack with replacement support +// Example: if replacements contains the pair ("bucket:bucket_water", "bucket:bucket_empty"), +// a water bucket will not be removed but replaced by an empty bucket. +static void craftDecrementOrReplaceInput(CraftInput &input, + const CraftReplacements &replacements, + IGameDef *gamedef) +{ + if(replacements.pairs.empty()) + { + craftDecrementInput(input, gamedef); + return; + } + + // Make a copy of the replacements pair list + std::vector > pairs = replacements.pairs; + + for(std::vector::iterator + i = input.items.begin(); + i != input.items.end(); i++) + { + if(i->count == 1) + { + // Find an appropriate replacement + bool found_replacement = false; + for(std::vector >::iterator + j = pairs.begin(); + j != pairs.end(); j++) + { + ItemStack from_item; + from_item.deSerialize(j->first, gamedef->idef()); + if(i->name == from_item.name) + { + i->deSerialize(j->second, gamedef->idef()); + found_replacement = true; + pairs.erase(j); + break; + } + } + // No replacement was found, simply decrement count to zero + if(!found_replacement) + i->remove(1); + } + else if(i->count >= 2) + { + // Ignore replacements for items with count >= 2 + i->remove(1); + } + } +} + +// Dump an itemstring matrix +static std::string craftDumpMatrix(const std::vector &items, + unsigned int width) +{ + std::ostringstream os(std::ios::binary); + os<<"{ "; + unsigned int x = 0; + for(std::vector::const_iterator + i = items.begin(); + i != items.end(); i++, x++) + { + if(x == width) + { + os<<"; "; + x = 0; + } + else if(x != 0) + { + os<<","; + } + os<<"\""<<(*i)<<"\""; + } + os<<" }"; + return os.str(); +} + +// Dump an item matrix +std::string craftDumpMatrix(const std::vector &items, + unsigned int width) +{ + std::ostringstream os(std::ios::binary); + os<<"{ "; + unsigned int x = 0; + for(std::vector::const_iterator + i = items.begin(); + i != items.end(); i++, x++) + { + if(x == width) + { + os<<"; "; + x = 0; + } + else if(x != 0) + { + os<<","; + } + os<<"\""<<(i->getItemString())<<"\""; + } + os<<" }"; + return os.str(); +} + + +/* + CraftInput +*/ + +std::string CraftInput::dump() const +{ + std::ostringstream os(std::ios::binary); + os<<"(method="<<((int)method)<<", items="< >::const_iterator + i = pairs.begin(); + i != pairs.end(); i++) + { + os<first)<<"\"=>\""<<(i->second)<<"\""; + sep = ","; + } + os<<"}"; + return os.str(); +} + +void CraftReplacements::serialize(std::ostream &os) const +{ + writeU16(os, pairs.size()); + for(u32 i=0; ideSerializeBody(is, version); + return def; +} + +/* + CraftDefinitionShaped +*/ + +std::string CraftDefinitionShaped::getName() const +{ + return "shaped"; +} + +bool CraftDefinitionShaped::check(const CraftInput &input, IGameDef *gamedef) const +{ + if(input.method != CRAFT_METHOD_NORMAL) + return false; + + // Get input item matrix + std::vector inp_names = craftGetItemNames(input.items, gamedef); + unsigned int inp_width = input.width; + if(inp_width == 0) + return false; + while(inp_names.size() % inp_width != 0) + inp_names.push_back(""); + + // Get input bounds + unsigned int inp_min_x=0, inp_max_x=0, inp_min_y=0, inp_max_y=0; + if(!craftGetBounds(inp_names, inp_width, inp_min_x, inp_max_x, inp_min_y, inp_max_y)) + return false; // it was empty + + // Get recipe item matrix + std::vector rec_names = craftGetItemNames(recipe, gamedef); + unsigned int rec_width = width; + if(rec_width == 0) + return false; + while(rec_names.size() % rec_width != 0) + rec_names.push_back(""); + + // Get recipe bounds + unsigned int rec_min_x=0, rec_max_x=0, rec_min_y=0, rec_max_y=0; + if(!craftGetBounds(rec_names, rec_width, rec_min_x, rec_max_x, rec_min_y, rec_max_y)) + return false; // it was empty + + // Different sizes? + if(inp_max_x - inp_min_x != rec_max_x - rec_min_x || + inp_max_y - inp_min_y != rec_max_y - rec_min_y) + return false; + + // Verify that all item names in the bounding box are equal + unsigned int w = inp_max_x - inp_min_x + 1; + unsigned int h = inp_max_y - inp_min_y + 1; + + for(unsigned int y=0; y < h; y++) { + unsigned int inp_y = (inp_min_y + y) * inp_width; + unsigned int rec_y = (rec_min_y + y) * rec_width; + + for(unsigned int x=0; x < w; x++) { + unsigned int inp_x = inp_min_x + x; + unsigned int rec_x = rec_min_x + x; + + if(!inputItemMatchesRecipe( + inp_names[inp_y + inp_x], + rec_names[rec_y + rec_x], gamedef->idef()) + ) { + return false; + } + } + } + + return true; +} + +CraftOutput CraftDefinitionShaped::getOutput(const CraftInput &input, IGameDef *gamedef) const +{ + return CraftOutput(output, 0); +} + +CraftInput CraftDefinitionShaped::getInput(const CraftOutput &output, IGameDef *gamedef) const +{ + return CraftInput(CRAFT_METHOD_NORMAL,width,craftGetItems(recipe,gamedef)); +} + +void CraftDefinitionShaped::decrementInput(CraftInput &input, IGameDef *gamedef) const +{ + craftDecrementOrReplaceInput(input, replacements, gamedef); +} + +std::string CraftDefinitionShaped::dump() const +{ + std::ostringstream os(std::ios::binary); + os<<"(shaped, output=\""< input_filtered; + for(std::vector::const_iterator + i = input.items.begin(); + i != input.items.end(); i++) + { + if(i->name != "") + input_filtered.push_back(i->name); + } + + // If there is a wrong number of items in input, no match + if(input_filtered.size() != recipe.size()){ + /*dstream<<"Number of input items ("<idef())){ + all_match = false; + break; + } + } + //dstream<<" -> match="<idef(); + if(item1.count != 1 || item2.count != 1 || item1.name != item2.name + || idef->get(item1.name).type != ITEM_TOOL + || idef->get(item2.name).type != ITEM_TOOL) + { + // Failure + return ItemStack(); + } + + s32 item1_uses = 65536 - (u32) item1.wear; + s32 item2_uses = 65536 - (u32) item2.wear; + s32 new_uses = item1_uses + item2_uses; + s32 new_wear = 65536 - new_uses + floor(additional_wear * 65536 + 0.5); + if(new_wear >= 65536) + return ItemStack(); + if(new_wear < 0) + new_wear = 0; + + ItemStack repaired = item1; + repaired.wear = new_wear; + return repaired; +} + +std::string CraftDefinitionToolRepair::getName() const +{ + return "toolrepair"; +} + +bool CraftDefinitionToolRepair::check(const CraftInput &input, IGameDef *gamedef) const +{ + if(input.method != CRAFT_METHOD_NORMAL) + return false; + + ItemStack item1; + ItemStack item2; + for(std::vector::const_iterator + i = input.items.begin(); + i != input.items.end(); i++) + { + if(!i->empty()) + { + if(item1.empty()) + item1 = *i; + else if(item2.empty()) + item2 = *i; + else + return false; + } + } + ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef); + return !repaired.empty(); +} + +CraftOutput CraftDefinitionToolRepair::getOutput(const CraftInput &input, IGameDef *gamedef) const +{ + ItemStack item1; + ItemStack item2; + for(std::vector::const_iterator + i = input.items.begin(); + i != input.items.end(); i++) + { + if(!i->empty()) + { + if(item1.empty()) + item1 = *i; + else if(item2.empty()) + item2 = *i; + } + } + ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef); + return CraftOutput(repaired.getItemString(), 0); +} + +CraftInput CraftDefinitionToolRepair::getInput(const CraftOutput &output, IGameDef *gamedef) const +{ + std::vector stack; + stack.push_back(ItemStack()); + return CraftInput(CRAFT_METHOD_COOKING,additional_wear,stack); +} + +void CraftDefinitionToolRepair::decrementInput(CraftInput &input, IGameDef *gamedef) const +{ + craftDecrementInput(input, gamedef); +} + +std::string CraftDefinitionToolRepair::dump() const +{ + std::ostringstream os(std::ios::binary); + os<<"(toolrepair, additional_wear="< input_filtered; + for(std::vector::const_iterator + i = input.items.begin(); + i != input.items.end(); i++) + { + if(i->name != "") + input_filtered.push_back(i->name); + } + + // If there is a wrong number of items in input, no match + if(input_filtered.size() != 1){ + /*dstream<<"Number of input items ("< input_filtered; + for(std::vector::const_iterator + i = input.items.begin(); + i != input.items.end(); i++) + { + if(i->name != "") + input_filtered.push_back(i->name); + } + + // If there is a wrong number of items in input, no match + if(input_filtered.size() != 1){ + /*dstream<<"Number of input items ("< rec; + rec.push_back(recipe); + return CraftInput(CRAFT_METHOD_COOKING,(int)burntime,craftGetItems(rec,gamedef)); +} + +void CraftDefinitionFuel::decrementInput(CraftInput &input, IGameDef *gamedef) const +{ + craftDecrementOrReplaceInput(input, replacements, gamedef); +} + +std::string CraftDefinitionFuel::dump() const +{ + std::ostringstream os(std::ios::binary); + os<<"(fuel, recipe=\""<::const_iterator + i = input.items.begin(); + i != input.items.end(); i++) + { + if(!i->empty()) + { + all_empty = false; + break; + } + } + if(all_empty) + return false; + + // Walk crafting definitions from back to front, so that later + // definitions can override earlier ones. + for(std::vector::const_reverse_iterator + i = m_craft_definitions.rbegin(); + i != m_craft_definitions.rend(); i++) + { + CraftDefinition *def = *i; + + /*infostream<<"Checking "<dump()<check(input, gamedef)) + { + // Get output, then decrement input (if requested) + output = def->getOutput(input, gamedef); + if(decrementInput) + def->decrementInput(input, gamedef); + return true; + } + } + catch(SerializationError &e) + { + errorstream<<"getCraftResult: ERROR: " + <<"Serialization error in recipe " + <dump()<::const_reverse_iterator + i = m_craft_definitions.rbegin(); + i != m_craft_definitions.rend(); i++) + { + CraftDefinition *def = *i; + + /*infostream<<"Checking "<dump()<getOutput(input, gamedef); + if((tmpout.item.substr(0,output.item.length()) == output.item) && + ((tmpout.item[output.item.length()] == 0) || + (tmpout.item[output.item.length()] == ' '))) + { + // Get output, then decrement input (if requested) + input = def->getInput(output, gamedef); + return true; + } + } + catch(SerializationError &e) + { + errorstream<<"getCraftResult: ERROR: " + <<"Serialization error in recipe " + <dump()< getCraftRecipes(CraftOutput &output, + IGameDef *gamedef) const + { + std::vector recipes_list; + CraftInput input; + CraftOutput tmpout; + tmpout.item = ""; + tmpout.time = 0; + + for(std::vector::const_reverse_iterator + i = m_craft_definitions.rbegin(); + i != m_craft_definitions.rend(); i++) + { + CraftDefinition *def = *i; + + /*infostream<<"Checking "<dump()<getOutput(input, gamedef); + if(tmpout.item.substr(0,output.item.length()) == output.item) + { + // Get output, then decrement input (if requested) + input = def->getInput(output, gamedef); + recipes_list.push_back(*i); + } + } + catch(SerializationError &e) + { + errorstream<<"getCraftResult: ERROR: " + <<"Serialization error in recipe " + <dump()<::const_iterator + i = m_craft_definitions.begin(); + i != m_craft_definitions.end(); i++) + { + os<<(*i)->dump()<<"\n"; + } + return os.str(); + } + virtual void registerCraft(CraftDefinition *def) + { + verbosestream<<"registerCraft: registering craft definition: " + <dump()<::iterator + i = m_craft_definitions.begin(); + i != m_craft_definitions.end(); i++){ + delete *i; + } + m_craft_definitions.clear(); + } + virtual void serialize(std::ostream &os) const + { + writeU8(os, 0); // version + u16 count = m_craft_definitions.size(); + writeU16(os, count); + for(std::vector::const_iterator + i = m_craft_definitions.begin(); + i != m_craft_definitions.end(); i++){ + CraftDefinition *def = *i; + // Serialize wrapped in a string + std::ostringstream tmp_os(std::ios::binary); + def->serialize(tmp_os); + os< m_craft_definitions; +}; + +IWritableCraftDefManager* createCraftDefManager() +{ + return new CCraftDefManager(); +} + diff --git a/src/craftdef.h b/src/craftdef.h new file mode 100644 index 0000000..14dc530 --- /dev/null +++ b/src/craftdef.h @@ -0,0 +1,400 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef CRAFTDEF_HEADER +#define CRAFTDEF_HEADER + +#include +#include +#include +#include +#include "gamedef.h" +#include "inventory.h" + +/* + Crafting methods. + + The crafting method depends on the inventory list + that the crafting input comes from. +*/ +enum CraftMethod +{ + // Crafting grid + CRAFT_METHOD_NORMAL, + // Cooking something in a furnace + CRAFT_METHOD_COOKING, + // Using something as fuel for a furnace + CRAFT_METHOD_FUEL, +}; + +/* + Input: The contents of the crafting slots, arranged in matrix form +*/ +struct CraftInput +{ + CraftMethod method; + unsigned int width; + std::vector items; + + CraftInput(): + method(CRAFT_METHOD_NORMAL), width(0), items() + {} + CraftInput(CraftMethod method_, unsigned int width_, + const std::vector &items_): + method(method_), width(width_), items(items_) + {} + std::string dump() const; +}; + +/* + Output: Result of crafting operation +*/ +struct CraftOutput +{ + // Used for normal crafting and cooking, itemstring + std::string item; + // Used for cooking (cook time) and fuel (burn time), seconds + float time; + + CraftOutput(): + item(""), time(0) + {} + CraftOutput(std::string item_, float time_): + item(item_), time(time_) + {} + std::string dump() const; +}; + +/* + A list of replacements. A replacement indicates that a specific + input item should not be deleted (when crafting) but replaced with + a different item. Each replacements is a pair (itemstring to remove, + itemstring to replace with) + + Example: If ("bucket:bucket_water", "bucket:bucket_empty") is a + replacement pair, the crafting input slot that contained a water + bucket will contain an empty bucket after crafting. + + Note: replacements only work correctly when stack_max of the item + to be replaced is 1. It is up to the mod writer to ensure this. +*/ +struct CraftReplacements +{ + // List of replacements + std::vector > pairs; + + CraftReplacements(): + pairs() + {} + CraftReplacements(std::vector > pairs_): + pairs(pairs_) + {} + std::string dump() const; + void serialize(std::ostream &os) const; + void deSerialize(std::istream &is); +}; + +/* + Crafting definition base class +*/ +class CraftDefinition +{ +public: + CraftDefinition(){} + virtual ~CraftDefinition(){} + + void serialize(std::ostream &os) const; + static CraftDefinition* deSerialize(std::istream &is); + + // Returns type of crafting definition + virtual std::string getName() const=0; + + // Checks whether the recipe is applicable + virtual bool check(const CraftInput &input, IGameDef *gamedef) const=0; + // Returns the output structure, meaning depends on crafting method + // The implementation can assume that check(input) returns true + virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const=0; + // the inverse of the above + virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const=0; + // Decreases count of every input item + virtual void decrementInput(CraftInput &input, IGameDef *gamedef) const=0; + + virtual std::string dump() const=0; + +protected: + virtual void serializeBody(std::ostream &os) const=0; + virtual void deSerializeBody(std::istream &is, int version)=0; +}; + +/* + A plain-jane (shaped) crafting definition + + Supported crafting method: CRAFT_METHOD_NORMAL. + Requires the input items to be arranged exactly like in the recipe. +*/ +class CraftDefinitionShaped: public CraftDefinition +{ +public: + CraftDefinitionShaped(): + output(""), width(1), recipe(), replacements() + {} + CraftDefinitionShaped( + const std::string &output_, + unsigned int width_, + const std::vector &recipe_, + const CraftReplacements &replacements_): + output(output_), width(width_), recipe(recipe_), replacements(replacements_) + {} + virtual ~CraftDefinitionShaped(){} + + virtual std::string getName() const; + virtual bool check(const CraftInput &input, IGameDef *gamedef) const; + virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const; + virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const; + virtual void decrementInput(CraftInput &input, IGameDef *gamedef) const; + + virtual std::string dump() const; + +protected: + virtual void serializeBody(std::ostream &os) const; + virtual void deSerializeBody(std::istream &is, int version); + +private: + // Output itemstring + std::string output; + // Width of recipe + unsigned int width; + // Recipe matrix (itemstrings) + std::vector recipe; + // Replacement items for decrementInput() + CraftReplacements replacements; +}; + +/* + A shapeless crafting definition + Supported crafting method: CRAFT_METHOD_NORMAL. + Input items can arranged in any way. +*/ +class CraftDefinitionShapeless: public CraftDefinition +{ +public: + CraftDefinitionShapeless(): + output(""), recipe(), replacements() + {} + CraftDefinitionShapeless( + const std::string &output_, + const std::vector &recipe_, + const CraftReplacements &replacements_): + output(output_), recipe(recipe_), replacements(replacements_) + {} + virtual ~CraftDefinitionShapeless(){} + + virtual std::string getName() const; + virtual bool check(const CraftInput &input, IGameDef *gamedef) const; + virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const; + virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const; + virtual void decrementInput(CraftInput &input, IGameDef *gamedef) const; + + virtual std::string dump() const; + +protected: + virtual void serializeBody(std::ostream &os) const; + virtual void deSerializeBody(std::istream &is, int version); + +private: + // Output itemstring + std::string output; + // Recipe list (itemstrings) + std::vector recipe; + // Replacement items for decrementInput() + CraftReplacements replacements; +}; + +/* + Tool repair crafting definition + Supported crafting method: CRAFT_METHOD_NORMAL. + Put two damaged tools into the crafting grid, get one tool back. + There should only be one crafting definition of this type. +*/ +class CraftDefinitionToolRepair: public CraftDefinition +{ +public: + CraftDefinitionToolRepair(): + additional_wear(0) + {} + CraftDefinitionToolRepair(float additional_wear_): + additional_wear(additional_wear_) + {} + virtual ~CraftDefinitionToolRepair(){} + + virtual std::string getName() const; + virtual bool check(const CraftInput &input, IGameDef *gamedef) const; + virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const; + virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const; + virtual void decrementInput(CraftInput &input, IGameDef *gamedef) const; + + virtual std::string dump() const; + +protected: + virtual void serializeBody(std::ostream &os) const; + virtual void deSerializeBody(std::istream &is, int version); + +private: + // This is a constant that is added to the wear of the result. + // May be positive or negative, allowed range [-1,1]. + // 1 = new tool is completely broken + // 0 = simply add remaining uses of both input tools + // -1 = new tool is completely pristine + float additional_wear; +}; + +/* + A cooking (in furnace) definition + Supported crafting method: CRAFT_METHOD_COOKING. +*/ +class CraftDefinitionCooking: public CraftDefinition +{ +public: + CraftDefinitionCooking(): + output(""), recipe(""), cooktime() + {} + CraftDefinitionCooking( + const std::string &output_, + const std::string &recipe_, + float cooktime_, + const CraftReplacements &replacements_): + output(output_), recipe(recipe_), cooktime(cooktime_), replacements(replacements_) + {} + virtual ~CraftDefinitionCooking(){} + + virtual std::string getName() const; + virtual bool check(const CraftInput &input, IGameDef *gamedef) const; + virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const; + virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const; + virtual void decrementInput(CraftInput &input, IGameDef *gamedef) const; + + virtual std::string dump() const; + +protected: + virtual void serializeBody(std::ostream &os) const; + virtual void deSerializeBody(std::istream &is, int version); + +private: + // Output itemstring + std::string output; + // Recipe itemstring + std::string recipe; + // Time in seconds + float cooktime; + // Replacement items for decrementInput() + CraftReplacements replacements; +}; + +/* + A fuel (for furnace) definition + Supported crafting method: CRAFT_METHOD_FUEL. +*/ +class CraftDefinitionFuel: public CraftDefinition +{ +public: + CraftDefinitionFuel(): + recipe(""), burntime() + {} + CraftDefinitionFuel(std::string recipe_, + float burntime_, + const CraftReplacements &replacements_): + recipe(recipe_), burntime(burntime_), replacements(replacements_) + {} + virtual ~CraftDefinitionFuel(){} + + virtual std::string getName() const; + virtual bool check(const CraftInput &input, IGameDef *gamedef) const; + virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const; + virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const; + virtual void decrementInput(CraftInput &input, IGameDef *gamedef) const; + + virtual std::string dump() const; + +protected: + virtual void serializeBody(std::ostream &os) const; + virtual void deSerializeBody(std::istream &is, int version); + +private: + // Recipe itemstring + std::string recipe; + // Time in seconds + float burntime; + // Replacement items for decrementInput() + CraftReplacements replacements; +}; + +/* + Crafting definition manager +*/ +class ICraftDefManager +{ +public: + ICraftDefManager(){} + virtual ~ICraftDefManager(){} + + // The main crafting function + virtual bool getCraftResult(CraftInput &input, CraftOutput &output, + bool decrementInput, IGameDef *gamedef) const=0; + virtual bool getCraftRecipe(CraftInput &input, CraftOutput &output, + IGameDef *gamedef) const=0; + virtual std::vector getCraftRecipes(CraftOutput &output, + IGameDef *gamedef) const=0; + + // Print crafting recipes for debugging + virtual std::string dump() const=0; + + virtual void serialize(std::ostream &os) const=0; +}; + +class IWritableCraftDefManager : public ICraftDefManager +{ +public: + IWritableCraftDefManager(){} + virtual ~IWritableCraftDefManager(){} + + // The main crafting function + virtual bool getCraftResult(CraftInput &input, CraftOutput &output, + bool decrementInput, IGameDef *gamedef) const=0; + virtual bool getCraftRecipe(CraftInput &input, CraftOutput &output, + IGameDef *gamedef) const=0; + virtual std::vector getCraftRecipes(CraftOutput &output, + IGameDef *gamedef) const=0; + + // Print crafting recipes for debugging + virtual std::string dump() const=0; + + // Add a crafting definition. + // After calling this, the pointer belongs to the manager. + virtual void registerCraft(CraftDefinition *def)=0; + // Delete all crafting definitions + virtual void clear()=0; + + virtual void serialize(std::ostream &os) const=0; + virtual void deSerialize(std::istream &is)=0; +}; + +IWritableCraftDefManager* createCraftDefManager(); + +#endif + diff --git a/src/database-dummy.cpp b/src/database-dummy.cpp new file mode 100644 index 0000000..2e5de5e --- /dev/null +++ b/src/database-dummy.cpp @@ -0,0 +1,55 @@ +/* +Minetest +Copyright (C) 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. +*/ + +/* +Dummy database class +*/ + +#include "database-dummy.h" + + +bool Database_Dummy::saveBlock(const v3s16 &pos, const std::string &data) +{ + m_database[getBlockAsInteger(pos)] = data; + return true; +} + +std::string Database_Dummy::loadBlock(const v3s16 &pos) +{ + s64 i = getBlockAsInteger(pos); + std::map::iterator it = m_database.find(i); + if (it == m_database.end()) + return ""; + return it->second; +} + +bool Database_Dummy::deleteBlock(const v3s16 &pos) +{ + m_database.erase(getBlockAsInteger(pos)); + return true; +} + +void Database_Dummy::listAllLoadableBlocks(std::vector &dst) +{ + for (std::map::const_iterator x = m_database.begin(); + x != m_database.end(); ++x) { + dst.push_back(getIntegerAsBlock(x->first)); + } +} + diff --git a/src/database-dummy.h b/src/database-dummy.h new file mode 100644 index 0000000..0cf5692 --- /dev/null +++ b/src/database-dummy.h @@ -0,0 +1,41 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef DATABASE_DUMMY_HEADER +#define DATABASE_DUMMY_HEADER + +#include +#include +#include "database.h" +#include "irrlichttypes.h" + +class Database_Dummy : public Database +{ +public: + virtual bool saveBlock(const v3s16 &pos, const std::string &data); + virtual std::string loadBlock(const v3s16 &pos); + virtual bool deleteBlock(const v3s16 &pos); + virtual void listAllLoadableBlocks(std::vector &dst); + +private: + std::map m_database; +}; + +#endif + diff --git a/src/database-leveldb.cpp b/src/database-leveldb.cpp new file mode 100644 index 0000000..e895354 --- /dev/null +++ b/src/database-leveldb.cpp @@ -0,0 +1,104 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#include "config.h" + +#if USE_LEVELDB + +#include "database-leveldb.h" + +#include "log.h" +#include "filesys.h" +#include "exceptions.h" +#include "util/string.h" + +#include "leveldb/db.h" + + +#define ENSURE_STATUS_OK(s) \ + if (!(s).ok()) { \ + throw FileNotGoodException(std::string("LevelDB error: ") + \ + (s).ToString()); \ + } + + +Database_LevelDB::Database_LevelDB(const std::string &savedir) +{ + leveldb::Options options; + options.create_if_missing = true; + leveldb::Status status = leveldb::DB::Open(options, + savedir + DIR_DELIM + "map.db", &m_database); + ENSURE_STATUS_OK(status); +} + +Database_LevelDB::~Database_LevelDB() +{ + delete m_database; +} + +bool Database_LevelDB::saveBlock(const v3s16 &pos, const std::string &data) +{ + leveldb::Status status = m_database->Put(leveldb::WriteOptions(), + i64tos(getBlockAsInteger(pos)), data); + if (!status.ok()) { + errorstream << "WARNING: saveBlock: LevelDB error saving block " + << PP(pos) << ": " << status.ToString() << std::endl; + return false; + } + + return true; +} + +std::string Database_LevelDB::loadBlock(const v3s16 &pos) +{ + std::string datastr; + leveldb::Status status = m_database->Get(leveldb::ReadOptions(), + i64tos(getBlockAsInteger(pos)), &datastr); + + if(status.ok()) + return datastr; + else + return ""; +} + +bool Database_LevelDB::deleteBlock(const v3s16 &pos) +{ + leveldb::Status status = m_database->Delete(leveldb::WriteOptions(), + i64tos(getBlockAsInteger(pos))); + if (!status.ok()) { + errorstream << "WARNING: deleteBlock: LevelDB error deleting block " + << PP(pos) << ": " << status.ToString() << std::endl; + return false; + } + + return true; +} + +void Database_LevelDB::listAllLoadableBlocks(std::vector &dst) +{ + leveldb::Iterator* it = m_database->NewIterator(leveldb::ReadOptions()); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + dst.push_back(getIntegerAsBlock(stoi64(it->key().ToString()))); + } + ENSURE_STATUS_OK(it->status()); // Check for any errors found during the scan + delete it; +} + +#endif // USE_LEVELDB + diff --git a/src/database-leveldb.h b/src/database-leveldb.h new file mode 100644 index 0000000..4afe2fd --- /dev/null +++ b/src/database-leveldb.h @@ -0,0 +1,49 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef DATABASE_LEVELDB_HEADER +#define DATABASE_LEVELDB_HEADER + +#include "config.h" + +#if USE_LEVELDB + +#include "database.h" +#include "leveldb/db.h" +#include + +class Database_LevelDB : public Database +{ +public: + Database_LevelDB(const std::string &savedir); + ~Database_LevelDB(); + + virtual bool saveBlock(const v3s16 &pos, const std::string &data); + virtual std::string loadBlock(const v3s16 &pos); + virtual bool deleteBlock(const v3s16 &pos); + virtual void listAllLoadableBlocks(std::vector &dst); + +private: + leveldb::DB *m_database; +}; + +#endif // USE_LEVELDB + +#endif + diff --git a/src/database-redis.cpp b/src/database-redis.cpp new file mode 100644 index 0000000..7b42274 --- /dev/null +++ b/src/database-redis.cpp @@ -0,0 +1,159 @@ +/* +Minetest +Copyright (C) 2014 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. +*/ + +#include "config.h" + +#if USE_REDIS + +#include "database-redis.h" + +#include "settings.h" +#include "log.h" +#include "exceptions.h" +#include "util/string.h" + +#include +#include + + +Database_Redis::Database_Redis(Settings &conf) +{ + std::string tmp; + try { + tmp = conf.get("redis_address"); + hash = conf.get("redis_hash"); + } catch (SettingNotFoundException) { + throw SettingNotFoundException("Set redis_address and " + "redis_hash in world.mt to use the redis backend"); + } + const char *addr = tmp.c_str(); + int port = conf.exists("redis_port") ? conf.getU16("redis_port") : 6379; + ctx = redisConnect(addr, port); + if (!ctx) { + throw FileNotGoodException("Cannot allocate redis context"); + } else if(ctx->err) { + std::string err = std::string("Connection error: ") + ctx->errstr; + redisFree(ctx); + throw FileNotGoodException(err); + } +} + +Database_Redis::~Database_Redis() +{ + redisFree(ctx); +} + +void Database_Redis::beginSave() { + redisReply *reply = static_cast(redisCommand(ctx, "MULTI")); + if (!reply) { + throw FileNotGoodException(std::string( + "Redis command 'MULTI' failed: ") + ctx->errstr); + } + freeReplyObject(reply); +} + +void Database_Redis::endSave() { + redisReply *reply = static_cast(redisCommand(ctx, "EXEC")); + if (!reply) { + throw FileNotGoodException(std::string( + "Redis command 'EXEC' failed: ") + ctx->errstr); + } + freeReplyObject(reply); +} + +bool Database_Redis::saveBlock(const v3s16 &pos, const std::string &data) +{ + std::string tmp = i64tos(getBlockAsInteger(pos)); + + redisReply *reply = static_cast(redisCommand(ctx, "HSET %s %s %b", + hash.c_str(), tmp.c_str(), data.c_str(), data.size())); + if (!reply) { + errorstream << "WARNING: saveBlock: redis command 'HSET' failed on " + "block " << PP(pos) << ": " << ctx->errstr << std::endl; + freeReplyObject(reply); + return false; + } + + if (reply->type == REDIS_REPLY_ERROR) { + errorstream << "WARNING: saveBlock: saving block " << PP(pos) + << "failed" << std::endl; + freeReplyObject(reply); + return false; + } + + freeReplyObject(reply); + return true; +} + +std::string Database_Redis::loadBlock(const v3s16 &pos) +{ + std::string tmp = i64tos(getBlockAsInteger(pos)); + redisReply *reply = static_cast(redisCommand(ctx, + "HGET %s %s", hash.c_str(), tmp.c_str())); + + if (!reply) { + throw FileNotGoodException(std::string( + "Redis command 'HGET %s %s' failed: ") + ctx->errstr); + } else if (reply->type != REDIS_REPLY_STRING) { + return ""; + } + + std::string str(reply->str, reply->len); + freeReplyObject(reply); // std::string copies the memory so this won't cause any problems + return str; +} + +bool Database_Redis::deleteBlock(const v3s16 &pos) +{ + std::string tmp = i64tos(getBlockAsInteger(pos)); + + redisReply *reply = static_cast(redisCommand(ctx, + "HDEL %s %s", hash.c_str(), tmp.c_str())); + if (!reply) { + throw FileNotGoodException(std::string( + "Redis command 'HDEL %s %s' failed: ") + ctx->errstr); + } else if (reply->type == REDIS_REPLY_ERROR) { + errorstream << "WARNING: deleteBlock: deleting block " << PP(pos) + << " failed" << std::endl; + freeReplyObject(reply); + return false; + } + + freeReplyObject(reply); + return true; +} + +void Database_Redis::listAllLoadableBlocks(std::vector &dst) +{ + redisReply *reply = static_cast(redisCommand(ctx, "HKEYS %s", hash.c_str())); + if (!reply) { + throw FileNotGoodException(std::string( + "Redis command 'HKEYS %s' failed: ") + ctx->errstr); + } else if (reply->type != REDIS_REPLY_ARRAY) { + throw FileNotGoodException("Failed to get keys from database"); + } + for (size_t i = 0; i < reply->elements; i++) { + assert(reply->element[i]->type == REDIS_REPLY_STRING); + dst.push_back(getIntegerAsBlock(stoi64(reply->element[i]->str))); + } + freeReplyObject(reply); +} + +#endif // USE_REDIS + diff --git a/src/database-redis.h b/src/database-redis.h new file mode 100644 index 0000000..45e702c --- /dev/null +++ b/src/database-redis.h @@ -0,0 +1,55 @@ +/* +Minetest +Copyright (C) 2014 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. +*/ + +#ifndef DATABASE_REDIS_HEADER +#define DATABASE_REDIS_HEADER + +#include "config.h" + +#if USE_REDIS + +#include "database.h" +#include +#include + +class Settings; + +class Database_Redis : public Database +{ +public: + Database_Redis(Settings &conf); + ~Database_Redis(); + + virtual void beginSave(); + virtual void endSave(); + + virtual bool saveBlock(const v3s16 &pos, const std::string &data); + virtual std::string loadBlock(const v3s16 &pos); + virtual bool deleteBlock(const v3s16 &pos); + virtual void listAllLoadableBlocks(std::vector &dst); + +private: + redisContext *ctx; + std::string hash; +}; + +#endif // USE_REDIS + +#endif + diff --git a/src/database-sqlite3.cpp b/src/database-sqlite3.cpp new file mode 100644 index 0000000..c937cae --- /dev/null +++ b/src/database-sqlite3.cpp @@ -0,0 +1,247 @@ +/* +Minetest +Copyright (C) 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. +*/ + +/* +SQLite format specification: + blocks: + (PK) INT id + BLOB data +*/ + + +#include "database-sqlite3.h" + +#include "log.h" +#include "filesys.h" +#include "exceptions.h" +#include "main.h" +#include "settings.h" +#include "util/string.h" + +#include + + +#define SQLRES(s, r) \ + if ((s) != (r)) { \ + throw FileNotGoodException(std::string(\ + "SQLite3 database error (" \ + __FILE__ ":" TOSTRING(__LINE__) \ + "): ") +\ + sqlite3_errmsg(m_database)); \ + } +#define SQLOK(s) SQLRES(s, SQLITE_OK) + +#define PREPARE_STATEMENT(name, query) \ + SQLOK(sqlite3_prepare_v2(m_database, query, -1, &m_stmt_##name, NULL)) + +#define FINALIZE_STATEMENT(statement) \ + if (sqlite3_finalize(statement) != SQLITE_OK) { \ + throw FileNotGoodException(std::string( \ + "SQLite3: Failed to finalize " #statement ": ") + \ + sqlite3_errmsg(m_database)); \ + } + + +Database_SQLite3::Database_SQLite3(const std::string &savedir) : + m_initialized(false), + m_savedir(savedir), + m_database(NULL), + m_stmt_read(NULL), + m_stmt_write(NULL), + m_stmt_list(NULL), + m_stmt_delete(NULL) +{ +} + +void Database_SQLite3::beginSave() { + verifyDatabase(); + SQLRES(sqlite3_step(m_stmt_begin), SQLITE_DONE); + sqlite3_reset(m_stmt_begin); +} + +void Database_SQLite3::endSave() { + verifyDatabase(); + SQLRES(sqlite3_step(m_stmt_end), SQLITE_DONE); + sqlite3_reset(m_stmt_end); +} + +void Database_SQLite3::openDatabase() +{ + if (m_database) return; + + std::string dbp = m_savedir + DIR_DELIM + "map.sqlite"; + + // Open the database connection + + if (!fs::CreateAllDirs(m_savedir)) { + infostream << "Database_SQLite3: Failed to create directory \"" + << m_savedir << "\"" << std::endl; + throw FileNotGoodException("Failed to create database " + "save directory"); + } + + bool needs_create = !fs::PathExists(dbp); + + if (sqlite3_open_v2(dbp.c_str(), &m_database, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + NULL) != SQLITE_OK) { + errorstream << "SQLite3 database failed to open: " + << sqlite3_errmsg(m_database) << std::endl; + throw FileNotGoodException("Cannot open database file"); + } + + if (needs_create) { + createDatabase(); + } + + std::string query_str = std::string("PRAGMA synchronous = ") + + itos(g_settings->getU16("sqlite_synchronous")); + SQLOK(sqlite3_exec(m_database, query_str.c_str(), NULL, NULL, NULL)); +} + +void Database_SQLite3::verifyDatabase() +{ + if (m_initialized) return; + + openDatabase(); + + PREPARE_STATEMENT(begin, "BEGIN"); + PREPARE_STATEMENT(end, "COMMIT"); + PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1"); +#ifdef __ANDROID__ + PREPARE_STATEMENT(write, "INSERT INTO `blocks` (`pos`, `data`) VALUES (?, ?)"); +#else + PREPARE_STATEMENT(write, "REPLACE INTO `blocks` (`pos`, `data`) VALUES (?, ?)"); +#endif + PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?"); + PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`"); + + m_initialized = true; + + verbosestream << "ServerMap: SQLite3 database opened." << std::endl; +} + +inline void Database_SQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index) +{ + SQLOK(sqlite3_bind_int64(stmt, index, getBlockAsInteger(pos))); +} + +bool Database_SQLite3::deleteBlock(const v3s16 &pos) +{ + verifyDatabase(); + + bindPos(m_stmt_delete, pos); + + bool good = sqlite3_step(m_stmt_delete) == SQLITE_DONE; + sqlite3_reset(m_stmt_delete); + + if (!good) { + errorstream << "WARNING: deleteBlock: Block failed to delete " + << PP(pos) << ": " << sqlite3_errmsg(m_database) << std::endl; + } + return good; +} + +bool Database_SQLite3::saveBlock(const v3s16 &pos, const std::string &data) +{ + verifyDatabase(); + +#ifdef __ANDROID__ + /** + * Note: For some unknown reason SQLite3 fails to REPLACE blocks on Android, + * deleting them and then inserting works. + */ + bindPos(m_stmt_read, pos); + + if (sqlite3_step(m_stmt_read) == SQLITE_ROW) { + deleteBlock(pos); + } + sqlite3_reset(m_stmt_read); +#endif + + bindPos(m_stmt_write, pos); + SQLOK(sqlite3_bind_blob(m_stmt_write, 2, data.data(), data.size(), NULL)); + + SQLRES(sqlite3_step(m_stmt_write), SQLITE_DONE) + sqlite3_reset(m_stmt_write); + + return true; +} + +std::string Database_SQLite3::loadBlock(const v3s16 &pos) +{ + verifyDatabase(); + + bindPos(m_stmt_read, pos); + + if (sqlite3_step(m_stmt_read) != SQLITE_ROW) { + sqlite3_reset(m_stmt_read); + return ""; + } + const char *data = (const char *) sqlite3_column_blob(m_stmt_read, 0); + size_t len = sqlite3_column_bytes(m_stmt_read, 0); + + std::string s; + if (data) + s = std::string(data, len); + + sqlite3_step(m_stmt_read); + // We should never get more than 1 row, so ok to reset + sqlite3_reset(m_stmt_read); + + return s; +} + +void Database_SQLite3::createDatabase() +{ + assert(m_database); // Pre-condition + SQLOK(sqlite3_exec(m_database, + "CREATE TABLE IF NOT EXISTS `blocks` (\n" + " `pos` INT PRIMARY KEY,\n" + " `data` BLOB\n" + ");\n", + NULL, NULL, NULL)); +} + +void Database_SQLite3::listAllLoadableBlocks(std::vector &dst) +{ + verifyDatabase(); + + while (sqlite3_step(m_stmt_list) == SQLITE_ROW) { + dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0))); + } + sqlite3_reset(m_stmt_list); +} + +Database_SQLite3::~Database_SQLite3() +{ + FINALIZE_STATEMENT(m_stmt_read) + FINALIZE_STATEMENT(m_stmt_write) + FINALIZE_STATEMENT(m_stmt_list) + FINALIZE_STATEMENT(m_stmt_begin) + FINALIZE_STATEMENT(m_stmt_end) + FINALIZE_STATEMENT(m_stmt_delete) + + if (sqlite3_close(m_database) != SQLITE_OK) { + errorstream << "Database_SQLite3::~Database_SQLite3(): " + << "Failed to close database: " + << sqlite3_errmsg(m_database) << std::endl; + } +} + diff --git a/src/database-sqlite3.h b/src/database-sqlite3.h new file mode 100644 index 0000000..a775742 --- /dev/null +++ b/src/database-sqlite3.h @@ -0,0 +1,69 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef DATABASE_SQLITE3_HEADER +#define DATABASE_SQLITE3_HEADER + +#include "database.h" +#include + +extern "C" { + #include "sqlite3.h" +} + +class Database_SQLite3 : public Database +{ +public: + Database_SQLite3(const std::string &savedir); + + virtual void beginSave(); + virtual void endSave(); + + virtual bool saveBlock(const v3s16 &pos, const std::string &data); + virtual std::string loadBlock(const v3s16 &pos); + virtual bool deleteBlock(const v3s16 &pos); + virtual void listAllLoadableBlocks(std::vector &dst); + virtual bool initialized() const { return m_initialized; } + ~Database_SQLite3(); + +private: + // Open the database + void openDatabase(); + // Create the database structure + void createDatabase(); + // Open and initialize the database if needed + void verifyDatabase(); + + void bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index=1); + + bool m_initialized; + + std::string m_savedir; + + sqlite3 *m_database; + sqlite3_stmt *m_stmt_read; + sqlite3_stmt *m_stmt_write; + sqlite3_stmt *m_stmt_list; + sqlite3_stmt *m_stmt_delete; + sqlite3_stmt *m_stmt_begin; + sqlite3_stmt *m_stmt_end; +}; + +#endif + diff --git a/src/database.cpp b/src/database.cpp new file mode 100644 index 0000000..262d475 --- /dev/null +++ b/src/database.cpp @@ -0,0 +1,69 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#include "database.h" +#include "irrlichttypes.h" + + +/**************** + * Black magic! * + **************** + * The position hashing is very messed up. + * It's a lot more complicated than it looks. + */ + +static inline s16 unsigned_to_signed(u16 i, u16 max_positive) +{ + if (i < max_positive) { + return i; + } else { + return i - (max_positive * 2); + } +} + + +// Modulo of a negative number does not work consistently in C +static inline s64 pythonmodulo(s64 i, s16 mod) +{ + if (i >= 0) { + return i % mod; + } + return mod - ((-i) % mod); +} + + +s64 Database::getBlockAsInteger(const v3s16 &pos) +{ + return (u64) pos.Z * 0x1000000 + + (u64) pos.Y * 0x1000 + + (u64) pos.X; +} + + +v3s16 Database::getIntegerAsBlock(s64 i) +{ + v3s16 pos; + pos.X = unsigned_to_signed(pythonmodulo(i, 4096), 2048); + i = (i - pos.X) / 4096; + pos.Y = unsigned_to_signed(pythonmodulo(i, 4096), 2048); + i = (i - pos.Y) / 4096; + pos.Z = unsigned_to_signed(pythonmodulo(i, 4096), 2048); + return pos; +} + diff --git a/src/database.h b/src/database.h new file mode 100644 index 0000000..cee7b6f --- /dev/null +++ b/src/database.h @@ -0,0 +1,53 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef DATABASE_HEADER +#define DATABASE_HEADER + +#include +#include +#include "irr_v3d.h" +#include "irrlichttypes.h" + +#ifndef PP + #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" +#endif + +class Database +{ +public: + virtual ~Database() {} + + virtual void beginSave() {} + virtual void endSave() {} + + virtual bool saveBlock(const v3s16 &pos, const std::string &data) = 0; + virtual std::string loadBlock(const v3s16 &pos) = 0; + virtual bool deleteBlock(const v3s16 &pos) = 0; + + static s64 getBlockAsInteger(const v3s16 &pos); + static v3s16 getIntegerAsBlock(s64 i); + + virtual void listAllLoadableBlocks(std::vector &dst) = 0; + + virtual bool initialized() const { return true; } +}; + +#endif + diff --git a/src/daynightratio.h b/src/daynightratio.h new file mode 100644 index 0000000..3375133 --- /dev/null +++ b/src/daynightratio.h @@ -0,0 +1,69 @@ +/* +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. +*/ + +#ifndef DAYNIGHTRATIO_HEADER +#define DAYNIGHTRATIO_HEADER + +inline u32 time_to_daynight_ratio(float time_of_day, bool smooth) +{ + float t = time_of_day; + if(t < 0) + t += ((int)(-t)/24000)*24000; + if(t >= 24000) + t -= ((int)(t)/24000)*24000; + if(t > 12000) + t = 24000 - t; + float values[][2] = { + {4250+125, 150}, + {4500+125, 150}, + {4750+125, 250}, + {5000+125, 350}, + {5250+125, 500}, + {5500+125, 675}, + {5750+125, 875}, + {6000+125, 1000}, + {6250+125, 1000}, + }; + if(!smooth){ + float lastt = values[0][0]; + for(u32 i=1; i + +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. +*/ + + +#include "porting.h" +#include "debug.h" +#include "exceptions.h" +#include "threads.h" +#include +#include +#include +#include +#include "jthread/jmutex.h" +#include "jthread/jmutexautolock.h" +#include "config.h" + +#ifdef _MSC_VER + #include + #include "version.h" + #include "filesys.h" +#endif + +/* + Debug output +*/ + +#define DEBUGSTREAM_COUNT 2 + +FILE *g_debugstreams[DEBUGSTREAM_COUNT] = {stderr, NULL}; + +#define DEBUGPRINT(...)\ +{\ + for(int i=0; i g_debug_stacks; +JMutex g_debug_stacks_mutex; + +void debug_stacks_init() +{ +} + +void debug_stacks_print_to(std::ostream &os) +{ + JMutexAutoLock lock(g_debug_stacks_mutex); + + os<<"Debug stacks:"<::iterator + i = g_debug_stacks.begin(); + i != g_debug_stacks.end(); ++i) + { + i->second->print(os, false); + } +} + +void debug_stacks_print() +{ + JMutexAutoLock lock(g_debug_stacks_mutex); + + DEBUGPRINT("Debug stacks:\n"); + + for(std::map::iterator + i = g_debug_stacks.begin(); + i != g_debug_stacks.end(); ++i) + { + DebugStack *stack = i->second; + + for(int i=0; iprint(g_debugstreams[i], true); + } + } +} + +DebugStacker::DebugStacker(const char *text) +{ + threadid_t threadid = get_current_thread_id(); + + JMutexAutoLock lock(g_debug_stacks_mutex); + + std::map::iterator n; + n = g_debug_stacks.find(threadid); + if(n != g_debug_stacks.end()) + { + m_stack = n->second; + } + else + { + /*DEBUGPRINT("Creating new debug stack for thread %x\n", + (unsigned int)threadid);*/ + m_stack = new DebugStack(threadid); + g_debug_stacks[threadid] = m_stack; + } + + if(m_stack->stack_i >= DEBUG_STACK_SIZE) + { + m_overflowed = true; + } + else + { + m_overflowed = false; + + snprintf(m_stack->stack[m_stack->stack_i], + DEBUG_STACK_TEXT_SIZE, "%s", text); + m_stack->stack_i++; + if(m_stack->stack_i > m_stack->stack_max_i) + m_stack->stack_max_i = m_stack->stack_i; + } +} + +DebugStacker::~DebugStacker() +{ + JMutexAutoLock lock(g_debug_stacks_mutex); + + if(m_overflowed == true) + return; + + m_stack->stack_i--; + + if(m_stack->stack_i == 0) + { + threadid_t threadid = m_stack->threadid; + /*DEBUGPRINT("Deleting debug stack for thread %x\n", + (unsigned int)threadid);*/ + delete m_stack; + g_debug_stacks.erase(threadid); + } +} + +#ifdef _MSC_VER + +const char *Win32ExceptionCodeToString(DWORD exception_code) +{ + switch (exception_code) { + case EXCEPTION_ACCESS_VIOLATION: + return "Access violation"; + case EXCEPTION_DATATYPE_MISALIGNMENT: + return "Misaligned data access"; + case EXCEPTION_BREAKPOINT: + return "Breakpoint reached"; + case EXCEPTION_SINGLE_STEP: + return "Single debug step"; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + return "Array access out of bounds"; + case EXCEPTION_FLT_DENORMAL_OPERAND: + return "Denormal floating point operand"; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + return "Floating point division by zero"; + case EXCEPTION_FLT_INEXACT_RESULT: + return "Inaccurate floating point result"; + case EXCEPTION_FLT_INVALID_OPERATION: + return "Invalid floating point operation"; + case EXCEPTION_FLT_OVERFLOW: + return "Floating point exponent overflow"; + case EXCEPTION_FLT_STACK_CHECK: + return "Floating point stack overflow or underflow"; + case EXCEPTION_FLT_UNDERFLOW: + return "Floating point exponent underflow"; + case EXCEPTION_INT_DIVIDE_BY_ZERO: + return "Integer division by zero"; + case EXCEPTION_INT_OVERFLOW: + return "Integer overflow"; + case EXCEPTION_PRIV_INSTRUCTION: + return "Privileged instruction executed"; + case EXCEPTION_IN_PAGE_ERROR: + return "Could not access or load page"; + case EXCEPTION_ILLEGAL_INSTRUCTION: + return "Illegal instruction encountered"; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: + return "Attempted to continue after fatal exception"; + case EXCEPTION_STACK_OVERFLOW: + return "Stack overflow"; + case EXCEPTION_INVALID_DISPOSITION: + return "Invalid disposition returned to the exception dispatcher"; + case EXCEPTION_GUARD_PAGE: + return "Attempted guard page access"; + case EXCEPTION_INVALID_HANDLE: + return "Invalid handle"; + } + + return "Unknown exception"; +} + +long WINAPI Win32ExceptionHandler(struct _EXCEPTION_POINTERS *pExceptInfo) +{ + char buf[512]; + MINIDUMP_EXCEPTION_INFORMATION mdei; + MINIDUMP_USER_STREAM_INFORMATION mdusi; + MINIDUMP_USER_STREAM mdus; + bool minidump_created = false; + std::string version_str("Blokel "); + + std::string dumpfile = porting::path_user + DIR_DELIM "minetest.dmp"; + + HANDLE hFile = CreateFileA(dumpfile.c_str(), GENERIC_WRITE, + FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) + goto minidump_failed; + + if (SetEndOfFile(hFile) == FALSE) + goto minidump_failed; + + mdei.ClientPointers = NULL; + mdei.ExceptionPointers = pExceptInfo; + mdei.ThreadId = GetCurrentThreadId(); + + version_str += minetest_version_hash; + + mdus.Type = CommentStreamA; + mdus.BufferSize = version_str.size(); + mdus.Buffer = (PVOID)version_str.c_str(); + + mdusi.UserStreamArray = &mdus; + mdusi.UserStreamCount = 1; + + if (MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, + MiniDumpNormal, &mdei, &mdusi, NULL) == FALSE) + goto minidump_failed; + + minidump_created = true; + +minidump_failed: + + CloseHandle(hFile); + + DWORD excode = pExceptInfo->ExceptionRecord->ExceptionCode; + _snprintf(buf, sizeof(buf), + " >> === FATAL ERROR ===\n" + " >> %s (Exception 0x%08X) at 0x%p\n", + Win32ExceptionCodeToString(excode), excode, + pExceptInfo->ExceptionRecord->ExceptionAddress); + dstream << buf; + + if (minidump_created) + dstream << " >> Saved dump to " << dumpfile << std::endl; + else + dstream << " >> Failed to save dump" << std::endl; + + return EXCEPTION_EXECUTE_HANDLER; +} + +#endif + +void debug_set_exception_handler() +{ +#ifdef _MSC_VER + SetUnhandledExceptionFilter(Win32ExceptionHandler); +#endif +} + diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 0000000..9684aa2 --- /dev/null +++ b/src/debug.h @@ -0,0 +1,162 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef DEBUG_HEADER +#define DEBUG_HEADER + +#include +#include +#include +#include "gettime.h" + +#if (defined(WIN32) || defined(_WIN32_WCE)) + #define WIN32_LEAN_AND_MEAN + #ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0501 + #endif + #include + #ifdef _MSC_VER + #include + #endif + #define __NORETURN __declspec(noreturn) + #define __FUNCTION_NAME __FUNCTION__ +#else + #define __NORETURN __attribute__ ((__noreturn__)) + #define __FUNCTION_NAME __PRETTY_FUNCTION__ +#endif + +// Whether to catch all std::exceptions. +// Assert will be called on such an event. +// In debug mode, leave these for the debugger and don't catch them. +#ifdef NDEBUG + #define CATCH_UNHANDLED_EXCEPTIONS 1 +#else + #define CATCH_UNHANDLED_EXCEPTIONS 0 +#endif + +/* + Debug output +*/ + +#define DTIME (getTimestamp()+": ") + +extern void debugstreams_init(bool disable_stderr, const char *filename); +extern void debugstreams_deinit(); + +// This is used to redirect output to /dev/null +class Nullstream : public std::ostream { +public: + Nullstream(): + std::ostream(0) + { + } +private: +}; + +extern std::ostream dstream; +extern std::ostream dstream_no_stderr; +extern Nullstream dummyout; + + +/* Abort program execution immediately + */ +__NORETURN extern void fatal_error_fn( + const char *msg, const char *file, + unsigned int line, const char *function); + +#define FATAL_ERROR(msg) \ + fatal_error_fn((msg), __FILE__, __LINE__, __FUNCTION_NAME) + +#define FATAL_ERROR_IF(expr, msg) \ + ((expr) \ + ? fatal_error_fn((msg), __FILE__, __LINE__, __FUNCTION_NAME) \ + : (void)(0)) + +/* + sanity_check() + Equivalent to assert() but persists in Release builds (i.e. when NDEBUG is + defined) +*/ + +__NORETURN extern void sanity_check_fn( + const char *assertion, const char *file, + unsigned int line, const char *function); + +#define SANITY_CHECK(expr) \ + ((expr) \ + ? (void)(0) \ + : sanity_check_fn(#expr, __FILE__, __LINE__, __FUNCTION_NAME)) + +#define sanity_check(expr) SANITY_CHECK(expr) + + +void debug_set_exception_handler(); + +/* + DebugStack +*/ + +#define DEBUG_STACK_SIZE 50 +#define DEBUG_STACK_TEXT_SIZE 300 + +extern void debug_stacks_init(); +extern void debug_stacks_print_to(std::ostream &os); +extern void debug_stacks_print(); + +struct DebugStack; +class DebugStacker +{ +public: + DebugStacker(const char *text); + ~DebugStacker(); + +private: + DebugStack *m_stack; + bool m_overflowed; +}; + +#define DSTACK(msg) \ + DebugStacker __debug_stacker(msg); + +#define DSTACKF(...) \ + char __buf[DEBUG_STACK_TEXT_SIZE]; \ + snprintf(__buf, DEBUG_STACK_TEXT_SIZE, __VA_ARGS__); \ + DebugStacker __debug_stacker(__buf); + +/* + These should be put into every thread +*/ + +#if CATCH_UNHANDLED_EXCEPTIONS == 1 + #define BEGIN_DEBUG_EXCEPTION_HANDLER try { + #define END_DEBUG_EXCEPTION_HANDLER(logstream) \ + } catch (std::exception &e) { \ + logstream << "ERROR: An unhandled exception occurred: " \ + << e.what() << std::endl; \ + assert(0); \ + } +#else + // Dummy ones + #define BEGIN_DEBUG_EXCEPTION_HANDLER + #define END_DEBUG_EXCEPTION_HANDLER(logstream) +#endif + +#endif // DEBUG_HEADER + + diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp new file mode 100644 index 0000000..803357d --- /dev/null +++ b/src/defaultsettings.cpp @@ -0,0 +1,354 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#include "settings.h" +#include "porting.h" +#include "filesys.h" +#include "config.h" +#include "constants.h" +#include "porting.h" + +void set_default_settings(Settings *settings) +{ + // Client and server + + settings->setDefault("name", ""); + + // Client stuff + settings->setDefault("remote_port", "30000"); + settings->setDefault("keymap_forward", "KEY_KEY_W"); + settings->setDefault("keymap_backward", "KEY_KEY_S"); + settings->setDefault("keymap_left", "KEY_KEY_A"); + settings->setDefault("keymap_right", "KEY_KEY_D"); + settings->setDefault("keymap_jump", "KEY_SPACE"); + settings->setDefault("keymap_sneak", "KEY_LSHIFT"); + settings->setDefault("keymap_drop", "KEY_KEY_Q"); + settings->setDefault("keymap_inventory", "KEY_KEY_I"); + settings->setDefault("keymap_special1", "KEY_KEY_E"); + settings->setDefault("keymap_chat", "KEY_KEY_T"); + settings->setDefault("keymap_cmd", "/"); + settings->setDefault("keymap_console", "KEY_F10"); + settings->setDefault("keymap_rangeselect", "KEY_KEY_R"); + settings->setDefault("keymap_freemove", "KEY_KEY_K"); + settings->setDefault("keymap_fastmove", "KEY_KEY_J"); + settings->setDefault("keymap_noclip", "KEY_KEY_H"); + settings->setDefault("keymap_cinematic", "KEY_F8"); + settings->setDefault("keymap_screenshot", "KEY_F12"); + settings->setDefault("keymap_toggle_hud", "KEY_F1"); + settings->setDefault("keymap_toggle_chat", "KEY_F2"); + settings->setDefault("keymap_toggle_force_fog_off", "KEY_F3"); + settings->setDefault("keymap_toggle_update_camera", +#if DEBUG + "KEY_F4"); +#else + "none"); +#endif + settings->setDefault("keymap_toggle_debug", "KEY_F5"); + settings->setDefault("keymap_toggle_profiler", "KEY_F6"); + settings->setDefault("keymap_camera_mode", "KEY_F7"); + settings->setDefault("keymap_increase_viewing_range_min", "+"); + settings->setDefault("keymap_decrease_viewing_range_min", "-"); + settings->setDefault("enable_build_where_you_stand", "false" ); + settings->setDefault("3d_mode", "none"); + settings->setDefault("3d_paralax_strength", "0.025"); + settings->setDefault("aux1_descends", "false"); + settings->setDefault("doubletap_jump", "false"); + settings->setDefault("always_fly_fast", "true"); + settings->setDefault("directional_colored_fog", "true"); + settings->setDefault("tooltip_show_delay", "400"); + + // Some (temporary) keys for debugging + settings->setDefault("keymap_print_debug_stacks", "KEY_KEY_P"); + settings->setDefault("keymap_quicktune_prev", "KEY_HOME"); + settings->setDefault("keymap_quicktune_next", "KEY_END"); + settings->setDefault("keymap_quicktune_dec", "KEY_NEXT"); + settings->setDefault("keymap_quicktune_inc", "KEY_PRIOR"); + + // Show debug info by default? + #ifdef NDEBUG + settings->setDefault("show_debug", "false"); + #else + settings->setDefault("show_debug", "true"); + #endif + + settings->setDefault("wanted_fps", "30"); + settings->setDefault("fps_max", "60"); + settings->setDefault("pause_fps_max", "20"); + // A bit more than the server will send around the player, to make fog blend well + settings->setDefault("viewing_range_nodes_max", "240"); + settings->setDefault("viewing_range_nodes_min", "35"); + settings->setDefault("screenW", "800"); + settings->setDefault("screenH", "600"); + settings->setDefault("fullscreen", "false"); + settings->setDefault("fullscreen_bpp", "24"); + settings->setDefault("fsaa", "0"); + settings->setDefault("vsync", "false"); + settings->setDefault("address", ""); + settings->setDefault("random_input", "false"); + settings->setDefault("client_unload_unused_data_timeout", "600"); + settings->setDefault("enable_fog", "true"); + settings->setDefault("fov", "72"); + settings->setDefault("view_bobbing", "true"); + settings->setDefault("new_style_water", "false"); + settings->setDefault("new_style_leaves", "true"); + settings->setDefault("connected_glass", "false"); + settings->setDefault("smooth_lighting", "true"); + settings->setDefault("display_gamma", "1.8"); + settings->setDefault("texture_path", ""); + settings->setDefault("shader_path", ""); + settings->setDefault("video_driver", "opengl"); + settings->setDefault("free_move", "false"); + settings->setDefault("noclip", "false"); + settings->setDefault("continuous_forward", "false"); + settings->setDefault("cinematic", "false"); + settings->setDefault("camera_smoothing", "0"); + settings->setDefault("cinematic_camera_smoothing", "0.7"); + settings->setDefault("fast_move", "false"); + settings->setDefault("invert_mouse", "false"); + settings->setDefault("enable_clouds", "true"); + settings->setDefault("screenshot_path", "."); + settings->setDefault("view_bobbing_amount", "1.0"); + settings->setDefault("fall_bobbing_amount", "0.0"); + settings->setDefault("enable_3d_clouds", "true"); + settings->setDefault("cloud_height", "120"); + settings->setDefault("menu_clouds", "true"); + settings->setDefault("opaque_water", "false"); + settings->setDefault("console_color", "(0,0,0)"); + settings->setDefault("console_alpha", "200"); + settings->setDefault("selectionbox_color", "(0,0,0)"); + settings->setDefault("enable_node_highlighting", "true"); + settings->setDefault("crosshair_color", "(255,255,255)"); + settings->setDefault("crosshair_alpha", "255"); + settings->setDefault("hud_scaling", "1.0"); + settings->setDefault("gui_scaling", "1.0"); + settings->setDefault("mouse_sensitivity", "0.2"); + settings->setDefault("enable_sound", "true"); + settings->setDefault("sound_volume", "0.8"); + settings->setDefault("desynchronize_mapblock_texture_animation", "true"); + settings->setDefault("selectionbox_width","2"); + settings->setDefault("hud_hotbar_max_width","1.0"); + settings->setDefault("enable_local_map_saving", "false"); + + settings->setDefault("mip_map", "false"); + settings->setDefault("anisotropic_filter", "false"); + settings->setDefault("bilinear_filter", "false"); + settings->setDefault("trilinear_filter", "false"); + settings->setDefault("preload_item_visuals", "false"); + settings->setDefault("enable_bumpmapping", "false"); + settings->setDefault("enable_parallax_occlusion", "false"); + settings->setDefault("generate_normalmaps", "false"); + settings->setDefault("normalmaps_strength", "0.6"); + settings->setDefault("normalmaps_smooth", "1"); + settings->setDefault("parallax_occlusion_scale", "0.06"); + settings->setDefault("parallax_occlusion_bias", "0.03"); + settings->setDefault("enable_waving_water", "false"); + settings->setDefault("water_wave_height", "1.0"); + settings->setDefault("water_wave_length", "20.0"); + settings->setDefault("water_wave_speed", "5.0"); + settings->setDefault("enable_waving_leaves", "false"); + settings->setDefault("enable_waving_plants", "false"); + settings->setDefault("ambient_occlusion_gamma", "2.2"); + settings->setDefault("enable_shaders", "true"); + settings->setDefault("repeat_rightclick_time", "0.25"); + settings->setDefault("enable_particles", "true"); + settings->setDefault("enable_mesh_cache", "true"); + + settings->setDefault("curl_timeout", "5000"); + settings->setDefault("curl_parallel_limit", "8"); + settings->setDefault("curl_file_download_timeout", "300000"); + settings->setDefault("curl_verify_cert", "true"); + + settings->setDefault("enable_remote_media_server", "true"); + + settings->setDefault("serverlist_url", "servers.minetest.net"); + settings->setDefault("serverlist_file", "favoriteservers.txt"); + settings->setDefault("server_announce", "false"); + settings->setDefault("server_url", ""); + settings->setDefault("server_address", ""); + settings->setDefault("server_name", ""); + settings->setDefault("server_description", ""); + +#if USE_FREETYPE + settings->setDefault("freetype", "true"); + settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "liberationsans.ttf")); + settings->setDefault("font_shadow", "1"); + settings->setDefault("font_shadow_alpha", "128"); + settings->setDefault("mono_font_path", porting::getDataPath("fonts" DIR_DELIM "liberationmono.ttf")); + settings->setDefault("fallback_font_path", porting::getDataPath("fonts" DIR_DELIM "DroidSansFallbackFull.ttf")); + + settings->setDefault("fallback_font_shadow", "1"); + settings->setDefault("fallback_font_shadow_alpha", "128"); + + std::stringstream fontsize; + fontsize << TTF_DEFAULT_FONT_SIZE; + + settings->setDefault("font_size", fontsize.str()); + settings->setDefault("mono_font_size", fontsize.str()); + settings->setDefault("fallback_font_size", fontsize.str()); +#else + settings->setDefault("freetype", "false"); + settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "lucida_sans")); + settings->setDefault("mono_font_path", porting::getDataPath("fonts" DIR_DELIM "mono_dejavu_sans")); + + std::stringstream fontsize; + fontsize << DEFAULT_FONT_SIZE; + + settings->setDefault("font_size", fontsize.str()); + settings->setDefault("mono_font_size", fontsize.str()); +#endif + + // Server stuff + // "map-dir" doesn't exist by default. + settings->setDefault("workaround_window_size","5"); + settings->setDefault("max_packets_per_iteration","1024"); + settings->setDefault("port", "30000"); + settings->setDefault("bind_address", ""); + settings->setDefault("default_game", "minetest"); + settings->setDefault("motd", ""); + settings->setDefault("max_users", "15"); + settings->setDefault("strict_protocol_version_checking", "false"); + settings->setDefault("creative_mode", "false"); + settings->setDefault("enable_damage", "true"); + settings->setDefault("fixed_map_seed", ""); + settings->setDefault("give_initial_stuff", "false"); + settings->setDefault("default_password", ""); + settings->setDefault("default_privs", "interact, shout"); + settings->setDefault("player_transfer_distance", "0"); + settings->setDefault("enable_pvp", "true"); + settings->setDefault("disallow_empty_password", "false"); + settings->setDefault("disable_anticheat", "false"); + settings->setDefault("enable_rollback_recording", "false"); +#ifdef NDEBUG + settings->setDefault("deprecated_lua_api_handling", "legacy"); +#else + settings->setDefault("deprecated_lua_api_handling", "log"); +#endif + + settings->setDefault("profiler_print_interval", "0"); + settings->setDefault("enable_mapgen_debug_info", "false"); + settings->setDefault("active_object_send_range_blocks", "3"); + settings->setDefault("active_block_range", "2"); + //settings->setDefault("max_simultaneous_block_sends_per_client", "1"); + // This causes frametime jitter on client side, or does it? + settings->setDefault("max_simultaneous_block_sends_per_client", "10"); + settings->setDefault("max_simultaneous_block_sends_server_total", "40"); + settings->setDefault("max_block_send_distance", "9"); + settings->setDefault("max_block_generate_distance", "7"); + settings->setDefault("max_clearobjects_extra_loaded_blocks", "4096"); + settings->setDefault("time_send_interval", "5"); + settings->setDefault("time_speed", "72"); + settings->setDefault("year_days", "30"); + settings->setDefault("server_unload_unused_data_timeout", "29"); + settings->setDefault("max_objects_per_block", "49"); + settings->setDefault("server_map_save_interval", "5.3"); + settings->setDefault("sqlite_synchronous", "2"); + settings->setDefault("full_block_send_enable_min_time_from_building", "2.0"); + settings->setDefault("dedicated_server_step", "0.1"); + settings->setDefault("ignore_world_load_errors", "false"); + settings->setDefault("remote_media", ""); + settings->setDefault("debug_log_level", "2"); + settings->setDefault("emergequeue_limit_total", "256"); + settings->setDefault("emergequeue_limit_diskonly", "32"); + settings->setDefault("emergequeue_limit_generate", "32"); + settings->setDefault("num_emerge_threads", "1"); + + // physics stuff + settings->setDefault("movement_acceleration_default", "3"); + settings->setDefault("movement_acceleration_air", "2"); + settings->setDefault("movement_acceleration_fast", "10"); + settings->setDefault("movement_speed_walk", "4"); + settings->setDefault("movement_speed_crouch", "1.35"); + settings->setDefault("movement_speed_fast", "20"); + settings->setDefault("movement_speed_climb", "2"); + settings->setDefault("movement_speed_jump", "6.5"); + settings->setDefault("movement_liquid_fluidity", "1"); + settings->setDefault("movement_liquid_fluidity_smooth", "0.5"); + settings->setDefault("movement_liquid_sink", "10"); + settings->setDefault("movement_gravity", "9.81"); + + //liquid stuff + settings->setDefault("liquid_loop_max", "100000"); + settings->setDefault("liquid_queue_purge_time", "0"); + settings->setDefault("liquid_update", "1.0"); + + //mapgen stuff + settings->setDefault("mg_name", "v6"); + settings->setDefault("water_level", "1"); + settings->setDefault("chunksize", "5"); + settings->setDefault("mg_flags", ""); + settings->setDefault("enable_floating_dungeons", "true"); + + // IPv6 + settings->setDefault("enable_ipv6", "true"); + settings->setDefault("ipv6_server", "false"); + + settings->setDefault("main_menu_path", ""); + settings->setDefault("main_menu_mod_mgr", "1"); + settings->setDefault("main_menu_game_mgr", "0"); + settings->setDefault("modstore_download_url", "https://forum.minetest.net/media/"); + settings->setDefault("modstore_listmods_url", "https://forum.minetest.net/mmdb/mods/"); + settings->setDefault("modstore_details_url", "https://forum.minetest.net/mmdb/mod/*/"); + + settings->setDefault("high_precision_fpu", "true"); + + settings->setDefault("language", ""); + +#ifdef __ANDROID__ + settings->setDefault("screenW", "0"); + settings->setDefault("screenH", "0"); + settings->setDefault("enable_shaders", "false"); + settings->setDefault("fullscreen", "true"); + settings->setDefault("enable_particles", "false"); + settings->setDefault("video_driver", "ogles1"); + settings->setDefault("touchtarget", "true"); + settings->setDefault("TMPFolder","/sdcard/Minetest/tmp/"); + settings->setDefault("touchscreen_threshold","20"); + settings->setDefault("smooth_lighting", "false"); + settings->setDefault("max_simultaneous_block_sends_per_client", "3"); + settings->setDefault("emergequeue_limit_diskonly", "8"); + settings->setDefault("emergequeue_limit_generate", "8"); + settings->setDefault("preload_item_visuals", "false"); + + settings->setDefault("viewing_range_nodes_max", "50"); + settings->setDefault("viewing_range_nodes_min", "20"); + settings->setDefault("inventory_image_hack", "false"); + + //check for device with small screen + float x_inches = ((double) porting::getDisplaySize().X / + (160 * porting::getDisplayDensity())); + if (x_inches < 3.5) { + settings->setDefault("hud_scaling", "0.6"); + } + else if (x_inches < 4.5) { + settings->setDefault("hud_scaling", "0.7"); + } + settings->setDefault("curl_verify_cert","false"); +#else + settings->setDefault("screen_dpi", "72"); +#endif +} + +void override_default_settings(Settings *settings, Settings *from) +{ + std::vector names = from->getNames(); + for(size_t i=0; isetDefault(name, from->get(name)); + } +} diff --git a/src/defaultsettings.h b/src/defaultsettings.h new file mode 100644 index 0000000..20274a0 --- /dev/null +++ b/src/defaultsettings.h @@ -0,0 +1,39 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef DEFAULTSETTINGS_HEADER +#define DEFAULTSETTINGS_HEADER + +class Settings; + +/** + * initialize basic default settings + * @param settings pointer to settings + */ +void set_default_settings(Settings *settings); + +/** + * override a default settings by settings from another settings element + * @param settings target settings pointer + * @param from source settings pointer + */ +void override_default_settings(Settings *settings, Settings *from); + +#endif + diff --git a/src/drawscene.cpp b/src/drawscene.cpp new file mode 100644 index 0000000..b089e71 --- /dev/null +++ b/src/drawscene.cpp @@ -0,0 +1,552 @@ +/* +Minetest +Copyright (C) 2010-2014 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. +*/ + +#include "drawscene.h" +#include "main.h" // for g_settings +#include "settings.h" +#include "clouds.h" +#include "clientmap.h" +#include "util/timetaker.h" +#include "fontengine.h" + +typedef enum { + LEFT = -1, + RIGHT = 1, + EYECOUNT = 2 +} paralax_sign; + + +void draw_selectionbox(video::IVideoDriver* driver, Hud& hud, + std::vector& hilightboxes, bool show_hud) +{ + static const s16 selectionbox_width = rangelim(g_settings->getS16("selectionbox_width"), 1, 5); + + if (!show_hud) + return; + + video::SMaterial oldmaterial = driver->getMaterial2D(); + video::SMaterial m; + m.Thickness = selectionbox_width; + m.Lighting = false; + driver->setMaterial(m); + hud.drawSelectionBoxes(hilightboxes); + driver->setMaterial(oldmaterial); +} + +void draw_anaglyph_3d_mode(Camera& camera, bool show_hud, Hud& hud, + std::vector hilightboxes, video::IVideoDriver* driver, + scene::ISceneManager* smgr, bool draw_wield_tool, Client& client, + gui::IGUIEnvironment* guienv ) +{ + + /* preserve old setup*/ + irr::core::vector3df oldPosition = camera.getCameraNode()->getPosition(); + irr::core::vector3df oldTarget = camera.getCameraNode()->getTarget(); + + irr::core::matrix4 startMatrix = + camera.getCameraNode()->getAbsoluteTransformation(); + irr::core::vector3df focusPoint = (camera.getCameraNode()->getTarget() + - camera.getCameraNode()->getAbsolutePosition()).setLength(1) + + camera.getCameraNode()->getAbsolutePosition(); + + + //Left eye... + irr::core::vector3df leftEye; + irr::core::matrix4 leftMove; + leftMove.setTranslation( + irr::core::vector3df(-g_settings->getFloat("3d_paralax_strength"), + 0.0f, 0.0f)); + leftEye = (startMatrix * leftMove).getTranslation(); + + //clear the depth buffer, and color + driver->beginScene( true, true, irr::video::SColor(0, 200, 200, 255)); + driver->getOverrideMaterial().Material.ColorMask = irr::video::ECP_RED; + driver->getOverrideMaterial().EnableFlags = irr::video::EMF_COLOR_MASK; + driver->getOverrideMaterial().EnablePasses = irr::scene::ESNRP_SKY_BOX + + irr::scene::ESNRP_SOLID + irr::scene::ESNRP_TRANSPARENT + + irr::scene::ESNRP_TRANSPARENT_EFFECT + irr::scene::ESNRP_SHADOW; + camera.getCameraNode()->setPosition(leftEye); + camera.getCameraNode()->setTarget(focusPoint); + smgr->drawAll(); + driver->setTransform(video::ETS_WORLD, core::IdentityMatrix); + if (show_hud) + { + draw_selectionbox(driver, hud, hilightboxes, show_hud); + + if (draw_wield_tool) + camera.drawWieldedTool(&leftMove); + } + + guienv->drawAll(); + + //Right eye... + irr::core::vector3df rightEye; + irr::core::matrix4 rightMove; + rightMove.setTranslation( + irr::core::vector3df(g_settings->getFloat("3d_paralax_strength"), + 0.0f, 0.0f)); + rightEye = (startMatrix * rightMove).getTranslation(); + + //clear the depth buffer + driver->clearZBuffer(); + driver->getOverrideMaterial().Material.ColorMask = irr::video::ECP_GREEN + + irr::video::ECP_BLUE; + driver->getOverrideMaterial().EnableFlags = irr::video::EMF_COLOR_MASK; + driver->getOverrideMaterial().EnablePasses = irr::scene::ESNRP_SKY_BOX + + irr::scene::ESNRP_SOLID + irr::scene::ESNRP_TRANSPARENT + + irr::scene::ESNRP_TRANSPARENT_EFFECT + irr::scene::ESNRP_SHADOW; + camera.getCameraNode()->setPosition(rightEye); + camera.getCameraNode()->setTarget(focusPoint); + smgr->drawAll(); + driver->setTransform(video::ETS_WORLD, core::IdentityMatrix); + if (show_hud) + { + draw_selectionbox(driver, hud, hilightboxes, show_hud); + + if (draw_wield_tool) + camera.drawWieldedTool(&rightMove); + } + + guienv->drawAll(); + + driver->getOverrideMaterial().Material.ColorMask = irr::video::ECP_ALL; + driver->getOverrideMaterial().EnableFlags = 0; + driver->getOverrideMaterial().EnablePasses = 0; + camera.getCameraNode()->setPosition(oldPosition); + camera.getCameraNode()->setTarget(oldTarget); +} + +void init_texture(video::IVideoDriver* driver, const v2u32& screensize, + video::ITexture** texture, const char* name) +{ + if (*texture != NULL) + { + driver->removeTexture(*texture); + } + *texture = driver->addRenderTargetTexture( + core::dimension2d(screensize.X, screensize.Y), name, + irr::video::ECF_A8R8G8B8); +} + +video::ITexture* draw_image(const v2u32& screensize, + paralax_sign psign, const irr::core::matrix4& startMatrix, + const irr::core::vector3df& focusPoint, bool show_hud, + video::IVideoDriver* driver, Camera& camera, scene::ISceneManager* smgr, + Hud& hud, std::vector& hilightboxes, + bool draw_wield_tool, Client& client, gui::IGUIEnvironment* guienv, + video::SColor skycolor ) +{ + static video::ITexture* images[2] = { NULL, NULL }; + static v2u32 last_screensize = v2u32(0,0); + + video::ITexture* image = NULL; + + if (screensize != last_screensize) { + init_texture(driver, screensize, &images[1], "mt_drawimage_img1"); + init_texture(driver, screensize, &images[0], "mt_drawimage_img2"); + last_screensize = screensize; + } + + if (psign == RIGHT) + image = images[1]; + else + image = images[0]; + + driver->setRenderTarget(image, true, true, + irr::video::SColor(255, + skycolor.getRed(), skycolor.getGreen(), skycolor.getBlue())); + + irr::core::vector3df eye_pos; + irr::core::matrix4 movement; + movement.setTranslation( + irr::core::vector3df((int) psign * + g_settings->getFloat("3d_paralax_strength"), 0.0f, 0.0f)); + eye_pos = (startMatrix * movement).getTranslation(); + + //clear the depth buffer + driver->clearZBuffer(); + camera.getCameraNode()->setPosition(eye_pos); + camera.getCameraNode()->setTarget(focusPoint); + smgr->drawAll(); + + driver->setTransform(video::ETS_WORLD, core::IdentityMatrix); + + if (show_hud) + { + draw_selectionbox(driver, hud, hilightboxes, show_hud); + + if (draw_wield_tool) + camera.drawWieldedTool(&movement); + } + + guienv->drawAll(); + + /* switch back to real renderer */ + driver->setRenderTarget(0, true, true, + irr::video::SColor(0, + skycolor.getRed(), skycolor.getGreen(), skycolor.getBlue())); + + return image; +} + +video::ITexture* draw_hud(video::IVideoDriver* driver, const v2u32& screensize, + bool show_hud, Hud& hud, Client& client, bool draw_crosshair, + video::SColor skycolor, gui::IGUIEnvironment* guienv, Camera& camera ) +{ + static video::ITexture* image = NULL; + init_texture(driver, screensize, &image, "mt_drawimage_hud"); + driver->setRenderTarget(image, true, true, + irr::video::SColor(255,0,0,0)); + + if (show_hud) + { + if (draw_crosshair) + hud.drawCrosshair(); + hud.drawHotbar(client.getPlayerItem()); + hud.drawLuaElements(camera.getOffset()); + + guienv->drawAll(); + } + + driver->setRenderTarget(0, true, true, + irr::video::SColor(0, + skycolor.getRed(), skycolor.getGreen(), skycolor.getBlue())); + + return image; +} + +void draw_interlaced_3d_mode(Camera& camera, bool show_hud, + Hud& hud, std::vector hilightboxes, video::IVideoDriver* driver, + scene::ISceneManager* smgr, const v2u32& screensize, + bool draw_wield_tool, Client& client, gui::IGUIEnvironment* guienv, + video::SColor skycolor ) +{ + /* save current info */ + irr::core::vector3df oldPosition = camera.getCameraNode()->getPosition(); + irr::core::vector3df oldTarget = camera.getCameraNode()->getTarget(); + irr::core::matrix4 startMatrix = + camera.getCameraNode()->getAbsoluteTransformation(); + irr::core::vector3df focusPoint = (camera.getCameraNode()->getTarget() + - camera.getCameraNode()->getAbsolutePosition()).setLength(1) + + camera.getCameraNode()->getAbsolutePosition(); + + /* create left view */ + video::ITexture* left_image = draw_image(screensize, LEFT, startMatrix, + focusPoint, show_hud, driver, camera, smgr, hud, hilightboxes, + draw_wield_tool, client, guienv, skycolor); + + //Right eye... + irr::core::vector3df rightEye; + irr::core::matrix4 rightMove; + rightMove.setTranslation( + irr::core::vector3df(g_settings->getFloat("3d_paralax_strength"), + 0.0f, 0.0f)); + rightEye = (startMatrix * rightMove).getTranslation(); + + //clear the depth buffer + driver->clearZBuffer(); + camera.getCameraNode()->setPosition(rightEye); + camera.getCameraNode()->setTarget(focusPoint); + smgr->drawAll(); + + driver->setTransform(video::ETS_WORLD, core::IdentityMatrix); + + if (show_hud) + { + draw_selectionbox(driver, hud, hilightboxes, show_hud); + + if(draw_wield_tool) + camera.drawWieldedTool(&rightMove); + } + guienv->drawAll(); + + for (unsigned int i = 0; i < screensize.Y; i+=2 ) { +#if (IRRLICHT_VERSION_MAJOR >= 1) && (IRRLICHT_VERSION_MINOR >= 8) + driver->draw2DImage(left_image, irr::core::position2d(0, i), +#else + driver->draw2DImage(left_image, irr::core::position2d(0, screensize.Y-i), +#endif + irr::core::rect(0, i,screensize.X, i+1), 0, + irr::video::SColor(255, 255, 255, 255), + false); + } + + /* cleanup */ + camera.getCameraNode()->setPosition(oldPosition); + camera.getCameraNode()->setTarget(oldTarget); +} + +void draw_sidebyside_3d_mode(Camera& camera, bool show_hud, + Hud& hud, std::vector hilightboxes, video::IVideoDriver* driver, + scene::ISceneManager* smgr, const v2u32& screensize, + bool draw_wield_tool, Client& client, gui::IGUIEnvironment* guienv, + video::SColor skycolor ) +{ + /* save current info */ + irr::core::vector3df oldPosition = camera.getCameraNode()->getPosition(); + irr::core::vector3df oldTarget = camera.getCameraNode()->getTarget(); + irr::core::matrix4 startMatrix = + camera.getCameraNode()->getAbsoluteTransformation(); + irr::core::vector3df focusPoint = (camera.getCameraNode()->getTarget() + - camera.getCameraNode()->getAbsolutePosition()).setLength(1) + + camera.getCameraNode()->getAbsolutePosition(); + + /* create left view */ + video::ITexture* left_image = draw_image(screensize, LEFT, startMatrix, + focusPoint, show_hud, driver, camera, smgr, hud, hilightboxes, + draw_wield_tool, client, guienv, skycolor); + + /* create right view */ + video::ITexture* right_image = draw_image(screensize, RIGHT, startMatrix, + focusPoint, show_hud, driver, camera, smgr, hud, hilightboxes, + draw_wield_tool, client, guienv, skycolor); + + /* create hud overlay */ + video::ITexture* hudtexture = draw_hud(driver, screensize, show_hud, hud, client, + false, skycolor, guienv, camera ); + driver->makeColorKeyTexture(hudtexture, irr::video::SColor(255, 0, 0, 0)); + //makeColorKeyTexture mirrors texture so we do it twice to get it right again + driver->makeColorKeyTexture(hudtexture, irr::video::SColor(255, 0, 0, 0)); + + driver->draw2DImage(left_image, + irr::core::rect(0, 0, screensize.X/2, screensize.Y), + irr::core::rect(0, 0, screensize.X, screensize.Y), 0, 0, false); + + driver->draw2DImage(hudtexture, + irr::core::rect(0, 0, screensize.X/2, screensize.Y), + irr::core::rect(0, 0, screensize.X, screensize.Y), 0, 0, true); + + driver->draw2DImage(right_image, + irr::core::rect(screensize.X/2, 0, screensize.X, screensize.Y), + irr::core::rect(0, 0, screensize.X, screensize.Y), 0, 0, false); + + driver->draw2DImage(hudtexture, + irr::core::rect(screensize.X/2, 0, screensize.X, screensize.Y), + irr::core::rect(0, 0, screensize.X, screensize.Y), 0, 0, true); + + left_image = NULL; + right_image = NULL; + + /* cleanup */ + camera.getCameraNode()->setPosition(oldPosition); + camera.getCameraNode()->setTarget(oldTarget); +} + +void draw_top_bottom_3d_mode(Camera& camera, bool show_hud, + Hud& hud, std::vector hilightboxes, video::IVideoDriver* driver, + scene::ISceneManager* smgr, const v2u32& screensize, + bool draw_wield_tool, Client& client, gui::IGUIEnvironment* guienv, + video::SColor skycolor ) +{ + /* save current info */ + irr::core::vector3df oldPosition = camera.getCameraNode()->getPosition(); + irr::core::vector3df oldTarget = camera.getCameraNode()->getTarget(); + irr::core::matrix4 startMatrix = + camera.getCameraNode()->getAbsoluteTransformation(); + irr::core::vector3df focusPoint = (camera.getCameraNode()->getTarget() + - camera.getCameraNode()->getAbsolutePosition()).setLength(1) + + camera.getCameraNode()->getAbsolutePosition(); + + /* create left view */ + video::ITexture* left_image = draw_image(screensize, LEFT, startMatrix, + focusPoint, show_hud, driver, camera, smgr, hud, hilightboxes, + draw_wield_tool, client, guienv, skycolor); + + /* create right view */ + video::ITexture* right_image = draw_image(screensize, RIGHT, startMatrix, + focusPoint, show_hud, driver, camera, smgr, hud, hilightboxes, + draw_wield_tool, client, guienv, skycolor); + + /* create hud overlay */ + video::ITexture* hudtexture = draw_hud(driver, screensize, show_hud, hud, client, + false, skycolor, guienv, camera ); + driver->makeColorKeyTexture(hudtexture, irr::video::SColor(255, 0, 0, 0)); + //makeColorKeyTexture mirrors texture so we do it twice to get it right again + driver->makeColorKeyTexture(hudtexture, irr::video::SColor(255, 0, 0, 0)); + + driver->draw2DImage(left_image, + irr::core::rect(0, 0, screensize.X, screensize.Y/2), + irr::core::rect(0, 0, screensize.X, screensize.Y), 0, 0, false); + + driver->draw2DImage(hudtexture, + irr::core::rect(0, 0, screensize.X, screensize.Y/2), + irr::core::rect(0, 0, screensize.X, screensize.Y), 0, 0, true); + + driver->draw2DImage(right_image, + irr::core::rect(0, screensize.Y/2, screensize.X, screensize.Y), + irr::core::rect(0, 0, screensize.X, screensize.Y), 0, 0, false); + + driver->draw2DImage(hudtexture, + irr::core::rect(0, screensize.Y/2, screensize.X, screensize.Y), + irr::core::rect(0, 0, screensize.X, screensize.Y), 0, 0, true); + + left_image = NULL; + right_image = NULL; + + /* cleanup */ + camera.getCameraNode()->setPosition(oldPosition); + camera.getCameraNode()->setTarget(oldTarget); +} + +void draw_plain(Camera& camera, bool show_hud, Hud& hud, + std::vector hilightboxes, video::IVideoDriver* driver, + bool draw_wield_tool, Client& client, gui::IGUIEnvironment* guienv) +{ + driver->setTransform(video::ETS_WORLD, core::IdentityMatrix); + + draw_selectionbox(driver, hud, hilightboxes, show_hud); + + if(draw_wield_tool) + camera.drawWieldedTool(); +} + +void draw_scene(video::IVideoDriver* driver, scene::ISceneManager* smgr, + Camera& camera, Client& client, LocalPlayer* player, Hud& hud, + gui::IGUIEnvironment* guienv, std::vector hilightboxes, + const v2u32& screensize, video::SColor skycolor, bool show_hud) +{ + TimeTaker timer("smgr"); + + bool draw_wield_tool = (show_hud && + (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) && + camera.getCameraMode() < CAMERA_MODE_THIRD ); + + bool draw_crosshair = ((player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) && + (camera.getCameraMode() != CAMERA_MODE_THIRD_FRONT)); + +#ifdef HAVE_TOUCHSCREENGUI + try { + draw_crosshair = !g_settings->getBool("touchtarget"); + } + catch(SettingNotFoundException) {} +#endif + + std::string draw_mode = g_settings->get("3d_mode"); + + smgr->drawAll(); + + if (draw_mode == "anaglyph") + { + draw_anaglyph_3d_mode(camera, show_hud, hud, hilightboxes, driver, + smgr, draw_wield_tool, client, guienv); + draw_crosshair = false; + } + else if (draw_mode == "interlaced") + { + draw_interlaced_3d_mode(camera, show_hud, hud, hilightboxes, driver, + smgr, screensize, draw_wield_tool, client, guienv, skycolor); + draw_crosshair = false; + } + else if (draw_mode == "sidebyside") + { + draw_sidebyside_3d_mode(camera, show_hud, hud, hilightboxes, driver, + smgr, screensize, draw_wield_tool, client, guienv, skycolor); + show_hud = false; + } + else if (draw_mode == "topbottom") + { + draw_top_bottom_3d_mode(camera, show_hud, hud, hilightboxes, driver, + smgr, screensize, draw_wield_tool, client, guienv, skycolor); + show_hud = false; + } + else { + draw_plain(camera, show_hud, hud, hilightboxes, driver, + draw_wield_tool, client, guienv); + } + + /* + Post effects + */ + { + client.getEnv().getClientMap().renderPostFx(camera.getCameraMode()); + } + + //TODO how to make those 3d too + if (show_hud) + { + if (draw_crosshair) + hud.drawCrosshair(); + hud.drawHotbar(client.getPlayerItem()); + hud.drawLuaElements(camera.getOffset()); + } + + guienv->drawAll(); + + timer.stop(true); +} + +/* + Draws a screen with a single text on it. + Text will be removed when the screen is drawn the next time. + Additionally, a progressbar can be drawn when percent is set between 0 and 100. +*/ +void draw_load_screen(const std::wstring &text, IrrlichtDevice* device, + gui::IGUIEnvironment* guienv, float dtime, int percent, bool clouds ) +{ + video::IVideoDriver* driver = device->getVideoDriver(); + v2u32 screensize = porting::getWindowSize(); + + v2s32 textsize(g_fontengine->getTextWidth(text), g_fontengine->getLineHeight()); + v2s32 center(screensize.X / 2, screensize.Y / 2); + core::rect textrect(center - textsize / 2, center + textsize / 2); + + gui::IGUIStaticText *guitext = guienv->addStaticText( + text.c_str(), textrect, false, false); + guitext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); + + bool cloud_menu_background = clouds && g_settings->getBool("menu_clouds"); + if (cloud_menu_background) + { + g_menuclouds->step(dtime*3); + g_menuclouds->render(); + driver->beginScene(true, true, video::SColor(255, 140, 186, 250)); + g_menucloudsmgr->drawAll(); + } + else + driver->beginScene(true, true, video::SColor(255, 0, 0, 0)); + + // draw progress bar + if ((percent >= 0) && (percent <= 100)) + { + v2s32 barsize( + // 342 is (approximately) 256/0.75 to keep bar on same size as + // before with default settings + 342 * porting::getDisplayDensity() * + g_settings->getFloat("gui_scaling"), + g_fontengine->getTextHeight() * 2); + + core::rect barrect(center - barsize / 2, center + barsize / 2); + driver->draw2DRectangle(video::SColor(255, 255, 255, 255),barrect, NULL); // border + driver->draw2DRectangle(video::SColor(255, 64, 64, 64), core::rect ( + barrect.UpperLeftCorner + 1, + barrect.LowerRightCorner-1), NULL); // black inside the bar + driver->draw2DRectangle(video::SColor(255, 128, 128, 128), core::rect ( + barrect.UpperLeftCorner + 1, + core::vector2d( + barrect.LowerRightCorner.X - + (barsize.X - 1) + percent * (barsize.X - 2) / 100, + barrect.LowerRightCorner.Y - 1)), NULL); // the actual progress + } + guienv->drawAll(); + driver->endScene(); + + guitext->remove(); + + //return guitext; +} diff --git a/src/drawscene.h b/src/drawscene.h new file mode 100644 index 0000000..3268bcb --- /dev/null +++ b/src/drawscene.h @@ -0,0 +1,37 @@ +/* +Minetest +Copyright (C) 2010-2014 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. +*/ + +#ifndef DRAWSCENE_H_ +#define DRAWSCENE_H_ + +#include "camera.h" +#include "hud.h" +#include "irrlichttypes_extrabloated.h" + + +void draw_load_screen(const std::wstring &text, IrrlichtDevice* device, + gui::IGUIEnvironment* guienv, float dtime=0, int percent=0, + bool clouds=true); + +void draw_scene(video::IVideoDriver* driver, scene::ISceneManager* smgr, + Camera& camera, Client& client, LocalPlayer* player, Hud& hud, + gui::IGUIEnvironment* guienv, std::vector hilightboxes, + const v2u32& screensize, video::SColor skycolor, bool show_hud); + +#endif /* DRAWSCENE_H_ */ diff --git a/src/dungeongen.cpp b/src/dungeongen.cpp new file mode 100644 index 0000000..3b7e755 --- /dev/null +++ b/src/dungeongen.cpp @@ -0,0 +1,650 @@ +/* +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. +*/ + +#include "dungeongen.h" +#include "mapgen.h" +#include "voxel.h" +#include "noise.h" +#include "mapblock.h" +#include "mapnode.h" +#include "map.h" +#include "nodedef.h" +#include "profiler.h" +#include "settings.h" // For g_settings +#include "main.h" // For g_profiler + +//#define DGEN_USE_TORCHES + +NoiseParams nparams_dungeon_rarity(0.0, 1.0, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0); +NoiseParams nparams_dungeon_wetness(0.0, 1.0, v3f(40.0, 40.0, 40.0), 32474, 4, 1.1, 2.0); +NoiseParams nparams_dungeon_density(0.0, 1.0, v3f(2.5, 2.5, 2.5), 0, 2, 1.4, 2.0); + + +/////////////////////////////////////////////////////////////////////////////// + + +DungeonGen::DungeonGen(Mapgen *mapgen, DungeonParams *dparams) { + this->mg = mapgen; + this->vm = mapgen->vm; + +#ifdef DGEN_USE_TORCHES + c_torch = ndef->getId("default:torch"); +#endif + + if (dparams) { + memcpy(&dp, dparams, sizeof(dp)); + } else { + dp.c_water = mg->ndef->getId("mapgen_water_source"); + dp.c_cobble = mg->ndef->getId("mapgen_cobble"); + dp.c_moss = mg->ndef->getId("mapgen_mossycobble"); + dp.c_stair = mg->ndef->getId("mapgen_stair_cobble"); + + dp.diagonal_dirs = false; + dp.mossratio = 3.0; + dp.holesize = v3s16(1, 2, 1); + dp.roomsize = v3s16(0,0,0); + dp.notifytype = GENNOTIFY_DUNGEON; + + dp.np_rarity = nparams_dungeon_rarity; + dp.np_wetness = nparams_dungeon_wetness; + dp.np_density = nparams_dungeon_density; + } +} + + +void DungeonGen::generate(u32 bseed, v3s16 nmin, v3s16 nmax) { + //TimeTaker t("gen dungeons"); + if (NoisePerlin3D(&dp.np_rarity, nmin.X, nmin.Y, nmin.Z, mg->seed) < 0.2) + return; + + this->blockseed = bseed; + random.seed(bseed + 2); + + // Dungeon generator doesn't modify places which have this set + vm->clearFlag(VMANIP_FLAG_DUNGEON_INSIDE | VMANIP_FLAG_DUNGEON_PRESERVE); + + bool no_float = !g_settings->getBool("enable_floating_dungeons"); + + // Set all air and water (and optionally ignore) to be untouchable + // to make dungeons open to caves and open air + for (s16 z = nmin.Z; z <= nmax.Z; z++) { + for (s16 y = nmin.Y; y <= nmax.Y; y++) { + u32 i = vm->m_area.index(nmin.X, y, z); + for (s16 x = nmin.X; x <= nmax.X; x++) { + content_t c = vm->m_data[i].getContent(); + if (c == CONTENT_AIR || c == dp.c_water + || (no_float && c == CONTENT_IGNORE)) + vm->m_flags[i] |= VMANIP_FLAG_DUNGEON_PRESERVE; + i++; + } + } + } + + // Add it + makeDungeon(v3s16(1,1,1) * MAP_BLOCKSIZE); + + // Convert some cobble to mossy cobble + if (dp.mossratio != 0.0) { + for (s16 z = nmin.Z; z <= nmax.Z; z++) + for (s16 y = nmin.Y; y <= nmax.Y; y++) { + u32 i = vm->m_area.index(nmin.X, y, z); + for (s16 x = nmin.X; x <= nmax.X; x++) { + if (vm->m_data[i].getContent() == dp.c_cobble) { + float wetness = NoisePerlin3D(&dp.np_wetness, x, y, z, mg->seed); + float density = NoisePerlin3D(&dp.np_density, x, y, z, blockseed); + if (density < wetness / dp.mossratio) + vm->m_data[i].setContent(dp.c_moss); + } + i++; + } + } + } + + //printf("== gen dungeons: %dms\n", t.stop()); +} + + +void DungeonGen::makeDungeon(v3s16 start_padding) +{ + v3s16 areasize = vm->m_area.getExtent(); + v3s16 roomsize; + v3s16 roomplace; + + /* + Find place for first room + */ + bool fits = false; + for (u32 i = 0; i < 100 && !fits; i++) + { + bool is_large_room = ((random.next() & 3) == 1); + roomsize = is_large_room ? + v3s16(random.range(8, 16),random.range(8, 16),random.range(8, 16)) : + v3s16(random.range(4, 8),random.range(4, 6),random.range(4, 8)); + roomsize += dp.roomsize; + + // start_padding is used to disallow starting the generation of + // a dungeon in a neighboring generation chunk + roomplace = vm->m_area.MinEdge + start_padding + v3s16( + random.range(0,areasize.X-roomsize.X-1-start_padding.X), + random.range(0,areasize.Y-roomsize.Y-1-start_padding.Y), + random.range(0,areasize.Z-roomsize.Z-1-start_padding.Z)); + + /* + Check that we're not putting the room to an unknown place, + otherwise it might end up floating in the air + */ + fits = true; + for (s16 z = 1; z < roomsize.Z - 1; z++) + for (s16 y = 1; y < roomsize.Y - 1; y++) + for (s16 x = 1; x < roomsize.X - 1; x++) + { + v3s16 p = roomplace + v3s16(x, y, z); + u32 vi = vm->m_area.index(p); + if ((vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_INSIDE) || + vm->m_data[vi].getContent() == CONTENT_IGNORE) { + fits = false; + break; + } + } + } + // No place found + if (fits == false) + return; + + /* + Stores the center position of the last room made, so that + a new corridor can be started from the last room instead of + the new room, if chosen so. + */ + v3s16 last_room_center = roomplace + v3s16(roomsize.X / 2, 1, roomsize.Z / 2); + + u32 room_count = random.range(2, 16); + for (u32 i = 0; i < room_count; i++) + { + // Make a room to the determined place + makeRoom(roomsize, roomplace); + + v3s16 room_center = roomplace + v3s16(roomsize.X / 2, 1, roomsize.Z / 2); + mg->gennotify.addEvent(dp.notifytype, room_center); + +#ifdef DGEN_USE_TORCHES + // Place torch at room center (for testing) + vm->m_data[vm->m_area.index(room_center)] = MapNode(c_torch); +#endif + + // Quit if last room + if (i == room_count - 1) + break; + + // Determine walker start position + + bool start_in_last_room = (random.range(0, 2) != 0); + + v3s16 walker_start_place; + + if (start_in_last_room) { + walker_start_place = last_room_center; + } else { + walker_start_place = room_center; + // Store center of current room as the last one + last_room_center = room_center; + } + + // Create walker and find a place for a door + v3s16 doorplace; + v3s16 doordir; + + m_pos = walker_start_place; + if (!findPlaceForDoor(doorplace, doordir)) + return; + + if (random.range(0,1) == 0) + // Make the door + makeDoor(doorplace, doordir); + else + // Don't actually make a door + doorplace -= doordir; + + // Make a random corridor starting from the door + v3s16 corridor_end; + v3s16 corridor_end_dir; + makeCorridor(doorplace, doordir, corridor_end, corridor_end_dir); + + // Find a place for a random sized room + roomsize = v3s16(random.range(4,8),random.range(4,6),random.range(4,8)); + roomsize += dp.roomsize; + + m_pos = corridor_end; + m_dir = corridor_end_dir; + if (!findPlaceForRoomDoor(roomsize, doorplace, doordir, roomplace)) + return; + + if (random.range(0,1) == 0) + // Make the door + makeDoor(doorplace, doordir); + else + // Don't actually make a door + roomplace -= doordir; + + } +} + + +void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) +{ + MapNode n_cobble(dp.c_cobble); + MapNode n_air(CONTENT_AIR); + + // Make +-X walls + for (s16 z = 0; z < roomsize.Z; z++) + for (s16 y = 0; y < roomsize.Y; y++) + { + { + v3s16 p = roomplace + v3s16(0, y, z); + if (vm->m_area.contains(p) == false) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_cobble; + } + { + v3s16 p = roomplace + v3s16(roomsize.X - 1, y, z); + if (vm->m_area.contains(p) == false) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_cobble; + } + } + + // Make +-Z walls + for (s16 x = 0; x < roomsize.X; x++) + for (s16 y = 0; y < roomsize.Y; y++) + { + { + v3s16 p = roomplace + v3s16(x, y, 0); + if (vm->m_area.contains(p) == false) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_cobble; + } + { + v3s16 p = roomplace + v3s16(x, y, roomsize.Z - 1); + if (vm->m_area.contains(p) == false) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_cobble; + } + } + + // Make +-Y walls (floor and ceiling) + for (s16 z = 0; z < roomsize.Z; z++) + for (s16 x = 0; x < roomsize.X; x++) + { + { + v3s16 p = roomplace + v3s16(x, 0, z); + if (vm->m_area.contains(p) == false) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_cobble; + } + { + v3s16 p = roomplace + v3s16(x,roomsize. Y - 1, z); + if (vm->m_area.contains(p) == false) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_cobble; + } + } + + // Fill with air + for (s16 z = 1; z < roomsize.Z - 1; z++) + for (s16 y = 1; y < roomsize.Y - 1; y++) + for (s16 x = 1; x < roomsize.X - 1; x++) + { + v3s16 p = roomplace + v3s16(x, y, z); + if (vm->m_area.contains(p) == false) + continue; + u32 vi = vm->m_area.index(p); + vm->m_flags[vi] |= VMANIP_FLAG_DUNGEON_UNTOUCHABLE; + vm->m_data[vi] = n_air; + } +} + + +void DungeonGen::makeFill(v3s16 place, v3s16 size, + u8 avoid_flags, MapNode n, u8 or_flags) +{ + for (s16 z = 0; z < size.Z; z++) + for (s16 y = 0; y < size.Y; y++) + for (s16 x = 0; x < size.X; x++) + { + v3s16 p = place + v3s16(x, y, z); + if (vm->m_area.contains(p) == false) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & avoid_flags) + continue; + vm->m_flags[vi] |= or_flags; + vm->m_data[vi] = n; + } +} + + +void DungeonGen::makeHole(v3s16 place) +{ + makeFill(place, dp.holesize, 0, + MapNode(CONTENT_AIR), VMANIP_FLAG_DUNGEON_INSIDE); +} + + +void DungeonGen::makeDoor(v3s16 doorplace, v3s16 doordir) +{ + makeHole(doorplace); + +#ifdef DGEN_USE_TORCHES + // Place torch (for testing) + vm->m_data[vm->m_area.index(doorplace)] = MapNode(c_torch); +#endif +} + + +void DungeonGen::makeCorridor(v3s16 doorplace, + v3s16 doordir, v3s16 &result_place, v3s16 &result_dir) +{ + makeHole(doorplace); + v3s16 p0 = doorplace; + v3s16 dir = doordir; + u32 length; + /*if (random.next() % 2) + length = random.range(1, 13); + else + length = random.range(1, 6);*/ + length = random.range(1, 13); + u32 partlength = random.range(1, 13); + u32 partcount = 0; + s16 make_stairs = 0; + + if (random.next() % 2 == 0 && partlength >= 3) + make_stairs = random.next() % 2 ? 1 : -1; + + for (u32 i = 0; i < length; i++) { + v3s16 p = p0 + dir; + if (partcount != 0) + p.Y += make_stairs; + + if (vm->m_area.contains(p) == true && + vm->m_area.contains(p + v3s16(0, 1, 0)) == true) { + if (make_stairs) { + makeFill(p + v3s16(-1, -1, -1), dp.holesize + v3s16(2, 3, 2), + VMANIP_FLAG_DUNGEON_UNTOUCHABLE, MapNode(dp.c_cobble), 0); + makeHole(p); + makeHole(p - dir); + + // TODO: fix stairs code so it works 100% (quite difficult) + + // exclude stairs from the bottom step + // exclude stairs from diagonal steps + if (((dir.X ^ dir.Z) & 1) && + (((make_stairs == 1) && i != 0) || + ((make_stairs == -1) && i != length - 1))) { + // rotate face 180 deg if making stairs backwards + int facedir = dir_to_facedir(dir * make_stairs); + + u32 vi = vm->m_area.index(p.X - dir.X, p.Y - 1, p.Z - dir.Z); + if (vm->m_data[vi].getContent() == dp.c_cobble) + vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir); + + vi = vm->m_area.index(p.X, p.Y, p.Z); + if (vm->m_data[vi].getContent() == dp.c_cobble) + vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir); + } + } else { + makeFill(p + v3s16(-1, -1, -1), dp.holesize + v3s16(2, 2, 2), + VMANIP_FLAG_DUNGEON_UNTOUCHABLE, MapNode(dp.c_cobble), 0); + makeHole(p); + } + + p0 = p; + } else { + // Can't go here, turn away + dir = turn_xz(dir, random.range(0, 1)); + make_stairs = -make_stairs; + partcount = 0; + partlength = random.range(1, length); + continue; + } + + partcount++; + if (partcount >= partlength) { + partcount = 0; + + dir = random_turn(random, dir); + + partlength = random.range(1,length); + + make_stairs = 0; + if (random.next() % 2 == 0 && partlength >= 3) + make_stairs = random.next() % 2 ? 1 : -1; + } + } + result_place = p0; + result_dir = dir; +} + + +bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir) +{ + for (u32 i = 0; i < 100; i++) + { + v3s16 p = m_pos + m_dir; + v3s16 p1 = p + v3s16(0, 1, 0); + if (vm->m_area.contains(p) == false + || vm->m_area.contains(p1) == false + || i % 4 == 0) + { + randomizeDir(); + continue; + } + if (vm->getNodeNoExNoEmerge(p).getContent() == dp.c_cobble + && vm->getNodeNoExNoEmerge(p1).getContent() == dp.c_cobble) + { + // Found wall, this is a good place! + result_place = p; + result_dir = m_dir; + // Randomize next direction + randomizeDir(); + return true; + } + /* + Determine where to move next + */ + // Jump one up if the actual space is there + if (vm->getNodeNoExNoEmerge(p+v3s16(0,0,0)).getContent() == dp.c_cobble + && vm->getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent() == CONTENT_AIR + && vm->getNodeNoExNoEmerge(p+v3s16(0,2,0)).getContent() == CONTENT_AIR) + p += v3s16(0,1,0); + // Jump one down if the actual space is there + if (vm->getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent() == dp.c_cobble + && vm->getNodeNoExNoEmerge(p+v3s16(0,0,0)).getContent() == CONTENT_AIR + && vm->getNodeNoExNoEmerge(p+v3s16(0,-1,0)).getContent() == CONTENT_AIR) + p += v3s16(0,-1,0); + // Check if walking is now possible + if (vm->getNodeNoExNoEmerge(p).getContent() != CONTENT_AIR + || vm->getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent() != CONTENT_AIR) + { + // Cannot continue walking here + randomizeDir(); + continue; + } + // Move there + m_pos = p; + } + return false; +} + + +bool DungeonGen::findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace, + v3s16 &result_doordir, v3s16 &result_roomplace) +{ + for (s16 trycount = 0; trycount < 30; trycount++) + { + v3s16 doorplace; + v3s16 doordir; + bool r = findPlaceForDoor(doorplace, doordir); + if (r == false) + continue; + v3s16 roomplace; + // X east, Z north, Y up +#if 1 + if (doordir == v3s16(1, 0, 0)) // X+ + roomplace = doorplace + + v3s16(0, -1, random.range(-roomsize.Z + 2, -2)); + if (doordir == v3s16(-1, 0, 0)) // X- + roomplace = doorplace + + v3s16(-roomsize.X + 1, -1, random.range(-roomsize.Z + 2, -2)); + if (doordir == v3s16(0, 0, 1)) // Z+ + roomplace = doorplace + + v3s16(random.range(-roomsize.X + 2, -2), -1, 0); + if (doordir == v3s16(0, 0, -1)) // Z- + roomplace = doorplace + + v3s16(random.range(-roomsize.X + 2, -2), -1, -roomsize.Z + 1); +#endif +#if 0 + if (doordir == v3s16(1, 0, 0)) // X+ + roomplace = doorplace + v3s16(0, -1, -roomsize.Z / 2); + if (doordir == v3s16(-1, 0, 0)) // X- + roomplace = doorplace + v3s16(-roomsize.X+1,-1,-roomsize.Z / 2); + if (doordir == v3s16(0, 0, 1)) // Z+ + roomplace = doorplace + v3s16(-roomsize.X / 2, -1, 0); + if (doordir == v3s16(0, 0, -1)) // Z- + roomplace = doorplace + v3s16(-roomsize.X / 2, -1, -roomsize.Z + 1); +#endif + + // Check fit + bool fits = true; + for (s16 z = 1; z < roomsize.Z - 1; z++) + for (s16 y = 1; y < roomsize.Y - 1; y++) + for (s16 x = 1; x < roomsize.X - 1; x++) + { + v3s16 p = roomplace + v3s16(x, y, z); + if (vm->m_area.contains(p) == false) + { + fits = false; + break; + } + if (vm->m_flags[vm->m_area.index(p)] + & VMANIP_FLAG_DUNGEON_INSIDE) + { + fits = false; + break; + } + } + if(fits == false) + { + // Find new place + continue; + } + result_doorplace = doorplace; + result_doordir = doordir; + result_roomplace = roomplace; + return true; + } + return false; +} + + +v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs) +{ + // Make diagonal directions somewhat rare + if (diagonal_dirs && (random.next() % 4 == 0)) { + v3s16 dir; + int trycount = 0; + + do { + trycount++; + dir = v3s16(random.next() % 3 - 1, 0, random.next() % 3 - 1); + } while ((dir.X == 0 && dir.Z == 0) && trycount < 10); + + return dir; + } else { + if (random.next() % 2 == 0) + return random.next() % 2 ? v3s16(-1, 0, 0) : v3s16(1, 0, 0); + else + return random.next() % 2 ? v3s16(0, 0, -1) : v3s16(0, 0, 1); + } +} + + +v3s16 turn_xz(v3s16 olddir, int t) +{ + v3s16 dir; + if (t == 0) + { + // Turn right + dir.X = olddir.Z; + dir.Z = -olddir.X; + dir.Y = olddir.Y; + } + else + { + // Turn left + dir.X = -olddir.Z; + dir.Z = olddir.X; + dir.Y = olddir.Y; + } + return dir; +} + + +v3s16 random_turn(PseudoRandom &random, v3s16 olddir) +{ + int turn = random.range(0, 2); + v3s16 dir; + if (turn == 0) + { + // Go straight + dir = olddir; + } + else if (turn == 1) + // Turn right + dir = turn_xz(olddir, 0); + else + // Turn left + dir = turn_xz(olddir, 1); + return dir; +} + + +int dir_to_facedir(v3s16 d) { + if (abs(d.X) > abs(d.Z)) + return d.X < 0 ? 3 : 1; + else + return d.Z < 0 ? 2 : 0; +} diff --git a/src/dungeongen.h b/src/dungeongen.h new file mode 100644 index 0000000..4e1201d --- /dev/null +++ b/src/dungeongen.h @@ -0,0 +1,98 @@ +/* +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. +*/ + +#ifndef DUNGEONGEN_HEADER +#define DUNGEONGEN_HEADER + +#include "voxel.h" +#include "noise.h" +#include "mapgen.h" + +#define VMANIP_FLAG_DUNGEON_INSIDE VOXELFLAG_CHECKED1 +#define VMANIP_FLAG_DUNGEON_PRESERVE VOXELFLAG_CHECKED2 +#define VMANIP_FLAG_DUNGEON_UNTOUCHABLE (\ + VMANIP_FLAG_DUNGEON_INSIDE|VMANIP_FLAG_DUNGEON_PRESERVE) + +class MMVManip; +class INodeDefManager; + +v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs); +v3s16 turn_xz(v3s16 olddir, int t); +v3s16 random_turn(PseudoRandom &random, v3s16 olddir); +int dir_to_facedir(v3s16 d); + + +struct DungeonParams { + content_t c_water; + content_t c_cobble; + content_t c_moss; + content_t c_stair; + + GenNotifyType notifytype; + bool diagonal_dirs; + float mossratio; + v3s16 holesize; + v3s16 roomsize; + + NoiseParams np_rarity; + NoiseParams np_wetness; + NoiseParams np_density; +}; + +class DungeonGen { +public: + MMVManip *vm; + Mapgen *mg; + u32 blockseed; + PseudoRandom random; + v3s16 csize; + + content_t c_torch; + DungeonParams dp; + + //RoomWalker + v3s16 m_pos; + v3s16 m_dir; + + DungeonGen(Mapgen *mg, DungeonParams *dparams); + void generate(u32 bseed, v3s16 full_node_min, v3s16 full_node_max); + + void makeDungeon(v3s16 start_padding); + void makeRoom(v3s16 roomsize, v3s16 roomplace); + void makeCorridor(v3s16 doorplace, v3s16 doordir, + v3s16 &result_place, v3s16 &result_dir); + void makeDoor(v3s16 doorplace, v3s16 doordir); + void makeFill(v3s16 place, v3s16 size, u8 avoid_flags, MapNode n, u8 or_flags); + void makeHole(v3s16 place); + + bool findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir); + bool findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace, + v3s16 &result_doordir, v3s16 &result_roomplace); + + void randomizeDir() + { + m_dir = rand_ortho_dir(random, dp.diagonal_dirs); + } +}; + +extern NoiseParams nparams_dungeon_rarity; +extern NoiseParams nparams_dungeon_wetness; +extern NoiseParams nparams_dungeon_density; + +#endif diff --git a/src/emerge.cpp b/src/emerge.cpp new file mode 100644 index 0000000..310e9d2 --- /dev/null +++ b/src/emerge.cpp @@ -0,0 +1,559 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola +Copyright (C) 2010-2013 kwolekr, Ryan Kwolek + +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. +*/ + + +#include "emerge.h" +#include "server.h" +#include +#include +#include "jthread/jevent.h" +#include "map.h" +#include "environment.h" +#include "util/container.h" +#include "util/thread.h" +#include "main.h" +#include "constants.h" +#include "voxel.h" +#include "config.h" +#include "mapblock.h" +#include "serverobject.h" +#include "settings.h" +#include "scripting_game.h" +#include "profiler.h" +#include "log.h" +#include "nodedef.h" +#include "mg_biome.h" +#include "mg_ore.h" +#include "mg_decoration.h" +#include "mg_schematic.h" +#include "mapgen_v5.h" +#include "mapgen_v6.h" +#include "mapgen_v7.h" +#include "mapgen_singlenode.h" + +struct MapgenDesc { + const char *name; + MapgenFactory *factory; +}; + +MapgenDesc reg_mapgens[] = { + {"v5", new MapgenFactoryV5}, + {"v6", new MapgenFactoryV6}, + {"v7", new MapgenFactoryV7}, + {"singlenode", new MapgenFactorySinglenode}, +}; + +class EmergeThread : public JThread +{ +public: + Server *m_server; + ServerMap *map; + EmergeManager *emerge; + Mapgen *mapgen; + bool enable_mapgen_debug_info; + int id; + + Event qevent; + std::queue blockqueue; + + EmergeThread(Server *server, int ethreadid): + JThread(), + m_server(server), + map(NULL), + emerge(NULL), + mapgen(NULL), + enable_mapgen_debug_info(false), + id(ethreadid) + { + } + + void *Thread(); + bool popBlockEmerge(v3s16 *pos, u8 *flags); + bool getBlockOrStartGen(v3s16 p, MapBlock **b, + BlockMakeData *data, bool allow_generate); +}; + + +/////////////////////////////// Emerge Manager //////////////////////////////// + +EmergeManager::EmergeManager(IGameDef *gamedef) +{ + this->ndef = gamedef->getNodeDefManager(); + this->biomemgr = new BiomeManager(gamedef); + this->oremgr = new OreManager(gamedef); + this->decomgr = new DecorationManager(gamedef); + this->schemmgr = new SchematicManager(gamedef); + this->gen_notify_on = 0; + + // Note that accesses to this variable are not synchronized. + // This is because the *only* thread ever starting or stopping + // EmergeThreads should be the ServerThread. + this->threads_active = false; + + mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info"); + + // if unspecified, leave a proc for the main thread and one for + // some other misc thread + s16 nthreads = 0; + if (!g_settings->getS16NoEx("num_emerge_threads", nthreads)) + nthreads = porting::getNumberOfProcessors() - 2; + if (nthreads < 1) + nthreads = 1; + + qlimit_total = g_settings->getU16("emergequeue_limit_total"); + if (!g_settings->getU16NoEx("emergequeue_limit_diskonly", qlimit_diskonly)) + qlimit_diskonly = nthreads * 5 + 1; + if (!g_settings->getU16NoEx("emergequeue_limit_generate", qlimit_generate)) + qlimit_generate = nthreads + 1; + + // don't trust user input for something very important like this + if (qlimit_total < 1) + qlimit_total = 1; + if (qlimit_diskonly < 1) + qlimit_diskonly = 1; + if (qlimit_generate < 1) + qlimit_generate = 1; + + for (s16 i = 0; i < nthreads; i++) + emergethread.push_back(new EmergeThread((Server *) gamedef, i)); + + infostream << "EmergeManager: using " << nthreads << " threads" << std::endl; +} + + +EmergeManager::~EmergeManager() +{ + for (u32 i = 0; i != emergethread.size(); i++) { + if (threads_active) { + emergethread[i]->Stop(); + emergethread[i]->qevent.signal(); + emergethread[i]->Wait(); + } + delete emergethread[i]; + delete mapgen[i]; + } + emergethread.clear(); + mapgen.clear(); + + delete biomemgr; + delete oremgr; + delete decomgr; + delete schemmgr; + + if (params.sparams) { + delete params.sparams; + params.sparams = NULL; + } +} + + +void EmergeManager::loadMapgenParams() +{ + params.load(*g_settings); +} + + +void EmergeManager::initMapgens() +{ + if (mapgen.size()) + return; + + if (!params.sparams) { + params.sparams = createMapgenParams(params.mg_name); + if (!params.sparams) { + params.mg_name = DEFAULT_MAPGEN; + params.sparams = createMapgenParams(params.mg_name); + assert(params.sparams); + } + params.sparams->readParams(g_settings); + } + + // Create the mapgens + for (u32 i = 0; i != emergethread.size(); i++) { + Mapgen *mg = createMapgen(params.mg_name, i, ¶ms); + assert(mg); + mapgen.push_back(mg); + } +} + + +Mapgen *EmergeManager::getCurrentMapgen() +{ + for (u32 i = 0; i != emergethread.size(); i++) { + if (emergethread[i]->IsSameThread()) + return emergethread[i]->mapgen; + } + + return NULL; +} + + +void EmergeManager::startThreads() +{ + if (threads_active) + return; + + for (u32 i = 0; i != emergethread.size(); i++) + emergethread[i]->Start(); + + threads_active = true; +} + + +void EmergeManager::stopThreads() +{ + if (!threads_active) + return; + + // Request thread stop in parallel + for (u32 i = 0; i != emergethread.size(); i++) { + emergethread[i]->Stop(); + emergethread[i]->qevent.signal(); + } + + // Then do the waiting for each + for (u32 i = 0; i != emergethread.size(); i++) + emergethread[i]->Wait(); + + threads_active = false; +} + + +bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate) +{ + std::map::const_iterator iter; + BlockEmergeData *bedata; + u16 count; + u8 flags = 0; + int idx = 0; + + if (allow_generate) + flags |= BLOCK_EMERGE_ALLOWGEN; + + { + JMutexAutoLock queuelock(queuemutex); + + count = blocks_enqueued.size(); + if (count >= qlimit_total) + return false; + + count = peer_queue_count[peer_id]; + u16 qlimit_peer = allow_generate ? qlimit_generate : qlimit_diskonly; + if (count >= qlimit_peer) + return false; + + iter = blocks_enqueued.find(p); + if (iter != blocks_enqueued.end()) { + bedata = iter->second; + bedata->flags |= flags; + return true; + } + + bedata = new BlockEmergeData; + bedata->flags = flags; + bedata->peer_requested = peer_id; + blocks_enqueued.insert(std::make_pair(p, bedata)); + + peer_queue_count[peer_id] = count + 1; + + // insert into the EmergeThread queue with the least items + int lowestitems = emergethread[0]->blockqueue.size(); + for (u32 i = 1; i != emergethread.size(); i++) { + int nitems = emergethread[i]->blockqueue.size(); + if (nitems < lowestitems) { + idx = i; + lowestitems = nitems; + } + } + + emergethread[idx]->blockqueue.push(p); + } + emergethread[idx]->qevent.signal(); + + return true; +} + + +int EmergeManager::getGroundLevelAtPoint(v2s16 p) +{ + if (mapgen.size() == 0 || !mapgen[0]) { + errorstream << "EmergeManager: getGroundLevelAtPoint() called" + " before mapgen initialized" << std::endl; + return 0; + } + + return mapgen[0]->getGroundLevelAtPoint(p); +} + + +bool EmergeManager::isBlockUnderground(v3s16 blockpos) +{ + /* + v2s16 p = v2s16((blockpos.X * MAP_BLOCKSIZE) + MAP_BLOCKSIZE / 2, + (blockpos.Y * MAP_BLOCKSIZE) + MAP_BLOCKSIZE / 2); + int ground_level = getGroundLevelAtPoint(p); + return blockpos.Y * (MAP_BLOCKSIZE + 1) <= min(water_level, ground_level); + */ + + //yuck, but then again, should i bother being accurate? + //the height of the nodes in a single block is quite variable + return blockpos.Y * (MAP_BLOCKSIZE + 1) <= params.water_level; +} + + +void EmergeManager::getMapgenNames(std::list &mgnames) +{ + for (u32 i = 0; i != ARRLEN(reg_mapgens); i++) + mgnames.push_back(reg_mapgens[i].name); +} + + +Mapgen *EmergeManager::createMapgen(const std::string &mgname, int mgid, + MapgenParams *mgparams) +{ + u32 i; + for (i = 0; i != ARRLEN(reg_mapgens) && mgname != reg_mapgens[i].name; i++); + if (i == ARRLEN(reg_mapgens)) { + errorstream << "EmergeManager; mapgen " << mgname << + " not registered" << std::endl; + return NULL; + } + + MapgenFactory *mgfactory = reg_mapgens[i].factory; + return mgfactory->createMapgen(mgid, mgparams, this); +} + + +MapgenSpecificParams *EmergeManager::createMapgenParams(const std::string &mgname) +{ + u32 i; + for (i = 0; i < ARRLEN(reg_mapgens) && mgname != reg_mapgens[i].name; i++); + if (i == ARRLEN(reg_mapgens)) { + errorstream << "EmergeManager: Mapgen " << mgname << + " not registered" << std::endl; + return NULL; + } + + MapgenFactory *mgfactory = reg_mapgens[i].factory; + return mgfactory->createMapgenParams(); +} + + +////////////////////////////// Emerge Thread ////////////////////////////////// + +bool EmergeThread::popBlockEmerge(v3s16 *pos, u8 *flags) +{ + std::map::iterator iter; + JMutexAutoLock queuelock(emerge->queuemutex); + + if (blockqueue.empty()) + return false; + v3s16 p = blockqueue.front(); + blockqueue.pop(); + + *pos = p; + + iter = emerge->blocks_enqueued.find(p); + if (iter == emerge->blocks_enqueued.end()) + return false; //uh oh, queue and map out of sync!! + + BlockEmergeData *bedata = iter->second; + *flags = bedata->flags; + + emerge->peer_queue_count[bedata->peer_requested]--; + + delete bedata; + emerge->blocks_enqueued.erase(iter); + + return true; +} + + +bool EmergeThread::getBlockOrStartGen(v3s16 p, MapBlock **b, + BlockMakeData *data, bool allow_gen) +{ + v2s16 p2d(p.X, p.Z); + //envlock: usually takes <=1ms, sometimes 90ms or ~400ms to acquire + JMutexAutoLock envlock(m_server->m_env_mutex); + + // Load sector if it isn't loaded + if (map->getSectorNoGenerateNoEx(p2d) == NULL) + map->loadSectorMeta(p2d); + + // Attempt to load block + MapBlock *block = map->getBlockNoCreateNoEx(p); + if (!block || block->isDummy() || !block->isGenerated()) { + EMERGE_DBG_OUT("not in memory, attempting to load from disk"); + block = map->loadBlock(p); + if (block && block->isGenerated()) + map->prepareBlock(block); + } + + // If could not load and allowed to generate, + // start generation inside this same envlock + if (allow_gen && (block == NULL || !block->isGenerated())) { + EMERGE_DBG_OUT("generating"); + *b = block; + return map->initBlockMake(data, p); + } + + *b = block; + return false; +} + + +void *EmergeThread::Thread() +{ + ThreadStarted(); + log_register_thread("EmergeThread" + itos(id)); + DSTACK(__FUNCTION_NAME); + BEGIN_DEBUG_EXCEPTION_HANDLER + + v3s16 last_tried_pos(-32768,-32768,-32768); // For error output + v3s16 p; + u8 flags = 0; + + map = (ServerMap *)&(m_server->m_env->getMap()); + emerge = m_server->m_emerge; + mapgen = emerge->mapgen[id]; + enable_mapgen_debug_info = emerge->mapgen_debug_info; + + porting::setThreadName("EmergeThread"); + + while (!StopRequested()) + try { + if (!popBlockEmerge(&p, &flags)) { + qevent.wait(); + continue; + } + + last_tried_pos = p; + if (blockpos_over_limit(p)) + continue; + + bool allow_generate = flags & BLOCK_EMERGE_ALLOWGEN; + EMERGE_DBG_OUT("p=" PP(p) " allow_generate=" << allow_generate); + + /* + Try to fetch block from memory or disk. + If not found and asked to generate, initialize generator. + */ + BlockMakeData data; + MapBlock *block = NULL; + std::map modified_blocks; + + if (getBlockOrStartGen(p, &block, &data, allow_generate) && mapgen) { + { + ScopeProfiler sp(g_profiler, "EmergeThread: Mapgen::makeChunk", SPT_AVG); + TimeTaker t("mapgen::make_block()"); + + mapgen->makeChunk(&data); + + if (enable_mapgen_debug_info == false) + t.stop(true); // Hide output + } + + { + //envlock: usually 0ms, but can take either 30 or 400ms to acquire + JMutexAutoLock envlock(m_server->m_env_mutex); + ScopeProfiler sp(g_profiler, "EmergeThread: after " + "Mapgen::makeChunk (envlock)", SPT_AVG); + + map->finishBlockMake(&data, modified_blocks); + + block = map->getBlockNoCreateNoEx(p); + if (block) { + /* + Do some post-generate stuff + */ + v3s16 minp = data.blockpos_min * MAP_BLOCKSIZE; + v3s16 maxp = data.blockpos_max * MAP_BLOCKSIZE + + v3s16(1,1,1) * (MAP_BLOCKSIZE - 1); + + // Ignore map edit events, they will not need to be sent + // to anybody because the block hasn't been sent to anybody + MapEditEventAreaIgnorer + ign(&m_server->m_ignore_map_edit_events_area, + VoxelArea(minp, maxp)); + try { // takes about 90ms with -O1 on an e3-1230v2 + m_server->getScriptIface()->environment_OnGenerated( + minp, maxp, mapgen->blockseed); + } catch(LuaError &e) { + m_server->setAsyncFatalError(e.what()); + } + + EMERGE_DBG_OUT("ended up with: " << analyze_block(block)); + + m_server->m_env->activateBlock(block, 0); + } + } + } + + /* + Set sent status of modified blocks on clients + */ + // Add the originally fetched block to the modified list + if (block) + modified_blocks[p] = block; + + if (modified_blocks.size() > 0) { + m_server->SetBlocksNotSent(modified_blocks); + } + } + catch (VersionMismatchException &e) { + std::ostringstream err; + err << "World data version mismatch in MapBlock "<setAsyncFatalError(err.str()); + } + catch (SerializationError &e) { + std::ostringstream err; + err << "Invalid data in MapBlock "<setAsyncFatalError(err.str()); + } + + { + JMutexAutoLock queuelock(emerge->queuemutex); + while (!blockqueue.empty()) + { + v3s16 p = blockqueue.front(); + blockqueue.pop(); + + std::map::iterator iter; + iter = emerge->blocks_enqueued.find(p); + if (iter == emerge->blocks_enqueued.end()) + continue; //uh oh, queue and map out of sync!! + + BlockEmergeData *bedata = iter->second; + delete bedata; + } + } + + END_DEBUG_EXCEPTION_HANDLER(errorstream) + log_deregister_thread(); + return NULL; +} diff --git a/src/emerge.h b/src/emerge.h new file mode 100644 index 0000000..1653199 --- /dev/null +++ b/src/emerge.h @@ -0,0 +1,120 @@ +/* +Minetest +Copyright (C) 2010-2013 kwolekr, Ryan Kwolek + +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. +*/ + +#ifndef EMERGE_HEADER +#define EMERGE_HEADER + +#include +#include "irr_v3d.h" +#include "util/container.h" +#include "mapgen.h" // for MapgenParams +#include "map.h" + +#define BLOCK_EMERGE_ALLOWGEN (1<<0) + +#define EMERGE_DBG_OUT(x) \ + do { \ + if (enable_mapgen_debug_info) \ + infostream << "EmergeThread: " x << std::endl; \ + } while (0) + +class EmergeThread; +class INodeDefManager; +class Settings; + +class BiomeManager; +class OreManager; +class DecorationManager; +class SchematicManager; + +struct BlockMakeData { + MMVManip *vmanip; + u64 seed; + v3s16 blockpos_min; + v3s16 blockpos_max; + v3s16 blockpos_requested; + UniqueQueue transforming_liquid; + INodeDefManager *nodedef; + + BlockMakeData(): + vmanip(NULL), + seed(0), + nodedef(NULL) + {} + + ~BlockMakeData() { delete vmanip; } +}; + +struct BlockEmergeData { + u16 peer_requested; + u8 flags; +}; + +class EmergeManager { +public: + INodeDefManager *ndef; + + std::vector mapgen; + std::vector emergethread; + + bool threads_active; + + //settings + MapgenParams params; + bool mapgen_debug_info; + u16 qlimit_total; + u16 qlimit_diskonly; + u16 qlimit_generate; + + u32 gen_notify_on; + std::set gen_notify_on_deco_ids; + + //// Block emerge queue data structures + JMutex queuemutex; + std::map blocks_enqueued; + std::map peer_queue_count; + + //// Managers of map generation-related components + BiomeManager *biomemgr; + OreManager *oremgr; + DecorationManager *decomgr; + SchematicManager *schemmgr; + + //// Methods + EmergeManager(IGameDef *gamedef); + ~EmergeManager(); + + void loadMapgenParams(); + static MapgenSpecificParams *createMapgenParams(const std::string &mgname); + void initMapgens(); + Mapgen *getCurrentMapgen(); + Mapgen *createMapgen(const std::string &mgname, int mgid, + MapgenParams *mgparams); + static void getMapgenNames(std::list &mgnames); + void startThreads(); + void stopThreads(); + bool enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate); + + //mapgen helper methods + Biome *getBiomeAtPoint(v3s16 p); + int getGroundLevelAtPoint(v2s16 p); + bool isBlockUnderground(v3s16 blockpos); +}; + +#endif diff --git a/src/environment.cpp b/src/environment.cpp new file mode 100644 index 0000000..953fa28 --- /dev/null +++ b/src/environment.cpp @@ -0,0 +1,2630 @@ +/* +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. +*/ + +#include "environment.h" +#include "filesys.h" +#include "porting.h" +#include "collision.h" +#include "content_mapnode.h" +#include "mapblock.h" +#include "serverobject.h" +#include "content_sao.h" +#include "settings.h" +#include "log.h" +#include "profiler.h" +#include "scripting_game.h" +#include "nodedef.h" +#include "nodemetadata.h" +#include "main.h" // For g_settings, g_profiler +#include "gamedef.h" +#ifndef SERVER +#include "clientmap.h" +#include "localplayer.h" +#include "mapblock_mesh.h" +#include "event.h" +#endif +#include "daynightratio.h" +#include "map.h" +#include "emerge.h" +#include "util/serialize.h" +#include "jthread/jmutexautolock.h" + +#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" + +Environment::Environment(): + m_time_of_day(9000), + m_time_of_day_f(9000./24000), + m_time_of_day_speed(0), + m_time_counter(0), + m_enable_day_night_ratio_override(false), + m_day_night_ratio_override(0.0f) +{ + m_cache_enable_shaders = g_settings->getBool("enable_shaders"); +} + +Environment::~Environment() +{ + // Deallocate players + for(std::vector::iterator i = m_players.begin(); + i != m_players.end(); ++i) { + delete (*i); + } +} + +void Environment::addPlayer(Player *player) +{ + DSTACK(__FUNCTION_NAME); + /* + Check that peer_ids are unique. + Also check that names are unique. + Exception: there can be multiple players with peer_id=0 + */ + // If peer id is non-zero, it has to be unique. + if(player->peer_id != 0) + FATAL_ERROR_IF(getPlayer(player->peer_id) != NULL, "Peer id not unique"); + // Name has to be unique. + FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique"); + // Add. + m_players.push_back(player); +} + +void Environment::removePlayer(u16 peer_id) +{ + DSTACK(__FUNCTION_NAME); + + for(std::vector::iterator i = m_players.begin(); + i != m_players.end();) + { + Player *player = *i; + if(player->peer_id == peer_id) { + delete player; + i = m_players.erase(i); + } else { + ++i; + } + } +} + +void Environment::removePlayer(const char *name) +{ + for (std::vector::iterator it = m_players.begin(); + it != m_players.end(); ++it) { + if (strcmp((*it)->getName(), name) == 0) { + delete *it; + m_players.erase(it); + return; + } + } +} + +Player * Environment::getPlayer(u16 peer_id) +{ + for(std::vector::iterator i = m_players.begin(); + i != m_players.end(); ++i) { + Player *player = *i; + if(player->peer_id == peer_id) + return player; + } + return NULL; +} + +Player * Environment::getPlayer(const char *name) +{ + for(std::vector::iterator i = m_players.begin(); + i != m_players.end(); ++i) { + Player *player = *i; + if(strcmp(player->getName(), name) == 0) + return player; + } + return NULL; +} + +Player * Environment::getRandomConnectedPlayer() +{ + std::vector connected_players = getPlayers(true); + u32 chosen_one = myrand() % connected_players.size(); + u32 j = 0; + for(std::vector::iterator + i = connected_players.begin(); + i != connected_players.end(); ++i) { + if(j == chosen_one) { + Player *player = *i; + return player; + } + j++; + } + return NULL; +} + +Player * Environment::getNearestConnectedPlayer(v3f pos) +{ + std::vector connected_players = getPlayers(true); + f32 nearest_d = 0; + Player *nearest_player = NULL; + for(std::vector::iterator + i = connected_players.begin(); + i != connected_players.end(); ++i) { + Player *player = *i; + f32 d = player->getPosition().getDistanceFrom(pos); + if(d < nearest_d || nearest_player == NULL) { + nearest_d = d; + nearest_player = player; + } + } + return nearest_player; +} + +std::vector Environment::getPlayers() +{ + return m_players; +} + +std::vector Environment::getPlayers(bool ignore_disconnected) +{ + std::vector newlist; + for(std::vector::iterator + i = m_players.begin(); + i != m_players.end(); ++i) { + Player *player = *i; + + if(ignore_disconnected) { + // Ignore disconnected players + if(player->peer_id == 0) + continue; + } + + newlist.push_back(player); + } + return newlist; +} + +u32 Environment::getDayNightRatio() +{ + if(m_enable_day_night_ratio_override) + return m_day_night_ratio_override; + return time_to_daynight_ratio(m_time_of_day_f*24000, m_cache_enable_shaders); +} + +void Environment::setTimeOfDaySpeed(float speed) +{ + JMutexAutoLock(this->m_timeofday_lock); + m_time_of_day_speed = speed; +} + +float Environment::getTimeOfDaySpeed() +{ + JMutexAutoLock(this->m_timeofday_lock); + float retval = m_time_of_day_speed; + return retval; +} + +void Environment::setTimeOfDay(u32 time) +{ + JMutexAutoLock(this->m_time_lock); + m_time_of_day = time; + m_time_of_day_f = (float)time / 24000.0; +} + +u32 Environment::getTimeOfDay() +{ + JMutexAutoLock(this->m_time_lock); + u32 retval = m_time_of_day; + return retval; +} + +float Environment::getTimeOfDayF() +{ + JMutexAutoLock(this->m_time_lock); + float retval = m_time_of_day_f; + return retval; +} + +void Environment::stepTimeOfDay(float dtime) +{ + // getTimeOfDaySpeed lock the value we need to prevent MT problems + float day_speed = getTimeOfDaySpeed(); + + m_time_counter += dtime; + f32 speed = day_speed * 24000./(24.*3600); + u32 units = (u32)(m_time_counter*speed); + bool sync_f = false; + if(units > 0){ + // Sync at overflow + if(m_time_of_day + units >= 24000) + sync_f = true; + m_time_of_day = (m_time_of_day + units) % 24000; + if(sync_f) + m_time_of_day_f = (float)m_time_of_day / 24000.0; + } + if (speed > 0) { + m_time_counter -= (f32)units / speed; + } + if(!sync_f){ + m_time_of_day_f += day_speed/24/3600*dtime; + if(m_time_of_day_f > 1.0) + m_time_of_day_f -= 1.0; + if(m_time_of_day_f < 0.0) + m_time_of_day_f += 1.0; + } +} + +/* + ABMWithState +*/ + +ABMWithState::ABMWithState(ActiveBlockModifier *abm_): + abm(abm_), + timer(0) +{ + // Initialize timer to random value to spread processing + float itv = abm->getTriggerInterval(); + itv = MYMAX(0.001, itv); // No less than 1ms + int minval = MYMAX(-0.51*itv, -60); // Clamp to + int maxval = MYMIN(0.51*itv, 60); // +-60 seconds + timer = myrand_range(minval, maxval); +} + +/* + ActiveBlockList +*/ + +void fillRadiusBlock(v3s16 p0, s16 r, std::set &list) +{ + v3s16 p; + for(p.X=p0.X-r; p.X<=p0.X+r; p.X++) + for(p.Y=p0.Y-r; p.Y<=p0.Y+r; p.Y++) + for(p.Z=p0.Z-r; p.Z<=p0.Z+r; p.Z++) + { + // Set in list + list.insert(p); + } +} + +void ActiveBlockList::update(std::vector &active_positions, + s16 radius, + std::set &blocks_removed, + std::set &blocks_added) +{ + /* + Create the new list + */ + std::set newlist = m_forceloaded_list; + for(std::vector::iterator i = active_positions.begin(); + i != active_positions.end(); ++i) + { + fillRadiusBlock(*i, radius, newlist); + } + + /* + Find out which blocks on the old list are not on the new list + */ + // Go through old list + for(std::set::iterator i = m_list.begin(); + i != m_list.end(); ++i) + { + v3s16 p = *i; + // If not on new list, it's been removed + if(newlist.find(p) == newlist.end()) + blocks_removed.insert(p); + } + + /* + Find out which blocks on the new list are not on the old list + */ + // Go through new list + for(std::set::iterator i = newlist.begin(); + i != newlist.end(); ++i) + { + v3s16 p = *i; + // If not on old list, it's been added + if(m_list.find(p) == m_list.end()) + blocks_added.insert(p); + } + + /* + Update m_list + */ + m_list.clear(); + for(std::set::iterator i = newlist.begin(); + i != newlist.end(); ++i) + { + v3s16 p = *i; + m_list.insert(p); + } +} + +/* + ServerEnvironment +*/ + +ServerEnvironment::ServerEnvironment(ServerMap *map, + GameScripting *scriptIface, IGameDef *gamedef, + const std::string &path_world) : + m_map(map), + m_script(scriptIface), + m_gamedef(gamedef), + m_path_world(path_world), + m_send_recommended_timer(0), + m_active_block_interval_overload_skip(0), + m_game_time(0), + m_game_time_fraction_counter(0), + m_recommended_send_interval(0.1), + m_max_lag_estimate(0.1) +{ +} + +ServerEnvironment::~ServerEnvironment() +{ + // Clear active block list. + // This makes the next one delete all active objects. + m_active_blocks.clear(); + + // Convert all objects to static and delete the active objects + deactivateFarObjects(true); + + // Drop/delete map + m_map->drop(); + + // Delete ActiveBlockModifiers + for(std::vector::iterator + i = m_abms.begin(); i != m_abms.end(); ++i){ + delete i->abm; + } +} + +Map & ServerEnvironment::getMap() +{ + return *m_map; +} + +ServerMap & ServerEnvironment::getServerMap() +{ + return *m_map; +} + +bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize, v3s16 *p) +{ + float distance = pos1.getDistanceFrom(pos2); + + //calculate normalized direction vector + v3f normalized_vector = v3f((pos2.X - pos1.X)/distance, + (pos2.Y - pos1.Y)/distance, + (pos2.Z - pos1.Z)/distance); + + //find out if there's a node on path between pos1 and pos2 + for (float i = 1; i < distance; i += stepsize) { + v3s16 pos = floatToInt(v3f(normalized_vector.X * i, + normalized_vector.Y * i, + normalized_vector.Z * i) +pos1,BS); + + MapNode n = getMap().getNodeNoEx(pos); + + if(n.param0 != CONTENT_AIR) { + if (p) { + *p = pos; + } + return false; + } + } + return true; +} + +void ServerEnvironment::saveLoadedPlayers() +{ + std::string players_path = m_path_world + DIR_DELIM "players"; + fs::CreateDir(players_path); + + for (std::vector::iterator it = m_players.begin(); + it != m_players.end(); + ++it) { + RemotePlayer *player = static_cast(*it); + if (player->checkModified()) { + player->save(players_path); + } + } +} + +void ServerEnvironment::savePlayer(const std::string &playername) +{ + std::string players_path = m_path_world + DIR_DELIM "players"; + fs::CreateDir(players_path); + + RemotePlayer *player = static_cast(getPlayer(playername.c_str())); + if (player) { + player->save(players_path); + } +} + +Player *ServerEnvironment::loadPlayer(const std::string &playername) +{ + std::string players_path = m_path_world + DIR_DELIM "players" DIR_DELIM; + + RemotePlayer *player = static_cast(getPlayer(playername.c_str())); + bool newplayer = false; + bool found = false; + if (!player) { + player = new RemotePlayer(m_gamedef, playername.c_str()); + newplayer = true; + } + + RemotePlayer testplayer(m_gamedef, ""); + std::string path = players_path + playername; + for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) { + // Open file and deserialize + std::ifstream is(path.c_str(), std::ios_base::binary); + if (!is.good()) { + return NULL; + } + testplayer.deSerialize(is, path); + is.close(); + if (testplayer.getName() == playername) { + *player = testplayer; + found = true; + break; + } + path = players_path + playername + itos(i); + } + if (!found) { + infostream << "Player file for player " << playername + << " not found" << std::endl; + return NULL; + } + if (newplayer) { + addPlayer(player); + } + player->setModified(false); + return player; +} + +void ServerEnvironment::saveMeta() +{ + std::string path = m_path_world + DIR_DELIM "env_meta.txt"; + + // Open file and serialize + std::ostringstream ss(std::ios_base::binary); + + Settings args; + args.setU64("game_time", m_game_time); + args.setU64("time_of_day", getTimeOfDay()); + args.writeLines(ss); + ss<<"EnvArgsEnd\n"; + + if(!fs::safeWriteToFile(path, ss.str())) + { + infostream<<"ServerEnvironment::saveMeta(): Failed to write " + < required_neighbors; +}; + +class ABMHandler +{ +private: + ServerEnvironment *m_env; + std::map > m_aabms; +public: + ABMHandler(std::vector &abms, + float dtime_s, ServerEnvironment *env, + bool use_timers): + m_env(env) + { + if(dtime_s < 0.001) + return; + INodeDefManager *ndef = env->getGameDef()->ndef(); + for(std::vector::iterator + i = abms.begin(); i != abms.end(); ++i) { + ActiveBlockModifier *abm = i->abm; + float trigger_interval = abm->getTriggerInterval(); + if(trigger_interval < 0.001) + trigger_interval = 0.001; + float actual_interval = dtime_s; + if(use_timers){ + i->timer += dtime_s; + if(i->timer < trigger_interval) + continue; + i->timer -= trigger_interval; + actual_interval = trigger_interval; + } + float intervals = actual_interval / trigger_interval; + if(intervals == 0) + continue; + float chance = abm->getTriggerChance(); + if(chance == 0) + chance = 1; + ActiveABM aabm; + aabm.abm = abm; + aabm.chance = chance / intervals; + if(aabm.chance == 0) + aabm.chance = 1; + // Trigger neighbors + std::set required_neighbors_s + = abm->getRequiredNeighbors(); + for(std::set::iterator + i = required_neighbors_s.begin(); + i != required_neighbors_s.end(); i++) + { + ndef->getIds(*i, aabm.required_neighbors); + } + // Trigger contents + std::set contents_s = abm->getTriggerContents(); + for(std::set::iterator + i = contents_s.begin(); i != contents_s.end(); i++) + { + std::set ids; + ndef->getIds(*i, ids); + for(std::set::const_iterator k = ids.begin(); + k != ids.end(); k++) + { + content_t c = *k; + std::map >::iterator j; + j = m_aabms.find(c); + if(j == m_aabms.end()){ + std::vector aabmlist; + m_aabms[c] = aabmlist; + j = m_aabms.find(c); + } + j->second.push_back(aabm); + } + } + } + } + // Find out how many objects the given block and its neighbours contain. + // Returns the number of objects in the block, and also in 'wider' the + // number of objects in the block and all its neighbours. The latter + // may an estimate if any neighbours are unloaded. + u32 countObjects(MapBlock *block, ServerMap * map, u32 &wider) + { + wider = 0; + u32 wider_unknown_count = 0; + for(s16 x=-1; x<=1; x++) + for(s16 y=-1; y<=1; y++) + for(s16 z=-1; z<=1; z++) + { + MapBlock *block2 = map->getBlockNoCreateNoEx( + block->getPos() + v3s16(x,y,z)); + if(block2==NULL){ + wider_unknown_count++; + continue; + } + wider += block2->m_static_objects.m_active.size() + + block2->m_static_objects.m_stored.size(); + } + // Extrapolate + u32 active_object_count = block->m_static_objects.m_active.size(); + u32 wider_known_count = 3*3*3 - wider_unknown_count; + wider += wider_unknown_count * wider / wider_known_count; + return active_object_count; + + } + void apply(MapBlock *block) + { + if(m_aabms.empty()) + return; + + ServerMap *map = &m_env->getServerMap(); + + u32 active_object_count_wider; + u32 active_object_count = this->countObjects(block, map, active_object_count_wider); + m_env->m_added_objects = 0; + + v3s16 p0; + for(p0.X=0; p0.XgetNodeNoEx(p0); + content_t c = n.getContent(); + v3s16 p = p0 + block->getPosRelative(); + + std::map >::iterator j; + j = m_aabms.find(c); + if(j == m_aabms.end()) + continue; + + for(std::vector::iterator + i = j->second.begin(); i != j->second.end(); i++) { + if(myrand() % i->chance != 0) + continue; + + // Check neighbors + if(!i->required_neighbors.empty()) + { + v3s16 p1; + for(p1.X = p.X-1; p1.X <= p.X+1; p1.X++) + for(p1.Y = p.Y-1; p1.Y <= p.Y+1; p1.Y++) + for(p1.Z = p.Z-1; p1.Z <= p.Z+1; p1.Z++) + { + if(p1 == p) + continue; + MapNode n = map->getNodeNoEx(p1); + content_t c = n.getContent(); + std::set::const_iterator k; + k = i->required_neighbors.find(c); + if(k != i->required_neighbors.end()){ + goto neighbor_found; + } + } + // No required neighbor found + continue; + } +neighbor_found: + + // Call all the trigger variations + i->abm->trigger(m_env, p, n); + i->abm->trigger(m_env, p, n, + active_object_count, active_object_count_wider); + + // Count surrounding objects again if the abms added any + if(m_env->m_added_objects > 0) { + active_object_count = countObjects(block, map, active_object_count_wider); + m_env->m_added_objects = 0; + } + } + } + } +}; + +void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime) +{ + // Reset usage timer immediately, otherwise a block that becomes active + // again at around the same time as it would normally be unloaded will + // get unloaded incorrectly. (I think this still leaves a small possibility + // of a race condition between this and server::AsyncRunStep, which only + // some kind of synchronisation will fix, but it at least reduces the window + // of opportunity for it to break from seconds to nanoseconds) + block->resetUsageTimer(); + + // Get time difference + u32 dtime_s = 0; + u32 stamp = block->getTimestamp(); + if(m_game_time > stamp && stamp != BLOCK_TIMESTAMP_UNDEFINED) + dtime_s = m_game_time - block->getTimestamp(); + dtime_s += additional_dtime; + + /*infostream<<"ServerEnvironment::activateBlock(): block timestamp: " + <setTimestampNoChangedFlag(m_game_time); + + /*infostream<<"ServerEnvironment::activateBlock(): block is " + < elapsed_timers = + block->m_node_timers.step((float)dtime_s); + if(!elapsed_timers.empty()){ + MapNode n; + for(std::map::iterator + i = elapsed_timers.begin(); + i != elapsed_timers.end(); i++){ + n = block->getNodeNoEx(i->first); + v3s16 p = i->first + block->getPosRelative(); + if(m_script->node_on_timer(p,n,i->second.elapsed)) + block->setNodeTimer(i->first,NodeTimer(i->second.timeout,0)); + } + } + + /* Handle ActiveBlockModifiers */ + ABMHandler abmhandler(m_abms, dtime_s, this, false); + abmhandler.apply(block); +} + +void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm) +{ + m_abms.push_back(ABMWithState(abm)); +} + +bool ServerEnvironment::setNode(v3s16 p, const MapNode &n) +{ + INodeDefManager *ndef = m_gamedef->ndef(); + MapNode n_old = m_map->getNodeNoEx(p); + + // Call destructor + if (ndef->get(n_old).has_on_destruct) + m_script->node_on_destruct(p, n_old); + + // Replace node + if (!m_map->addNodeWithEvent(p, n)) + return false; + + // Update active VoxelManipulator if a mapgen thread + m_map->updateVManip(p); + + // Call post-destructor + if (ndef->get(n_old).has_after_destruct) + m_script->node_after_destruct(p, n_old); + + // Call constructor + if (ndef->get(n).has_on_construct) + m_script->node_on_construct(p, n); + + return true; +} + +bool ServerEnvironment::removeNode(v3s16 p) +{ + INodeDefManager *ndef = m_gamedef->ndef(); + MapNode n_old = m_map->getNodeNoEx(p); + + // Call destructor + if (ndef->get(n_old).has_on_destruct) + m_script->node_on_destruct(p, n_old); + + // Replace with air + // This is slightly optimized compared to addNodeWithEvent(air) + if (!m_map->removeNodeWithEvent(p)) + return false; + + // Update active VoxelManipulator if a mapgen thread + m_map->updateVManip(p); + + // Call post-destructor + if (ndef->get(n_old).has_after_destruct) + m_script->node_after_destruct(p, n_old); + + // Air doesn't require constructor + return true; +} + +bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n) +{ + if (!m_map->addNodeWithEvent(p, n, false)) + return false; + + // Update active VoxelManipulator if a mapgen thread + m_map->updateVManip(p); + + return true; +} + +std::set ServerEnvironment::getObjectsInsideRadius(v3f pos, float radius) +{ + std::set objects; + for(std::map::iterator + i = m_active_objects.begin(); + i != m_active_objects.end(); ++i) + { + ServerActiveObject* obj = i->second; + u16 id = i->first; + v3f objectpos = obj->getBasePosition(); + if(objectpos.getDistanceFrom(pos) > radius) + continue; + objects.insert(id); + } + return objects; +} + +void ServerEnvironment::clearAllObjects() +{ + infostream<<"ServerEnvironment::clearAllObjects(): " + <<"Removing all active objects"< objects_to_remove; + for(std::map::iterator + i = m_active_objects.begin(); + i != m_active_objects.end(); ++i) { + ServerActiveObject* obj = i->second; + if(obj->getType() == ACTIVEOBJECT_TYPE_PLAYER) + continue; + u16 id = i->first; + // Delete static object if block is loaded + if(obj->m_static_exists){ + MapBlock *block = m_map->getBlockNoCreateNoEx(obj->m_static_block); + if(block){ + block->m_static_objects.remove(id); + block->raiseModified(MOD_STATE_WRITE_NEEDED, + "clearAllObjects"); + obj->m_static_exists = false; + } + } + // If known by some client, don't delete immediately + if(obj->m_known_by_count > 0){ + obj->m_pending_deactivation = true; + obj->m_removed = true; + continue; + } + + // Tell the object about removal + obj->removingFromEnvironment(); + // Deregister in scripting api + m_script->removeObjectReference(obj); + + // Delete active object + if(obj->environmentDeletes()) + delete obj; + // Id to be removed from m_active_objects + objects_to_remove.push_back(id); + } + + // Remove references from m_active_objects + for(std::vector::iterator i = objects_to_remove.begin(); + i != objects_to_remove.end(); ++i) { + m_active_objects.erase(*i); + } + + // Get list of loaded blocks + std::vector loaded_blocks; + infostream<<"ServerEnvironment::clearAllObjects(): " + <<"Listing all loaded blocks"<listAllLoadedBlocks(loaded_blocks); + infostream<<"ServerEnvironment::clearAllObjects(): " + <<"Done listing all loaded blocks: " + < loadable_blocks; + infostream<<"ServerEnvironment::clearAllObjects(): " + <<"Listing all loadable blocks"<listAllLoadableBlocks(loadable_blocks); + infostream<<"ServerEnvironment::clearAllObjects(): " + <<"Done listing all loadable blocks: " + <::iterator i = loaded_blocks.begin(); + i != loaded_blocks.end(); ++i) { + v3s16 p = *i; + MapBlock *block = m_map->getBlockNoCreateNoEx(p); + assert(block != NULL); + block->refGrab(); + } + + // Remove objects in all loadable blocks + u32 unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks"); + unload_interval = MYMAX(unload_interval, 1); + u32 report_interval = loadable_blocks.size() / 10; + u32 num_blocks_checked = 0; + u32 num_blocks_cleared = 0; + u32 num_objs_cleared = 0; + for(std::vector::iterator i = loadable_blocks.begin(); + i != loadable_blocks.end(); ++i) { + v3s16 p = *i; + MapBlock *block = m_map->emergeBlock(p, false); + if(!block){ + errorstream<<"ServerEnvironment::clearAllObjects(): " + <<"Failed to emerge block "<m_static_objects.m_stored.size(); + u32 num_active = block->m_static_objects.m_active.size(); + if(num_stored != 0 || num_active != 0){ + block->m_static_objects.m_stored.clear(); + block->m_static_objects.m_active.clear(); + block->raiseModified(MOD_STATE_WRITE_NEEDED, + "clearAllObjects"); + num_objs_cleared += num_stored + num_active; + num_blocks_cleared++; + } + num_blocks_checked++; + + if(report_interval != 0 && + num_blocks_checked % report_interval == 0){ + float percent = 100.0 * (float)num_blocks_checked / + loadable_blocks.size(); + infostream<<"ServerEnvironment::clearAllObjects(): " + <<"Cleared "<unloadUnreferencedBlocks(); + } + } + m_map->unloadUnreferencedBlocks(); + + // Drop references that were added above + for(std::vector::iterator i = loaded_blocks.begin(); + i != loaded_blocks.end(); ++i) { + v3s16 p = *i; + MapBlock *block = m_map->getBlockNoCreateNoEx(p); + assert(block); + block->refDrop(); + } + + infostream<<"ServerEnvironment::clearAllObjects(): " + <<"Finished: Cleared "<getFloat("dedicated_server_step"); + + /* + Increment game time + */ + { + m_game_time_fraction_counter += dtime; + u32 inc_i = (u32)m_game_time_fraction_counter; + m_game_time += inc_i; + m_game_time_fraction_counter -= (float)inc_i; + } + + /* + Handle players + */ + { + ScopeProfiler sp(g_profiler, "SEnv: handle players avg", SPT_AVG); + for(std::vector::iterator i = m_players.begin(); + i != m_players.end(); ++i) + { + Player *player = *i; + + // Ignore disconnected players + if(player->peer_id == 0) + continue; + + // Move + player->move(dtime, this, 100*BS); + } + } + + /* + Manage active block list + */ + if(m_active_blocks_management_interval.step(dtime, 2.0)) + { + ScopeProfiler sp(g_profiler, "SEnv: manage act. block list avg /2s", SPT_AVG); + /* + Get player block positions + */ + std::vector players_blockpos; + for(std::vector::iterator + i = m_players.begin(); + i != m_players.end(); ++i) { + Player *player = *i; + // Ignore disconnected players + if(player->peer_id == 0) + continue; + + v3s16 blockpos = getNodeBlockPos( + floatToInt(player->getPosition(), BS)); + players_blockpos.push_back(blockpos); + } + + /* + Update list of active blocks, collecting changes + */ + const s16 active_block_range = g_settings->getS16("active_block_range"); + std::set blocks_removed; + std::set blocks_added; + m_active_blocks.update(players_blockpos, active_block_range, + blocks_removed, blocks_added); + + /* + Handle removed blocks + */ + + // Convert active objects that are no more in active blocks to static + deactivateFarObjects(false); + + for(std::set::iterator + i = blocks_removed.begin(); + i != blocks_removed.end(); ++i) + { + v3s16 p = *i; + + /* infostream<<"Server: Block " << PP(p) + << " became inactive"<getBlockNoCreateNoEx(p); + if(block==NULL) + continue; + + // Set current time as timestamp (and let it set ChangedFlag) + block->setTimestamp(m_game_time); + } + + /* + Handle added blocks + */ + + for(std::set::iterator + i = blocks_added.begin(); + i != blocks_added.end(); ++i) + { + v3s16 p = *i; + + MapBlock *block = m_map->getBlockOrEmerge(p); + if(block==NULL){ + m_active_blocks.m_list.erase(p); + continue; + } + + activateBlock(block); + /* infostream<<"Server: Block " << PP(p) + << " became active"<::iterator + i = m_active_blocks.m_list.begin(); + i != m_active_blocks.m_list.end(); ++i) + { + v3s16 p = *i; + + /*infostream<<"Server: Block ("<getBlockNoCreateNoEx(p); + if(block==NULL) + continue; + + // Reset block usage timer + block->resetUsageTimer(); + + // Set current time as timestamp + block->setTimestampNoChangedFlag(m_game_time); + // If time has changed much from the one on disk, + // set block to be saved when it is unloaded + if(block->getTimestamp() > block->getDiskTimestamp() + 60) + block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD, + "Timestamp older than 60s (step)"); + + // Run node timers + std::map elapsed_timers = + block->m_node_timers.step((float)dtime); + if(!elapsed_timers.empty()){ + MapNode n; + for(std::map::iterator + i = elapsed_timers.begin(); + i != elapsed_timers.end(); i++){ + n = block->getNodeNoEx(i->first); + p = i->first + block->getPosRelative(); + if(m_script->node_on_timer(p,n,i->second.elapsed)) + block->setNodeTimer(i->first,NodeTimer(i->second.timeout,0)); + } + } + } + } + + const float abm_interval = 1.0; + if(m_active_block_modifier_interval.step(dtime, abm_interval)) + do{ // breakable + if(m_active_block_interval_overload_skip > 0){ + ScopeProfiler sp(g_profiler, "SEnv: ABM overload skips"); + m_active_block_interval_overload_skip--; + break; + } + ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg /1s", SPT_AVG); + TimeTaker timer("modify in active blocks"); + + // Initialize handling of ActiveBlockModifiers + ABMHandler abmhandler(m_abms, abm_interval, this, true); + + for(std::set::iterator + i = m_active_blocks.m_list.begin(); + i != m_active_blocks.m_list.end(); ++i) + { + v3s16 p = *i; + + /*infostream<<"Server: Block ("<getBlockNoCreateNoEx(p); + if(block == NULL) + continue; + + // Set current time as timestamp + block->setTimestampNoChangedFlag(m_game_time); + + /* Handle ActiveBlockModifiers */ + abmhandler.apply(block); + } + + u32 time_ms = timer.stop(true); + u32 max_time_ms = 200; + if(time_ms > max_time_ms){ + infostream<<"WARNING: active block modifiers took " + <environment_Step(dtime); + + /* + Step active objects + */ + { + ScopeProfiler sp(g_profiler, "SEnv: step act. objs avg", SPT_AVG); + //TimeTaker timer("Step active objects"); + + g_profiler->avg("SEnv: num of objects", m_active_objects.size()); + + // This helps the objects to send data at the same time + bool send_recommended = false; + m_send_recommended_timer += dtime; + if(m_send_recommended_timer > getSendRecommendedInterval()) + { + m_send_recommended_timer -= getSendRecommendedInterval(); + send_recommended = true; + } + + for(std::map::iterator + i = m_active_objects.begin(); + i != m_active_objects.end(); ++i) + { + ServerActiveObject* obj = i->second; + // Don't step if is to be removed or stored statically + if(obj->m_removed || obj->m_pending_deactivation) + continue; + // Step object + obj->step(dtime, send_recommended); + // Read messages from object + while(!obj->m_messages_out.empty()) + { + m_active_object_messages.push_back( + obj->m_messages_out.front()); + obj->m_messages_out.pop(); + } + } + } + + /* + Manage active objects + */ + if(m_object_management_interval.step(dtime, 0.5)) + { + ScopeProfiler sp(g_profiler, "SEnv: remove removed objs avg /.5s", SPT_AVG); + /* + Remove objects that satisfy (m_removed && m_known_by_count==0) + */ + removeRemovedObjects(); + } +} + +ServerActiveObject* ServerEnvironment::getActiveObject(u16 id) +{ + std::map::iterator n; + n = m_active_objects.find(id); + if(n == m_active_objects.end()) + return NULL; + return n->second; +} + +bool isFreeServerActiveObjectId(u16 id, + std::map &objects) +{ + if(id == 0) + return false; + + return objects.find(id) == objects.end(); +} + +u16 getFreeServerActiveObjectId( + std::map &objects) +{ + //try to reuse id's as late as possible + static u16 last_used_id = 0; + u16 startid = last_used_id; + for(;;) + { + last_used_id ++; + if(isFreeServerActiveObjectId(last_used_id, objects)) + return last_used_id; + + if(last_used_id == startid) + return 0; + } +} + +u16 ServerEnvironment::addActiveObject(ServerActiveObject *object) +{ + assert(object); // Pre-condition + m_added_objects++; + u16 id = addActiveObjectRaw(object, true, 0); + return id; +} + +#if 0 +bool ServerEnvironment::addActiveObjectAsStatic(ServerActiveObject *obj) +{ + assert(obj); + + v3f objectpos = obj->getBasePosition(); + + // The block in which the object resides in + v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS)); + + /* + Update the static data + */ + + // Create new static object + std::string staticdata = obj->getStaticData(); + StaticObject s_obj(obj->getType(), objectpos, staticdata); + // Add to the block where the object is located in + v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS)); + // Get or generate the block + MapBlock *block = m_map->emergeBlock(blockpos); + + bool succeeded = false; + + if(block) + { + block->m_static_objects.insert(0, s_obj); + block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD, + "addActiveObjectAsStatic"); + succeeded = true; + } + else{ + infostream<<"ServerEnvironment::addActiveObjectAsStatic: " + <<"Could not find or generate " + <<"a block for storing static object"<environmentDeletes()) + delete obj; + + return succeeded; +} +#endif + +/* + Finds out what new objects have been added to + inside a radius around a position +*/ +void ServerEnvironment::getAddedActiveObjects(v3s16 pos, s16 radius, + s16 player_radius, + std::set ¤t_objects, + std::set &added_objects) +{ + v3f pos_f = intToFloat(pos, BS); + f32 radius_f = radius * BS; + f32 player_radius_f = player_radius * BS; + + if (player_radius_f < 0) + player_radius_f = 0; + + /* + Go through the object list, + - discard m_removed objects, + - discard objects that are too far away, + - discard objects that are found in current_objects. + - add remaining objects to added_objects + */ + for(std::map::iterator + i = m_active_objects.begin(); + i != m_active_objects.end(); ++i) + { + u16 id = i->first; + // Get object + ServerActiveObject *object = i->second; + if(object == NULL) + continue; + // Discard if removed or deactivating + if(object->m_removed || object->m_pending_deactivation) + continue; + + f32 distance_f = object->getBasePosition().getDistanceFrom(pos_f); + if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + // Discard if too far + if (distance_f > player_radius_f && player_radius_f != 0) + continue; + } else if (distance_f > radius_f) + continue; + + // Discard if already on current_objects + std::set::iterator n; + n = current_objects.find(id); + if(n != current_objects.end()) + continue; + // Add to added_objects + added_objects.insert(id); + } +} + +/* + Finds out what objects have been removed from + inside a radius around a position +*/ +void ServerEnvironment::getRemovedActiveObjects(v3s16 pos, s16 radius, + s16 player_radius, + std::set ¤t_objects, + std::set &removed_objects) +{ + v3f pos_f = intToFloat(pos, BS); + f32 radius_f = radius * BS; + f32 player_radius_f = player_radius * BS; + + if (player_radius_f < 0) + player_radius_f = 0; + + /* + Go through current_objects; object is removed if: + - object is not found in m_active_objects (this is actually an + error condition; objects should be set m_removed=true and removed + only after all clients have been informed about removal), or + - object has m_removed=true, or + - object is too far away + */ + for(std::set::iterator + i = current_objects.begin(); + i != current_objects.end(); ++i) + { + u16 id = *i; + ServerActiveObject *object = getActiveObject(id); + + if(object == NULL){ + infostream<<"ServerEnvironment::getRemovedActiveObjects():" + <<" object in current_objects is NULL"<m_removed || object->m_pending_deactivation) + { + removed_objects.insert(id); + continue; + } + + f32 distance_f = object->getBasePosition().getDistanceFrom(pos_f); + if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + if (distance_f <= player_radius_f || player_radius_f == 0) + continue; + } else if (distance_f <= radius_f) + continue; + + // Object is no longer visible + removed_objects.insert(id); + } +} + +ActiveObjectMessage ServerEnvironment::getActiveObjectMessage() +{ + if(m_active_object_messages.empty()) + return ActiveObjectMessage(0); + + ActiveObjectMessage message = m_active_object_messages.front(); + m_active_object_messages.pop_front(); + return message; +} + +/* + ************ Private methods ************* +*/ + +u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object, + bool set_changed, u32 dtime_s) +{ + assert(object); // Pre-condition + if(object->getId() == 0){ + u16 new_id = getFreeServerActiveObjectId(m_active_objects); + if(new_id == 0) + { + errorstream<<"ServerEnvironment::addActiveObjectRaw(): " + <<"no free ids available"<environmentDeletes()) + delete object; + return 0; + } + object->setId(new_id); + } + else{ + verbosestream<<"ServerEnvironment::addActiveObjectRaw(): " + <<"supplied with id "<getId()<getId(), m_active_objects) == false) + { + errorstream<<"ServerEnvironment::addActiveObjectRaw(): " + <<"id is not free ("<getId()<<")"<environmentDeletes()) + delete object; + return 0; + } + /*infostream<<"ServerEnvironment::addActiveObjectRaw(): " + <<"added (id="<getId()<<")"<getId()] = object; + + verbosestream<<"ServerEnvironment::addActiveObjectRaw(): " + <<"Added id="<getId()<<"; there are now " + <addObjectReference(object); + // Post-initialize object + object->addedToEnvironment(dtime_s); + + // Add static data to block + if(object->isStaticAllowed()) + { + // Add static object to active static list of the block + v3f objectpos = object->getBasePosition(); + std::string staticdata = object->getStaticData(); + StaticObject s_obj(object->getType(), objectpos, staticdata); + // Add to the block where the object is located in + v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS)); + MapBlock *block = m_map->emergeBlock(blockpos); + if(block){ + block->m_static_objects.m_active[object->getId()] = s_obj; + object->m_static_exists = true; + object->m_static_block = blockpos; + + if(set_changed) + block->raiseModified(MOD_STATE_WRITE_NEEDED, + "addActiveObjectRaw"); + } else { + v3s16 p = floatToInt(objectpos, BS); + errorstream<<"ServerEnvironment::addActiveObjectRaw(): " + <<"could not emerge block for storing id="<getId() + <<" statically (pos="<getId(); +} + +/* + Remove objects that satisfy (m_removed && m_known_by_count==0) +*/ +void ServerEnvironment::removeRemovedObjects() +{ + std::vector objects_to_remove; + for(std::map::iterator + i = m_active_objects.begin(); + i != m_active_objects.end(); ++i) { + u16 id = i->first; + ServerActiveObject* obj = i->second; + // This shouldn't happen but check it + if(obj == NULL) + { + infostream<<"NULL object found in ServerEnvironment" + <<" while finding removed objects. id="<m_removed == false && obj->m_pending_deactivation == false) + continue; + + /* + Delete static data from block if is marked as removed + */ + if(obj->m_static_exists && obj->m_removed) + { + MapBlock *block = m_map->emergeBlock(obj->m_static_block, false); + if (block) { + block->m_static_objects.remove(id); + block->raiseModified(MOD_STATE_WRITE_NEEDED, + "removeRemovedObjects/remove"); + obj->m_static_exists = false; + } else { + infostream<<"Failed to emerge block from which an object to " + <<"be removed was loaded from. id="< 0, don't actually remove. On some future + // invocation this will be 0, which is when removal will continue. + if(obj->m_known_by_count > 0) + continue; + + /* + Move static data from active to stored if not marked as removed + */ + if(obj->m_static_exists && !obj->m_removed){ + MapBlock *block = m_map->emergeBlock(obj->m_static_block, false); + if (block) { + std::map::iterator i = + block->m_static_objects.m_active.find(id); + if(i != block->m_static_objects.m_active.end()){ + block->m_static_objects.m_stored.push_back(i->second); + block->m_static_objects.m_active.erase(id); + block->raiseModified(MOD_STATE_WRITE_NEEDED, + "removeRemovedObjects/deactivate"); + } + } else { + infostream<<"Failed to emerge block from which an object to " + <<"be deactivated was loaded from. id="<removingFromEnvironment(); + // Deregister in scripting api + m_script->removeObjectReference(obj); + + // Delete + if(obj->environmentDeletes()) + delete obj; + + // Id to be removed from m_active_objects + objects_to_remove.push_back(id); + } + // Remove references from m_active_objects + for(std::vector::iterator i = objects_to_remove.begin(); + i != objects_to_remove.end(); ++i) { + m_active_objects.erase(*i); + } +} + +static void print_hexdump(std::ostream &o, const std::string &data) +{ + const int linelength = 16; + for(int l=0; ; l++){ + int i0 = linelength * l; + bool at_end = false; + int thislinelength = linelength; + if(i0 + thislinelength > (int)data.size()){ + thislinelength = data.size() - i0; + at_end = true; + } + for(int di=0; di= 32) + o<m_static_objects.m_stored.empty()) + return; + + verbosestream<<"ServerEnvironment::activateObjects(): " + <<"activating objects of block "<getPos()) + <<" ("<m_static_objects.m_stored.size() + <<" objects)"<m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block")); + if (large_amount) { + errorstream<<"suspiciously large amount of objects detected: " + <m_static_objects.m_stored.size()<<" in " + <getPos()) + <<"; removing all of them."<m_static_objects.m_stored.clear(); + block->raiseModified(MOD_STATE_WRITE_NEEDED, + "stored list cleared in activateObjects due to " + "large amount of objects"); + return; + } + + // Activate stored objects + std::vector new_stored; + for (std::vector::iterator + i = block->m_static_objects.m_stored.begin(); + i != block->m_static_objects.m_stored.end(); ++i) { + StaticObject &s_obj = *i; + + // Create an active object from the data + ServerActiveObject *obj = ServerActiveObject::create + ((ActiveObjectType) s_obj.type, this, 0, s_obj.pos, s_obj.data); + // If couldn't create object, store static data back. + if(obj == NULL) { + errorstream<<"ServerEnvironment::activateObjects(): " + <<"failed to create active object from static object " + <<"in block "<getStaticData(); + StaticObject s_obj(obj->getType(), objectpos, staticdata_new); + block->m_static_objects.insert(id, s_obj); + obj->m_static_block = blockpos_o; + block->raiseModified(MOD_STATE_WRITE_NEEDED, + "deactivateFarObjects: Static data moved in"); + + // Delete from block where object was located + block = m_map->emergeBlock(old_static_block, false); + if(!block){ + errorstream<<"ServerEnvironment::deactivateFarObjects(): " + <<"Could not delete object id="<m_static_objects.remove(id); + block->raiseModified(MOD_STATE_WRITE_NEEDED, + "deactivateFarObjects: Static data moved out"); + continue; + } + + // If block is active, don't remove + if(!force_delete && m_active_blocks.contains(blockpos_o)) + continue; + + verbosestream<<"ServerEnvironment::deactivateFarObjects(): " + <<"deactivating object id="<m_known_by_count > 0 && !force_delete); + + /* + Update the static data + */ + + if(obj->isStaticAllowed()) + { + // Create new static object + std::string staticdata_new = obj->getStaticData(); + StaticObject s_obj(obj->getType(), objectpos, staticdata_new); + + bool stays_in_same_block = false; + bool data_changed = true; + + if(obj->m_static_exists){ + if(obj->m_static_block == blockpos_o) + stays_in_same_block = true; + + MapBlock *block = m_map->emergeBlock(obj->m_static_block, false); + + std::map::iterator n = + block->m_static_objects.m_active.find(id); + if(n != block->m_static_objects.m_active.end()){ + StaticObject static_old = n->second; + + float save_movem = obj->getMinimumSavedMovement(); + + if(static_old.data == staticdata_new && + (static_old.pos - objectpos).getLength() < save_movem) + data_changed = false; + } else { + errorstream<<"ServerEnvironment::deactivateFarObjects(): " + <<"id="<m_static_block)<m_static_exists) + { + MapBlock *block = m_map->emergeBlock(obj->m_static_block, false); + if(block) + { + block->m_static_objects.remove(id); + obj->m_static_exists = false; + // Only mark block as modified if data changed considerably + if(shall_be_written) + block->raiseModified(MOD_STATE_WRITE_NEEDED, + "deactivateFarObjects: Static data " + "changed considerably"); + } + } + + // Add to the block where the object is located in + v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS)); + // Get or generate the block + MapBlock *block = NULL; + try{ + block = m_map->emergeBlock(blockpos); + } catch(InvalidPositionException &e){ + // Handled via NULL pointer + // NOTE: emergeBlock's failure is usually determined by it + // actually returning NULL + } + + if(block) + { + if(block->m_static_objects.m_stored.size() >= g_settings->getU16("max_objects_per_block")){ + errorstream<<"ServerEnv: Trying to store id="<getId() + <<" statically but block "<m_static_objects.m_stored.size() + <<" objects." + <<" Forcing delete."<m_static_block, but happens rarely for some unknown + // reason. Unsuccessful attempts have been made to find + // said reason. + if(id && block->m_static_objects.m_active.find(id) != block->m_static_objects.m_active.end()){ + infostream<<"ServerEnv: WARNING: Performing hack #83274" + <m_static_objects.remove(id); + } + // Store static data + u16 store_id = pending_delete ? id : 0; + block->m_static_objects.insert(store_id, s_obj); + + // Only mark block as modified if data changed considerably + if(shall_be_written) + block->raiseModified(MOD_STATE_WRITE_NEEDED, + "deactivateFarObjects: Static data " + "changed considerably"); + + obj->m_static_exists = true; + obj->m_static_block = block->getPos(); + } + } + else{ + if(!force_delete){ + v3s16 p = floatToInt(objectpos, BS); + errorstream<<"ServerEnv: Could not find or generate " + <<"a block for storing id="<getId() + <<" statically (pos="<m_pending_deactivation = true; + continue; + } + + verbosestream<<"ServerEnvironment::deactivateFarObjects(): " + <<"object id="<removingFromEnvironment(); + // Deregister in scripting api + m_script->removeObjectReference(obj); + + // Delete active object + if(obj->environmentDeletes()) + delete obj; + // Id to be removed from m_active_objects + objects_to_remove.push_back(id); + } + + // Remove references from m_active_objects + for(std::vector::iterator i = objects_to_remove.begin(); + i != objects_to_remove.end(); ++i) { + m_active_objects.erase(*i); + } +} + + +#ifndef SERVER + +#include "clientsimpleobject.h" + +/* + ClientEnvironment +*/ + +ClientEnvironment::ClientEnvironment(ClientMap *map, scene::ISceneManager *smgr, + ITextureSource *texturesource, IGameDef *gamedef, + IrrlichtDevice *irr): + m_map(map), + m_smgr(smgr), + m_texturesource(texturesource), + m_gamedef(gamedef), + m_irr(irr) +{ + char zero = 0; + memset(m_attachements, zero, sizeof(m_attachements)); +} + +ClientEnvironment::~ClientEnvironment() +{ + // delete active objects + for(std::map::iterator + i = m_active_objects.begin(); + i != m_active_objects.end(); ++i) + { + delete i->second; + } + + for(std::vector::iterator + i = m_simple_objects.begin(); i != m_simple_objects.end(); ++i) { + delete *i; + } + + // Drop/delete map + m_map->drop(); +} + +Map & ClientEnvironment::getMap() +{ + return *m_map; +} + +ClientMap & ClientEnvironment::getClientMap() +{ + return *m_map; +} + +void ClientEnvironment::addPlayer(Player *player) +{ + DSTACK(__FUNCTION_NAME); + /* + It is a failure if player is local and there already is a local + player + */ + FATAL_ERROR_IF(player->isLocal() == true && getLocalPlayer() != NULL, + "Player is local but there is already a local player"); + + Environment::addPlayer(player); +} + +LocalPlayer * ClientEnvironment::getLocalPlayer() +{ + for(std::vector::iterator i = m_players.begin(); + i != m_players.end(); ++i) { + Player *player = *i; + if(player->isLocal()) + return (LocalPlayer*)player; + } + return NULL; +} + +void ClientEnvironment::step(float dtime) +{ + DSTACK(__FUNCTION_NAME); + + /* Step time of day */ + stepTimeOfDay(dtime); + + // Get some settings + bool fly_allowed = m_gamedef->checkLocalPrivilege("fly"); + bool free_move = fly_allowed && g_settings->getBool("free_move"); + + // Get local player + LocalPlayer *lplayer = getLocalPlayer(); + assert(lplayer); + // collision info queue + std::vector player_collisions; + + /* + Get the speed the player is going + */ + bool is_climbing = lplayer->is_climbing; + + f32 player_speed = lplayer->getSpeed().getLength(); + + /* + Maximum position increment + */ + //f32 position_max_increment = 0.05*BS; + f32 position_max_increment = 0.1*BS; + + // Maximum time increment (for collision detection etc) + // time = distance / speed + f32 dtime_max_increment = 1; + if(player_speed > 0.001) + dtime_max_increment = position_max_increment / player_speed; + + // Maximum time increment is 10ms or lower + if(dtime_max_increment > 0.01) + dtime_max_increment = 0.01; + + // Don't allow overly huge dtime + if(dtime > 0.5) + dtime = 0.5; + + f32 dtime_downcount = dtime; + + /* + Stuff that has a maximum time increment + */ + + u32 loopcount = 0; + do + { + loopcount++; + + f32 dtime_part; + if(dtime_downcount > dtime_max_increment) + { + dtime_part = dtime_max_increment; + dtime_downcount -= dtime_part; + } + else + { + dtime_part = dtime_downcount; + /* + Setting this to 0 (no -=dtime_part) disables an infinite loop + when dtime_part is so small that dtime_downcount -= dtime_part + does nothing + */ + dtime_downcount = 0; + } + + /* + Handle local player + */ + + { + // Apply physics + if(free_move == false && is_climbing == false) + { + // Gravity + v3f speed = lplayer->getSpeed(); + if(lplayer->in_liquid == false) + speed.Y -= lplayer->movement_gravity * lplayer->physics_override_gravity * dtime_part * 2; + + // Liquid floating / sinking + if(lplayer->in_liquid && !lplayer->swimming_vertical) + speed.Y -= lplayer->movement_liquid_sink * dtime_part * 2; + + // Liquid resistance + if(lplayer->in_liquid_stable || lplayer->in_liquid) + { + // How much the node's viscosity blocks movement, ranges between 0 and 1 + // Should match the scale at which viscosity increase affects other liquid attributes + const f32 viscosity_factor = 0.3; + + v3f d_wanted = -speed / lplayer->movement_liquid_fluidity; + f32 dl = d_wanted.getLength(); + if(dl > lplayer->movement_liquid_fluidity_smooth) + dl = lplayer->movement_liquid_fluidity_smooth; + dl *= (lplayer->liquid_viscosity * viscosity_factor) + (1 - viscosity_factor); + + v3f d = d_wanted.normalize() * dl; + speed += d; + +#if 0 // old code + if(speed.X > lplayer->movement_liquid_fluidity + lplayer->movement_liquid_fluidity_smooth) speed.X -= lplayer->movement_liquid_fluidity_smooth; + if(speed.X < -lplayer->movement_liquid_fluidity - lplayer->movement_liquid_fluidity_smooth) speed.X += lplayer->movement_liquid_fluidity_smooth; + if(speed.Y > lplayer->movement_liquid_fluidity + lplayer->movement_liquid_fluidity_smooth) speed.Y -= lplayer->movement_liquid_fluidity_smooth; + if(speed.Y < -lplayer->movement_liquid_fluidity - lplayer->movement_liquid_fluidity_smooth) speed.Y += lplayer->movement_liquid_fluidity_smooth; + if(speed.Z > lplayer->movement_liquid_fluidity + lplayer->movement_liquid_fluidity_smooth) speed.Z -= lplayer->movement_liquid_fluidity_smooth; + if(speed.Z < -lplayer->movement_liquid_fluidity - lplayer->movement_liquid_fluidity_smooth) speed.Z += lplayer->movement_liquid_fluidity_smooth; +#endif + } + + lplayer->setSpeed(speed); + } + + /* + Move the lplayer. + This also does collision detection. + */ + lplayer->move(dtime_part, this, position_max_increment, + &player_collisions); + } + } + while(dtime_downcount > 0.001); + + //std::cout<<"Looped "<::iterator i = player_collisions.begin(); + i != player_collisions.end(); ++i) { + CollisionInfo &info = *i; + v3f speed_diff = info.new_speed - info.old_speed;; + // Handle only fall damage + // (because otherwise walking against something in fast_move kills you) + if(speed_diff.Y < 0 || info.old_speed.Y >= 0) + continue; + // Get rid of other components + speed_diff.X = 0; + speed_diff.Z = 0; + f32 pre_factor = 1; // 1 hp per node/s + f32 tolerance = BS*14; // 5 without damage + f32 post_factor = 1; // 1 hp per node/s + if(info.type == COLLISION_NODE) + { + const ContentFeatures &f = m_gamedef->ndef()-> + get(m_map->getNodeNoEx(info.node_p)); + // Determine fall damage multiplier + int addp = itemgroup_get(f.groups, "fall_damage_add_percent"); + pre_factor = 1.0 + (float)addp/100.0; + } + float speed = pre_factor * speed_diff.getLength(); + if(speed > tolerance) + { + f32 damage_f = (speed - tolerance)/BS * post_factor; + u16 damage = (u16)(damage_f+0.5); + if(damage != 0){ + damageLocalPlayer(damage, true); + MtEvent *e = new SimpleTriggerEvent("PlayerFallingDamage"); + m_gamedef->event()->put(e); + } + } + } + + /* + A quick draft of lava damage + */ + if(m_lava_hurt_interval.step(dtime, 1.0)) + { + v3f pf = lplayer->getPosition(); + + // Feet, middle and head + v3s16 p1 = floatToInt(pf + v3f(0, BS*0.1, 0), BS); + MapNode n1 = m_map->getNodeNoEx(p1); + v3s16 p2 = floatToInt(pf + v3f(0, BS*0.8, 0), BS); + MapNode n2 = m_map->getNodeNoEx(p2); + v3s16 p3 = floatToInt(pf + v3f(0, BS*1.6, 0), BS); + MapNode n3 = m_map->getNodeNoEx(p3); + + u32 damage_per_second = 0; + damage_per_second = MYMAX(damage_per_second, + m_gamedef->ndef()->get(n1).damage_per_second); + damage_per_second = MYMAX(damage_per_second, + m_gamedef->ndef()->get(n2).damage_per_second); + damage_per_second = MYMAX(damage_per_second, + m_gamedef->ndef()->get(n3).damage_per_second); + + if(damage_per_second != 0) + { + damageLocalPlayer(damage_per_second, true); + } + } + + /* + Drowning + */ + if(m_drowning_interval.step(dtime, 2.0)) + { + v3f pf = lplayer->getPosition(); + + // head + v3s16 p = floatToInt(pf + v3f(0, BS*1.6, 0), BS); + MapNode n = m_map->getNodeNoEx(p); + ContentFeatures c = m_gamedef->ndef()->get(n); + u8 drowning_damage = c.drowning; + if(drowning_damage > 0 && lplayer->hp > 0){ + u16 breath = lplayer->getBreath(); + if(breath > 10){ + breath = 11; + } + if(breath > 0){ + breath -= 1; + } + lplayer->setBreath(breath); + updateLocalPlayerBreath(breath); + } + + if(lplayer->getBreath() == 0 && drowning_damage > 0){ + damageLocalPlayer(drowning_damage, true); + } + } + if(m_breathing_interval.step(dtime, 0.5)) + { + v3f pf = lplayer->getPosition(); + + // head + v3s16 p = floatToInt(pf + v3f(0, BS*1.6, 0), BS); + MapNode n = m_map->getNodeNoEx(p); + ContentFeatures c = m_gamedef->ndef()->get(n); + if (!lplayer->hp){ + lplayer->setBreath(11); + } + else if(c.drowning == 0){ + u16 breath = lplayer->getBreath(); + if(breath <= 10){ + breath += 1; + lplayer->setBreath(breath); + updateLocalPlayerBreath(breath); + } + } + } + + /* + Stuff that can be done in an arbitarily large dtime + */ + for(std::vector::iterator i = m_players.begin(); + i != m_players.end(); ++i) { + Player *player = *i; + + /* + Handle non-local players + */ + if(player->isLocal() == false) { + // Move + player->move(dtime, this, 100*BS); + + } + } + + // Update lighting on local player (used for wield item) + u32 day_night_ratio = getDayNightRatio(); + { + // Get node at head + + // On InvalidPositionException, use this as default + // (day: LIGHT_SUN, night: 0) + MapNode node_at_lplayer(CONTENT_AIR, 0x0f, 0); + + v3s16 p = lplayer->getLightPosition(); + node_at_lplayer = m_map->getNodeNoEx(p); + + u16 light = getInteriorLight(node_at_lplayer, 0, m_gamedef->ndef()); + u8 day = light & 0xff; + u8 night = (light >> 8) & 0xff; + finalColorBlend(lplayer->light_color, day, night, day_night_ratio); + } + + /* + Step active objects and update lighting of them + */ + + g_profiler->avg("CEnv: num of objects", m_active_objects.size()); + bool update_lighting = m_active_object_light_update_interval.step(dtime, 0.21); + for(std::map::iterator + i = m_active_objects.begin(); + i != m_active_objects.end(); ++i) + { + ClientActiveObject* obj = i->second; + // Step object + obj->step(dtime, this); + + if(update_lighting) + { + // Update lighting + u8 light = 0; + bool pos_ok; + + // Get node at head + v3s16 p = obj->getLightPosition(); + MapNode n = m_map->getNodeNoEx(p, &pos_ok); + if (pos_ok) + light = n.getLightBlend(day_night_ratio, m_gamedef->ndef()); + else + light = blend_light(day_night_ratio, LIGHT_SUN, 0); + + obj->updateLight(light); + } + } + + /* + Step and handle simple objects + */ + g_profiler->avg("CEnv: num of simple objects", m_simple_objects.size()); + for(std::vector::iterator + i = m_simple_objects.begin(); i != m_simple_objects.end();) { + std::vector::iterator cur = i; + ClientSimpleObject *simple = *cur; + + simple->step(dtime); + if(simple->m_to_be_removed) { + delete simple; + i = m_simple_objects.erase(cur); + } + else { + ++i; + } + } +} + +void ClientEnvironment::addSimpleObject(ClientSimpleObject *simple) +{ + m_simple_objects.push_back(simple); +} + +ClientActiveObject* ClientEnvironment::getActiveObject(u16 id) +{ + std::map::iterator n; + n = m_active_objects.find(id); + if(n == m_active_objects.end()) + return NULL; + return n->second; +} + +bool isFreeClientActiveObjectId(u16 id, + std::map &objects) +{ + if(id == 0) + return false; + + return objects.find(id) == objects.end(); +} + +u16 getFreeClientActiveObjectId( + std::map &objects) +{ + //try to reuse id's as late as possible + static u16 last_used_id = 0; + u16 startid = last_used_id; + for(;;) + { + last_used_id ++; + if(isFreeClientActiveObjectId(last_used_id, objects)) + return last_used_id; + + if(last_used_id == startid) + return 0; + } +} + +u16 ClientEnvironment::addActiveObject(ClientActiveObject *object) +{ + assert(object); // Pre-condition + if(object->getId() == 0) + { + u16 new_id = getFreeClientActiveObjectId(m_active_objects); + if(new_id == 0) + { + infostream<<"ClientEnvironment::addActiveObject(): " + <<"no free ids available"<setId(new_id); + } + if(isFreeClientActiveObjectId(object->getId(), m_active_objects) == false) + { + infostream<<"ClientEnvironment::addActiveObject(): " + <<"id is not free ("<getId()<<")"<getId()] = object; + object->addToScene(m_smgr, m_texturesource, m_irr); + { // Update lighting immediately + u8 light = 0; + bool pos_ok; + + // Get node at head + v3s16 p = object->getLightPosition(); + MapNode n = m_map->getNodeNoEx(p, &pos_ok); + if (pos_ok) + light = n.getLightBlend(getDayNightRatio(), m_gamedef->ndef()); + else + light = blend_light(getDayNightRatio(), LIGHT_SUN, 0); + + object->updateLight(light); + } + return object->getId(); +} + +void ClientEnvironment::addActiveObject(u16 id, u8 type, + const std::string &init_data) +{ + ClientActiveObject* obj = + ClientActiveObject::create((ActiveObjectType) type, m_gamedef, this); + if(obj == NULL) + { + infostream<<"ClientEnvironment::addActiveObject(): " + <<"id="<setId(id); + + try + { + obj->initialize(init_data); + } + catch(SerializationError &e) + { + errorstream<<"ClientEnvironment::addActiveObject():" + <<" id="<removeFromScene(true); + delete obj; + m_active_objects.erase(id); +} + +void ClientEnvironment::processActiveObjectMessage(u16 id, + const std::string &data) +{ + ClientActiveObject* obj = getActiveObject(id); + if(obj == NULL) + { + infostream<<"ClientEnvironment::processActiveObjectMessage():" + <<" got message for id="<processMessage(data); + } + catch(SerializationError &e) + { + errorstream<<"ClientEnvironment::processActiveObjectMessage():" + <<" id="< getPlayers(); + std::vector getPlayers(bool ignore_disconnected); + + u32 getDayNightRatio(); + + // 0-23999 + virtual void setTimeOfDay(u32 time); + u32 getTimeOfDay(); + float getTimeOfDayF(); + + void stepTimeOfDay(float dtime); + + void setTimeOfDaySpeed(float speed); + float getTimeOfDaySpeed(); + + void setDayNightRatioOverride(bool enable, u32 value) + { + m_enable_day_night_ratio_override = enable; + m_day_night_ratio_override = value; + } + + // counter used internally when triggering ABMs + u32 m_added_objects; + +protected: + // peer_ids in here should be unique, except that there may be many 0s + std::vector m_players; + // Time of day in milli-hours (0-23999); determines day and night + u32 m_time_of_day; + // Time of day in 0...1 + float m_time_of_day_f; + float m_time_of_day_speed; + // Used to buffer dtime for adding to m_time_of_day + float m_time_counter; + // Overriding the day-night ratio is useful for custom sky visuals + bool m_enable_day_night_ratio_override; + u32 m_day_night_ratio_override; + + /* TODO: Add a callback function so these can be updated when a setting + * changes. At this point in time it doesn't matter (e.g. /set + * is documented to change server settings only) + * + * TODO: Local caching of settings is not optimal and should at some stage + * be updated to use a global settings object for getting thse values + * (as opposed to the this local caching). This can be addressed in + * a later release. + */ + bool m_cache_enable_shaders; + +private: + JMutex m_timeofday_lock; + JMutex m_time_lock; + +}; + +/* + Active block modifier interface. + + These are fed into ServerEnvironment at initialization time; + ServerEnvironment handles deleting them. +*/ + +class ActiveBlockModifier +{ +public: + ActiveBlockModifier(){}; + virtual ~ActiveBlockModifier(){}; + + // Set of contents to trigger on + virtual std::set getTriggerContents()=0; + // Set of required neighbors (trigger doesn't happen if none are found) + // Empty = do not check neighbors + virtual std::set getRequiredNeighbors() + { return std::set(); } + // Trigger interval in seconds + virtual float getTriggerInterval() = 0; + // Random chance of (1 / return value), 0 is disallowed + virtual u32 getTriggerChance() = 0; + // This is called usually at interval for 1/chance of the nodes + virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n){}; + virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n, + u32 active_object_count, u32 active_object_count_wider){}; +}; + +struct ABMWithState +{ + ActiveBlockModifier *abm; + float timer; + + ABMWithState(ActiveBlockModifier *abm_); +}; + +/* + List of active blocks, used by ServerEnvironment +*/ + +class ActiveBlockList +{ +public: + void update(std::vector &active_positions, + s16 radius, + std::set &blocks_removed, + std::set &blocks_added); + + bool contains(v3s16 p){ + return (m_list.find(p) != m_list.end()); + } + + void clear(){ + m_list.clear(); + } + + std::set m_list; + std::set m_forceloaded_list; + +private: +}; + +/* + The server-side environment. + + This is not thread-safe. Server uses an environment mutex. +*/ + +class ServerEnvironment : public Environment +{ +public: + ServerEnvironment(ServerMap *map, GameScripting *scriptIface, + IGameDef *gamedef, const std::string &path_world); + ~ServerEnvironment(); + + Map & getMap(); + + ServerMap & getServerMap(); + + //TODO find way to remove this fct! + GameScripting* getScriptIface() + { return m_script; } + + IGameDef *getGameDef() + { return m_gamedef; } + + float getSendRecommendedInterval() + { return m_recommended_send_interval; } + + // Save players + void saveLoadedPlayers(); + void savePlayer(const std::string &playername); + Player *loadPlayer(const std::string &playername); + + /* + Save and load time of day and game timer + */ + void saveMeta(); + void loadMeta(); + + /* + External ActiveObject interface + ------------------------------------------- + */ + + ServerActiveObject* getActiveObject(u16 id); + + /* + Add an active object to the environment. + Environment handles deletion of object. + Object may be deleted by environment immediately. + If id of object is 0, assigns a free id to it. + Returns the id of the object. + Returns 0 if not added and thus deleted. + */ + u16 addActiveObject(ServerActiveObject *object); + + /* + Add an active object as a static object to the corresponding + MapBlock. + Caller allocates memory, ServerEnvironment frees memory. + Return value: true if succeeded, false if failed. + (note: not used, pending removal from engine) + */ + //bool addActiveObjectAsStatic(ServerActiveObject *object); + + /* + Find out what new objects have been added to + inside a radius around a position + */ + void getAddedActiveObjects(v3s16 pos, s16 radius, + s16 player_radius, + std::set ¤t_objects, + std::set &added_objects); + + /* + Find out what new objects have been removed from + inside a radius around a position + */ + void getRemovedActiveObjects(v3s16 pos, s16 radius, + s16 player_radius, + std::set ¤t_objects, + std::set &removed_objects); + + /* + Get the next message emitted by some active object. + Returns a message with id=0 if no messages are available. + */ + ActiveObjectMessage getActiveObjectMessage(); + + /* + Activate objects and dynamically modify for the dtime determined + from timestamp and additional_dtime + */ + void activateBlock(MapBlock *block, u32 additional_dtime=0); + + /* + ActiveBlockModifiers + ------------------------------------------- + */ + + void addActiveBlockModifier(ActiveBlockModifier *abm); + + /* + Other stuff + ------------------------------------------- + */ + + // Script-aware node setters + bool setNode(v3s16 p, const MapNode &n); + bool removeNode(v3s16 p); + bool swapNode(v3s16 p, const MapNode &n); + + // Find all active objects inside a radius around a point + std::set getObjectsInsideRadius(v3f pos, float radius); + + // Clear all objects, loading and going through every MapBlock + void clearAllObjects(); + + // This makes stuff happen + void step(f32 dtime); + + //check if there's a line of sight between two positions + bool line_of_sight(v3f pos1, v3f pos2, float stepsize=1.0, v3s16 *p=NULL); + + u32 getGameTime() { return m_game_time; } + + void reportMaxLagEstimate(float f) { m_max_lag_estimate = f; } + float getMaxLagEstimate() { return m_max_lag_estimate; } + + std::set* getForceloadedBlocks() { return &m_active_blocks.m_forceloaded_list; }; + +private: + + /* + Internal ActiveObject interface + ------------------------------------------- + */ + + /* + Add an active object to the environment. + + Called by addActiveObject. + + Object may be deleted by environment immediately. + If id of object is 0, assigns a free id to it. + Returns the id of the object. + Returns 0 if not added and thus deleted. + */ + u16 addActiveObjectRaw(ServerActiveObject *object, bool set_changed, u32 dtime_s); + + /* + Remove all objects that satisfy (m_removed && m_known_by_count==0) + */ + void removeRemovedObjects(); + + /* + Convert stored objects from block to active + */ + void activateObjects(MapBlock *block, u32 dtime_s); + + /* + Convert objects that are not in active blocks to static. + + If m_known_by_count != 0, active object is not deleted, but static + data is still updated. + + If force_delete is set, active object is deleted nevertheless. It + shall only be set so in the destructor of the environment. + */ + void deactivateFarObjects(bool force_delete); + + /* + Member variables + */ + + // The map + ServerMap *m_map; + // Lua state + GameScripting* m_script; + // Game definition + IGameDef *m_gamedef; + // World path + const std::string m_path_world; + // Active object list + std::map m_active_objects; + // Outgoing network message buffer for active objects + std::list m_active_object_messages; + // Some timers + float m_send_recommended_timer; + IntervalLimiter m_object_management_interval; + // List of active blocks + ActiveBlockList m_active_blocks; + IntervalLimiter m_active_blocks_management_interval; + IntervalLimiter m_active_block_modifier_interval; + IntervalLimiter m_active_blocks_nodemetadata_interval; + int m_active_block_interval_overload_skip; + // Time from the beginning of the game in seconds. + // Incremented in step(). + u32 m_game_time; + // A helper variable for incrementing the latter + float m_game_time_fraction_counter; + std::vector m_abms; + // An interval for generally sending object positions and stuff + float m_recommended_send_interval; + // Estimate for general maximum lag as determined by server. + // Can raise to high values like 15s with eg. map generation mods. + float m_max_lag_estimate; +}; + +#ifndef SERVER + +#include "clientobject.h" +class ClientSimpleObject; + +/* + The client-side environment. + + This is not thread-safe. + Must be called from main (irrlicht) thread (uses the SceneManager) + Client uses an environment mutex. +*/ + +enum ClientEnvEventType +{ + CEE_NONE, + CEE_PLAYER_DAMAGE, + CEE_PLAYER_BREATH +}; + +struct ClientEnvEvent +{ + ClientEnvEventType type; + union { + //struct{ + //} none; + struct{ + u8 amount; + bool send_to_server; + } player_damage; + struct{ + u16 amount; + } player_breath; + }; +}; + +class ClientEnvironment : public Environment +{ +public: + ClientEnvironment(ClientMap *map, scene::ISceneManager *smgr, + ITextureSource *texturesource, IGameDef *gamedef, + IrrlichtDevice *device); + ~ClientEnvironment(); + + Map & getMap(); + ClientMap & getClientMap(); + + IGameDef *getGameDef() + { return m_gamedef; } + + void step(f32 dtime); + + virtual void addPlayer(Player *player); + LocalPlayer * getLocalPlayer(); + + /* + ClientSimpleObjects + */ + + void addSimpleObject(ClientSimpleObject *simple); + + /* + ActiveObjects + */ + + ClientActiveObject* getActiveObject(u16 id); + + /* + Adds an active object to the environment. + Environment handles deletion of object. + Object may be deleted by environment immediately. + If id of object is 0, assigns a free id to it. + Returns the id of the object. + Returns 0 if not added and thus deleted. + */ + u16 addActiveObject(ClientActiveObject *object); + + void addActiveObject(u16 id, u8 type, const std::string &init_data); + void removeActiveObject(u16 id); + + void processActiveObjectMessage(u16 id, const std::string &data); + + /* + Callbacks for activeobjects + */ + + void damageLocalPlayer(u8 damage, bool handle_hp=true); + void updateLocalPlayerBreath(u16 breath); + + /* + Client likes to call these + */ + + // Get all nearby objects + void getActiveObjects(v3f origin, f32 max_d, + std::vector &dest); + + // Get event from queue. CEE_NONE is returned if queue is empty. + ClientEnvEvent getClientEvent(); + + u16 m_attachements[USHRT_MAX]; + + std::list getPlayerNames() + { return m_player_names; } + void addPlayerName(std::string name) + { m_player_names.push_back(name); } + void removePlayerName(std::string name) + { m_player_names.remove(name); } + void updateCameraOffset(v3s16 camera_offset) + { m_camera_offset = camera_offset; } + v3s16 getCameraOffset() + { return m_camera_offset; } + +private: + ClientMap *m_map; + scene::ISceneManager *m_smgr; + ITextureSource *m_texturesource; + IGameDef *m_gamedef; + IrrlichtDevice *m_irr; + std::map m_active_objects; + std::vector m_simple_objects; + std::list m_client_event_queue; + IntervalLimiter m_active_object_light_update_interval; + IntervalLimiter m_lava_hurt_interval; + IntervalLimiter m_drowning_interval; + IntervalLimiter m_breathing_interval; + std::list m_player_names; + v3s16 m_camera_offset; +}; + +#endif + +#endif + diff --git a/src/event.h b/src/event.h new file mode 100644 index 0000000..cfc222d --- /dev/null +++ b/src/event.h @@ -0,0 +1,72 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef EVENT_HEADER +#define EVENT_HEADER + +class MtEvent +{ +public: + virtual ~MtEvent(){}; + //virtual MtEvent* clone(){ return new IEvent; } + virtual const char* getType() const = 0; + + MtEvent* checkIs(const std::string &type) + { + if(type == getType()) + return this; + return NULL; + } +}; + +// An event with no parameters and customizable name +class SimpleTriggerEvent: public MtEvent +{ + const char *type; +public: + SimpleTriggerEvent(const char *type): + type(type) + {} + const char* getType() const + {return type;} +}; + +class MtEventReceiver +{ +public: + virtual ~MtEventReceiver(){}; + virtual void onEvent(MtEvent *e) = 0; +}; + +typedef void (*event_receive_func)(MtEvent *e, void *data); + +class MtEventManager +{ +public: + virtual ~MtEventManager(){}; + virtual void put(MtEvent *e) = 0; + virtual void reg(const char *type, event_receive_func f, void *data) = 0; + // If data==NULL, every occurence of f is deregistered. + virtual void dereg(const char *type, event_receive_func f, void *data) = 0; + virtual void reg(MtEventReceiver *r, const char *type) = 0; + virtual void dereg(MtEventReceiver *r, const char *type) = 0; +}; + +#endif + diff --git a/src/event_manager.h b/src/event_manager.h new file mode 100644 index 0000000..33d99b2 --- /dev/null +++ b/src/event_manager.h @@ -0,0 +1,115 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef EVENT_MANAGER_HEADER +#define EVENT_MANAGER_HEADER + +#include "event.h" +#include +#include + +class EventManager: public MtEventManager +{ + static void receiverReceive(MtEvent *e, void *data) + { + MtEventReceiver *r = (MtEventReceiver*)data; + r->onEvent(e); + } + struct FuncSpec{ + event_receive_func f; + void *d; + FuncSpec(event_receive_func f, void *d): + f(f), d(d) + {} + }; + struct Dest{ + std::list funcs; + }; + std::map m_dest; + +public: + ~EventManager() + { + } + void put(MtEvent *e) + { + std::map::iterator i = m_dest.find(e->getType()); + if(i != m_dest.end()){ + std::list &funcs = i->second.funcs; + for(std::list::iterator i = funcs.begin(); + i != funcs.end(); i++){ + (*(i->f))(e, i->d); + } + } + delete e; + } + void reg(const char *type, event_receive_func f, void *data) + { + std::map::iterator i = m_dest.find(type); + if(i != m_dest.end()){ + i->second.funcs.push_back(FuncSpec(f, data)); + } else{ + std::list funcs; + Dest dest; + dest.funcs.push_back(FuncSpec(f, data)); + m_dest[type] = dest; + } + } + void dereg(const char *type, event_receive_func f, void *data) + { + if(type != NULL){ + std::map::iterator i = m_dest.find(type); + if(i != m_dest.end()){ + std::list &funcs = i->second.funcs; + std::list::iterator j = funcs.begin(); + while(j != funcs.end()){ + bool remove = (j->f == f && (!data || j->d == data)); + if(remove) + funcs.erase(j++); + else + j++; + } + } + } else{ + for(std::map::iterator + i = m_dest.begin(); i != m_dest.end(); i++){ + std::list &funcs = i->second.funcs; + std::list::iterator j = funcs.begin(); + while(j != funcs.end()){ + bool remove = (j->f == f && (!data || j->d == data)); + if(remove) + funcs.erase(j++); + else + j++; + } + } + } + } + void reg(MtEventReceiver *r, const char *type) + { + reg(type, EventManager::receiverReceive, r); + } + void dereg(MtEventReceiver *r, const char *type) + { + dereg(type, EventManager::receiverReceive, r); + } +}; + +#endif + diff --git a/src/exceptions.h b/src/exceptions.h new file mode 100644 index 0000000..5b716c2 --- /dev/null +++ b/src/exceptions.h @@ -0,0 +1,162 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef EXCEPTIONS_HEADER +#define EXCEPTIONS_HEADER + +#include +#include + + +class BaseException : public std::exception +{ +public: + BaseException(const std::string &s) throw() + { + m_s = s; + } + ~BaseException() throw() {} + virtual const char * what() const throw() + { + return m_s.c_str(); + } +protected: + std::string m_s; +}; + +class AsyncQueuedException : public BaseException { +public: + AsyncQueuedException(const std::string &s): BaseException(s) {} +}; + +class NotImplementedException : public BaseException { +public: + NotImplementedException(const std::string &s): BaseException(s) {} +}; + +class AlreadyExistsException : public BaseException { +public: + AlreadyExistsException(const std::string &s): BaseException(s) {} +}; + +class VersionMismatchException : public BaseException { +public: + VersionMismatchException(const std::string &s): BaseException(s) {} +}; + +class FileNotGoodException : public BaseException { +public: + FileNotGoodException(const std::string &s): BaseException(s) {} +}; + +class SerializationError : public BaseException { +public: + SerializationError(const std::string &s): BaseException(s) {} +}; + +class LoadError : public BaseException { +public: + LoadError(const std::string &s): BaseException(s) {} +}; + +class ContainerFullException : public BaseException { +public: + ContainerFullException(const std::string &s): BaseException(s) {} +}; + +class SettingNotFoundException : public BaseException { +public: + SettingNotFoundException(const std::string &s): BaseException(s) {} +}; + +class InvalidFilenameException : public BaseException { +public: + InvalidFilenameException(const std::string &s): BaseException(s) {} +}; + +class ProcessingLimitException : public BaseException { +public: + ProcessingLimitException(const std::string &s): BaseException(s) {} +}; + +class CommandLineError : public BaseException { +public: + CommandLineError(const std::string &s): BaseException(s) {} +}; + +class ItemNotFoundException : public BaseException { +public: + ItemNotFoundException(const std::string &s): BaseException(s) {} +}; + +class ServerError : public BaseException { +public: + ServerError(const std::string &s): BaseException(s) {} +}; + +class ClientStateError : public BaseException { +public: + ClientStateError(std::string s): BaseException(s) {} +}; + +/* + Some "old-style" interrupts: +*/ + +class InvalidNoiseParamsException : public BaseException { +public: + InvalidNoiseParamsException(): + BaseException("One or more noise parameters were invalid or require " + "too much memory") + {} + + InvalidNoiseParamsException(const std::string &s): + BaseException(s) + {} +}; + +class InvalidPositionException : public BaseException +{ +public: + InvalidPositionException(): + BaseException("Somebody tried to get/set something in a nonexistent position.") + {} + InvalidPositionException(const std::string &s): + BaseException(s) + {} +}; + +class TargetInexistentException : public std::exception +{ + virtual const char * what() const throw() + { + return "Somebody tried to refer to something that doesn't exist."; + } +}; + +class NullPointerException : public std::exception +{ + virtual const char * what() const throw() + { + return "NullPointerException"; + } +}; + +#endif + diff --git a/src/filecache.cpp b/src/filecache.cpp new file mode 100644 index 0000000..f1694d8 --- /dev/null +++ b/src/filecache.cpp @@ -0,0 +1,89 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2013 Jonathan Neuschäfer + +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. +*/ + +#include "filecache.h" + +#include "network/networkprotocol.h" +#include "log.h" +#include "filesys.h" +#include +#include +#include +#include + +bool FileCache::loadByPath(const std::string &path, std::ostream &os) +{ + std::ifstream fis(path.c_str(), std::ios_base::binary); + + if(!fis.good()){ + verbosestream<<"FileCache: File not found in cache: " + < +Copyright (C) 2013 Jonathan Neuschäfer + +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. +*/ + +#ifndef FILECACHE_HEADER +#define FILECACHE_HEADER + +#include +#include + +class FileCache +{ +public: + /* + 'dir' is the file cache directory to use. + */ + FileCache(std::string dir): + m_dir(dir) + { + } + + bool update(const std::string &name, const std::string &data); + bool load(const std::string &name, std::ostream &os); +private: + std::string m_dir; + + bool loadByPath(const std::string &path, std::ostream &os); + bool updateByPath(const std::string &path, const std::string &data); +}; + +#endif diff --git a/src/filesys.cpp b/src/filesys.cpp new file mode 100644 index 0000000..7847156 --- /dev/null +++ b/src/filesys.cpp @@ -0,0 +1,681 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#include "filesys.h" +#include "util/string.h" +#include +#include +#include +#include +#include +#include "log.h" +#include "config.h" + +namespace fs +{ + +#ifdef _WIN32 // WINDOWS + +#define _WIN32_WINNT 0x0501 +#include + +std::vector GetDirListing(std::string pathstring) +{ + std::vector listing; + + WIN32_FIND_DATA FindFileData; + HANDLE hFind = INVALID_HANDLE_VALUE; + DWORD dwError; + + std::string dirSpec = pathstring + "\\*"; + + // Find the first file in the directory. + hFind = FindFirstFile(dirSpec.c_str(), &FindFileData); + + if (hFind == INVALID_HANDLE_VALUE) { + dwError = GetLastError(); + if (dwError != ERROR_FILE_NOT_FOUND && dwError != ERROR_PATH_NOT_FOUND) { + errorstream << "GetDirListing: FindFirstFile error." + << " Error is " << dwError << std::endl; + } + } else { + // NOTE: + // Be very sure to not include '..' in the results, it will + // result in an epic failure when deleting stuff. + + DirListNode node; + node.name = FindFileData.cFileName; + node.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; + if (node.name != "." && node.name != "..") + listing.push_back(node); + + // List all the other files in the directory. + while (FindNextFile(hFind, &FindFileData) != 0) { + DirListNode node; + node.name = FindFileData.cFileName; + node.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; + if(node.name != "." && node.name != "..") + listing.push_back(node); + } + + dwError = GetLastError(); + FindClose(hFind); + if (dwError != ERROR_NO_MORE_FILES) { + errorstream << "GetDirListing: FindNextFile error." + << " Error is " << dwError << std::endl; + listing.clear(); + return listing; + } + } + return listing; +} + +bool CreateDir(std::string path) +{ + bool r = CreateDirectory(path.c_str(), NULL); + if(r == true) + return true; + if(GetLastError() == ERROR_ALREADY_EXISTS) + return true; + return false; +} + +bool PathExists(std::string path) +{ + return (GetFileAttributes(path.c_str()) != INVALID_FILE_ATTRIBUTES); +} + +bool IsDir(std::string path) +{ + DWORD attr = GetFileAttributes(path.c_str()); + return (attr != INVALID_FILE_ATTRIBUTES && + (attr & FILE_ATTRIBUTE_DIRECTORY)); +} + +bool IsDirDelimiter(char c) +{ + return c == '/' || c == '\\'; +} + +bool RecursiveDelete(std::string path) +{ + infostream<<"Recursively deleting \""< content = GetDirListing(path); + for(int i=0; i buf(bufsize); + DWORD len = GetTempPath(bufsize, &buf[0]); + if(len == 0 || len > bufsize){ + errorstream<<"GetTempPath failed, error = "< +#include +#include +#include +#include + +std::vector GetDirListing(std::string pathstring) +{ + std::vector listing; + + DIR *dp; + struct dirent *dirp; + if((dp = opendir(pathstring.c_str())) == NULL) { + //infostream<<"Error("<d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0) + continue; + + DirListNode node; + node.name = dirp->d_name; + + int isdir = -1; // -1 means unknown + + /* + POSIX doesn't define d_type member of struct dirent and + certain filesystems on glibc/Linux will only return + DT_UNKNOWN for the d_type member. + + Also we don't know whether symlinks are directories or not. + */ +#ifdef _DIRENT_HAVE_D_TYPE + if(dirp->d_type != DT_UNKNOWN && dirp->d_type != DT_LNK) + isdir = (dirp->d_type == DT_DIR); +#endif /* _DIRENT_HAVE_D_TYPE */ + + /* + Was d_type DT_UNKNOWN, DT_LNK or nonexistent? + If so, try stat(). + */ + if(isdir == -1) { + struct stat statbuf; + if (stat((pathstring + "/" + node.name).c_str(), &statbuf)) + continue; + isdir = ((statbuf.st_mode & S_IFDIR) == S_IFDIR); + } + node.dir = isdir; + listing.push_back(node); + } + closedir(dp); + + return listing; +} + +bool CreateDir(std::string path) +{ + int r = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if(r == 0) + { + return true; + } + else + { + // If already exists, return true + if(errno == EEXIST) + return true; + return false; + } +} + +bool PathExists(std::string path) +{ + struct stat st; + return (stat(path.c_str(),&st) == 0); +} + +bool IsDir(std::string path) +{ + struct stat statbuf; + if(stat(path.c_str(), &statbuf)) + return false; // Actually error; but certainly not a directory + return ((statbuf.st_mode & S_IFDIR) == S_IFDIR); +} + +bool IsDirDelimiter(char c) +{ + return c == '/'; +} + +bool RecursiveDelete(std::string path) +{ + /* + Execute the 'rm' command directly, by fork() and execve() + */ + + infostream<<"Removing \""< &dst) +{ + std::vector content = GetDirListing(path); + for(unsigned int i=0; i &paths) +{ + bool success = true; + // Go backwards to succesfully delete the output of GetRecursiveSubPaths + for(int i=paths.size()-1; i>=0; i--){ + const std::string &path = paths[i]; + bool did = DeleteSingleFileOrEmptyDirectory(path); + if(!did){ + errorstream<<"Failed to delete "< list = GetDirListing(path); + for(unsigned int i=0; i tocreate; + std::string basepath = path; + while(!PathExists(basepath)) + { + tocreate.push_back(basepath); + basepath = RemoveLastPathComponent(basepath); + if(basepath.empty()) + break; + } + for(int i=tocreate.size()-1;i>=0;i--) + if(!CreateDir(tocreate[i])) + return false; + return true; +} + +bool CopyFileContents(std::string source, std::string target) +{ + FILE *sourcefile = fopen(source.c_str(), "rb"); + if(sourcefile == NULL){ + errorstream< 0){ + fwrite(readbuffer, 1, readbytes, targetfile); + } + if(feof(sourcefile) || ferror(sourcefile)){ + // flush destination file to catch write errors + // (e.g. disk full) + fflush(targetfile); + done = true; + } + if(ferror(targetfile)){ + errorstream< content = fs::GetDirListing(source); + + for(unsigned int i=0; i < content.size(); i++){ + std::string sourcechild = source + DIR_DELIM + content[i].name; + std::string targetchild = target + DIR_DELIM + content[i].name; + if(content[i].dir){ + if(!fs::CopyDir(sourcechild, targetchild)){ + retval = false; + } + } + else { + if(!fs::CopyFileContents(sourcechild, targetchild)){ + retval = false; + } + } + } + return retval; + } + else { + return false; + } +} + +bool PathStartsWith(std::string path, std::string prefix) +{ + size_t pathsize = path.size(); + size_t pathpos = 0; + size_t prefixsize = prefix.size(); + size_t prefixpos = 0; + for(;;){ + bool delim1 = pathpos == pathsize + || IsDirDelimiter(path[pathpos]); + bool delim2 = prefixpos == prefixsize + || IsDirDelimiter(prefix[prefixpos]); + + if(delim1 != delim2) + return false; + + if(delim1){ + while(pathpos < pathsize && + IsDirDelimiter(path[pathpos])) + ++pathpos; + while(prefixpos < prefixsize && + IsDirDelimiter(prefix[prefixpos])) + ++prefixpos; + if(prefixpos == prefixsize) + return true; + if(pathpos == pathsize) + return false; + } + else{ + size_t len = 0; + do{ + char pathchar = path[pathpos+len]; + char prefixchar = prefix[prefixpos+len]; + if(FILESYS_CASE_INSENSITIVE){ + pathchar = tolower(pathchar); + prefixchar = tolower(prefixchar); + } + if(pathchar != prefixchar) + return false; + ++len; + } while(pathpos+len < pathsize + && !IsDirDelimiter(path[pathpos+len]) + && prefixpos+len < prefixsize + && !IsDirDelimiter( + prefix[prefixpos+len])); + pathpos += len; + prefixpos += len; + } + } +} + +std::string RemoveLastPathComponent(std::string path, + std::string *removed, int count) +{ + if(removed) + *removed = ""; + + size_t remaining = path.size(); + + for(int i = 0; i < count; ++i){ + // strip a dir delimiter + while(remaining != 0 && IsDirDelimiter(path[remaining-1])) + remaining--; + // strip a path component + size_t component_end = remaining; + while(remaining != 0 && !IsDirDelimiter(path[remaining-1])) + remaining--; + size_t component_start = remaining; + // strip a dir delimiter + while(remaining != 0 && IsDirDelimiter(path[remaining-1])) + remaining--; + if(removed){ + std::string component = path.substr(component_start, + component_end - component_start); + if(i) + *removed = component + DIR_DELIM + *removed; + else + *removed = component; + } + } + return path.substr(0, remaining); +} + +std::string RemoveRelativePathComponents(std::string path) +{ + size_t pos = path.size(); + size_t dotdot_count = 0; + while(pos != 0){ + size_t component_with_delim_end = pos; + // skip a dir delimiter + while(pos != 0 && IsDirDelimiter(path[pos-1])) + pos--; + // strip a path component + size_t component_end = pos; + while(pos != 0 && !IsDirDelimiter(path[pos-1])) + pos--; + size_t component_start = pos; + + std::string component = path.substr(component_start, + component_end - component_start); + bool remove_this_component = false; + if(component == "."){ + remove_this_component = true; + } + else if(component == ".."){ + remove_this_component = true; + dotdot_count += 1; + } + else if(dotdot_count != 0){ + remove_this_component = true; + dotdot_count -= 1; + } + + if(remove_this_component){ + while(pos != 0 && IsDirDelimiter(path[pos-1])) + pos--; + path = path.substr(0, pos) + DIR_DELIM + + path.substr(component_with_delim_end, + std::string::npos); + pos++; + } + } + + if(dotdot_count > 0) + return ""; + + // remove trailing dir delimiters + pos = path.size(); + while(pos != 0 && IsDirDelimiter(path[pos-1])) + pos--; + return path.substr(0, pos); +} + +bool safeWriteToFile(const std::string &path, const std::string &content) +{ + std::string tmp_file = path + ".~mt"; + + // Write to a tmp file + std::ofstream os(tmp_file.c_str(), std::ios::binary); + if (!os.good()) + return false; + os << content; + os.flush(); + os.close(); + if (os.fail()) { + remove(tmp_file.c_str()); + return false; + } + + // Copy file + remove(path.c_str()); + if(rename(tmp_file.c_str(), path.c_str())) { + remove(tmp_file.c_str()); + return false; + } else { + return true; + } +} + +} // namespace fs + diff --git a/src/filesys.h b/src/filesys.h new file mode 100644 index 0000000..1b3659a --- /dev/null +++ b/src/filesys.h @@ -0,0 +1,106 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef FILESYS_HEADER +#define FILESYS_HEADER + +#include +#include +#include "exceptions.h" + +#ifdef _WIN32 // WINDOWS +#define DIR_DELIM "\\" +#define FILESYS_CASE_INSENSITIVE 1 +#else // POSIX +#define DIR_DELIM "/" +#define FILESYS_CASE_INSENSITIVE 0 +#endif + +namespace fs +{ + +struct DirListNode +{ + std::string name; + bool dir; +}; +std::vector GetDirListing(std::string path); + +// Returns true if already exists +bool CreateDir(std::string path); + +bool PathExists(std::string path); + +bool IsDir(std::string path); + +bool IsDirDelimiter(char c); + +// Only pass full paths to this one. True on success. +// NOTE: The WIN32 version returns always true. +bool RecursiveDelete(std::string path); + +bool DeleteSingleFileOrEmptyDirectory(std::string path); + +// Returns path to temp directory, can return "" on error +std::string TempPath(); + +/* Multiplatform */ + +// The path itself not included +void GetRecursiveSubPaths(std::string path, std::vector &dst); + +// Tries to delete all, returns false if any failed +bool DeletePaths(const std::vector &paths); + +// Only pass full paths to this one. True on success. +bool RecursiveDeleteContent(std::string path); + +// Create all directories on the given path that don't already exist. +bool CreateAllDirs(std::string path); + +// Copy a regular file +bool CopyFileContents(std::string source, std::string target); + +// Copy directory and all subdirectories +// Omits files and subdirectories that start with a period +bool CopyDir(std::string source, std::string target); + +// Check if one path is prefix of another +// For example, "/tmp" is a prefix of "/tmp" and "/tmp/file" but not "/tmp2" +// Ignores case differences and '/' vs. '\\' on Windows +bool PathStartsWith(std::string path, std::string prefix); + +// Remove last path component and the dir delimiter before and/or after it, +// returns "" if there is only one path component. +// removed: If non-NULL, receives the removed component(s). +// count: Number of components to remove +std::string RemoveLastPathComponent(std::string path, + std::string *removed = NULL, int count = 1); + +// Remove "." and ".." path components and for every ".." removed, remove +// the last normal path component before it. Unlike AbsolutePath, +// this does not resolve symlinks and check for existence of directories. +std::string RemoveRelativePathComponents(std::string path); + +bool safeWriteToFile(const std::string &path, const std::string &content); + +}//fs + +#endif + diff --git a/src/fontengine.cpp b/src/fontengine.cpp new file mode 100644 index 0000000..fa30b40 --- /dev/null +++ b/src/fontengine.cpp @@ -0,0 +1,455 @@ +/* +Minetest +Copyright (C) 2010-2014 sapier + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include "fontengine.h" +#include "log.h" +#include "main.h" +#include "config.h" +#include "porting.h" +#include "constants.h" +#include "filesys.h" + +#if USE_FREETYPE +#include "gettext.h" +#include "xCGUITTFont.h" +#endif + +/** maximum size distance for getting a "similar" font size */ +#define MAX_FONT_SIZE_OFFSET 10 + +/** reference to access font engine, has to be initialized by main */ +FontEngine* g_fontengine = NULL; + +/** callback to be used on change of font size setting */ +static void font_setting_changed(const std::string, void *userdata) { + g_fontengine->readSettings(); +} + +/******************************************************************************/ +FontEngine::FontEngine(Settings* main_settings, gui::IGUIEnvironment* env) : + m_settings(main_settings), + m_env(env), + m_font_cache(), + m_currentMode(FM_Standard), + m_lastMode(), + m_lastSize(0), + m_lastFont(NULL) +{ + + for (unsigned int i = 0; i < FM_MaxMode; i++) { + m_default_size[i] = (FontMode) FONT_SIZE_UNSPECIFIED; + } + + assert(m_settings != NULL); // pre-condition + assert(m_env != NULL); // pre-condition + assert(m_env->getSkin() != NULL); // pre-condition + + m_currentMode = FM_Simple; + +#if USE_FREETYPE + if (g_settings->getBool("freetype")) { + m_default_size[FM_Standard] = m_settings->getU16("font_size"); + m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size"); + m_default_size[FM_Mono] = m_settings->getU16("mono_font_size"); + + if (is_yes(gettext("needs_fallback_font"))) { + m_currentMode = FM_Fallback; + } + else { + m_currentMode = FM_Standard; + } + } + + // having freetype but not using it is quite a strange case so we need to do + // special handling for it + if (m_currentMode == FM_Simple) { + std::stringstream fontsize; + fontsize << DEFAULT_FONT_SIZE; + m_settings->setDefault("font_size", fontsize.str()); + m_settings->setDefault("mono_font_size", fontsize.str()); + } +#endif + + m_default_size[FM_Simple] = m_settings->getU16("font_size"); + m_default_size[FM_SimpleMono] = m_settings->getU16("mono_font_size"); + + updateSkin(); + + if (m_currentMode == FM_Standard) { + m_settings->registerChangedCallback("font_size", font_setting_changed, NULL); + m_settings->registerChangedCallback("font_path", font_setting_changed, NULL); + m_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL); + m_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL); + } + else if (m_currentMode == FM_Fallback) { + m_settings->registerChangedCallback("fallback_font_size", font_setting_changed, NULL); + m_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL); + m_settings->registerChangedCallback("fallback_font_shadow", font_setting_changed, NULL); + m_settings->registerChangedCallback("fallback_font_shadow_alpha", font_setting_changed, NULL); + } + + m_settings->registerChangedCallback("mono_font_path", font_setting_changed, NULL); + m_settings->registerChangedCallback("mono_font_size", font_setting_changed, NULL); + m_settings->registerChangedCallback("screen_dpi", font_setting_changed, NULL); + m_settings->registerChangedCallback("gui_scaling", font_setting_changed, NULL); +} + +/******************************************************************************/ +FontEngine::~FontEngine() +{ + cleanCache(); +} + +/******************************************************************************/ +void FontEngine::cleanCache() +{ + for ( unsigned int i = 0; i < FM_MaxMode; i++) { + + for (std::map::iterator iter + = m_font_cache[i].begin(); + iter != m_font_cache[i].end(); iter++) { + iter->second->drop(); + iter->second = NULL; + } + m_font_cache[i].clear(); + } +} + +/******************************************************************************/ +irr::gui::IGUIFont* FontEngine::getFont(unsigned int font_size, FontMode mode) +{ + if (mode == FM_Unspecified) { + mode = m_currentMode; + } + else if ((mode == FM_Mono) && (m_currentMode == FM_Simple)) { + mode = FM_SimpleMono; + } + + if (font_size == FONT_SIZE_UNSPECIFIED) { + font_size = m_default_size[mode]; + } + + if ((font_size == m_lastSize) && (mode == m_lastMode)) { + return m_lastFont; + } + + if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) { + initFont(font_size, mode); + } + + if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) { + return NULL; + } + + m_lastSize = font_size; + m_lastMode = mode; + m_lastFont = m_font_cache[mode][font_size]; + + return m_font_cache[mode][font_size]; +} + +/******************************************************************************/ +unsigned int FontEngine::getTextHeight(unsigned int font_size, FontMode mode) +{ + irr::gui::IGUIFont* font = getFont(font_size, mode); + + // use current skin font as fallback + if (font == NULL) { + font = m_env->getSkin()->getFont(); + } + FATAL_ERROR_IF(font == NULL, "Could not get skin font"); + + return font->getDimension(L"Some unimportant example String").Height; +} + +/******************************************************************************/ +unsigned int FontEngine::getTextWidth(const std::wstring& text, + unsigned int font_size, FontMode mode) +{ + irr::gui::IGUIFont* font = getFont(font_size, mode); + + // use current skin font as fallback + if (font == NULL) { + font = m_env->getSkin()->getFont(); + } + FATAL_ERROR_IF(font == NULL, "Could not get font"); + + return font->getDimension(text.c_str()).Width; +} + + +/** get line height for a specific font (including empty room between lines) */ +unsigned int FontEngine::getLineHeight(unsigned int font_size, FontMode mode) +{ + irr::gui::IGUIFont* font = getFont(font_size, mode); + + // use current skin font as fallback + if (font == NULL) { + font = m_env->getSkin()->getFont(); + } + FATAL_ERROR_IF(font == NULL, "Could not get font"); + + return font->getDimension(L"Some unimportant example String").Height + + font->getKerningHeight(); +} + +/******************************************************************************/ +unsigned int FontEngine::getDefaultFontSize() +{ + return m_default_size[m_currentMode]; +} + +/******************************************************************************/ +void FontEngine::readSettings() +{ +#if USE_FREETYPE + if (g_settings->getBool("freetype")) { + m_default_size[FM_Standard] = m_settings->getU16("font_size"); + m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size"); + m_default_size[FM_Mono] = m_settings->getU16("mono_font_size"); + + if (is_yes(gettext("needs_fallback_font"))) { + m_currentMode = FM_Fallback; + } + else { + m_currentMode = FM_Standard; + } + } +#endif + m_default_size[FM_Simple] = m_settings->getU16("font_size"); + m_default_size[FM_SimpleMono] = m_settings->getU16("mono_font_size"); + + cleanCache(); + updateFontCache(); + updateSkin(); +} + +/******************************************************************************/ +void FontEngine::updateSkin() +{ + gui::IGUIFont *font = getFont(); + + if (font) + m_env->getSkin()->setFont(font); + else + errorstream << "FontEngine: Default font file: " << + "\n\t\"" << m_settings->get("font_path") << "\"" << + "\n\trequired for current screen configuration was not found" << + " or was invalid file format." << + "\n\tUsing irrlicht default font." << std::endl; + + // If we did fail to create a font our own make irrlicht find a default one + font = m_env->getSkin()->getFont(); + FATAL_ERROR_IF(font == NULL, "Could not create/get font"); + + u32 text_height = font->getDimension(L"Hello, world!").Height; + infostream << "text_height=" << text_height << std::endl; +} + +/******************************************************************************/ +void FontEngine::updateFontCache() +{ + /* the only font to be initialized is default one, + * all others are re-initialized on demand */ + initFont(m_default_size[m_currentMode], m_currentMode); + + /* reset font quick access */ + m_lastMode = FM_Unspecified; + m_lastSize = 0; + m_lastFont = NULL; +} + +/******************************************************************************/ +void FontEngine::initFont(unsigned int basesize, FontMode mode) +{ + + std::string font_config_prefix; + + if (mode == FM_Unspecified) { + mode = m_currentMode; + } + + switch (mode) { + + case FM_Standard: + font_config_prefix = ""; + break; + + case FM_Fallback: + font_config_prefix = "fallback_"; + break; + + case FM_Mono: + font_config_prefix = "mono_"; + if (m_currentMode == FM_Simple) + mode = FM_SimpleMono; + break; + + case FM_Simple: /* Fallthrough */ + case FM_SimpleMono: /* Fallthrough */ + default: + font_config_prefix = ""; + + } + + if (m_font_cache[mode].find(basesize) != m_font_cache[mode].end()) + return; + + if ((mode == FM_Simple) || (mode == FM_SimpleMono)) { + initSimpleFont(basesize, mode); + return; + } +#if USE_FREETYPE + else { + if (! is_yes(m_settings->get("freetype"))) { + return; + } + unsigned int size = floor( + porting::getDisplayDensity() * + m_settings->getFloat("gui_scaling") * + basesize); + u32 font_shadow = 0; + u32 font_shadow_alpha = 0; + + try { + font_shadow = + g_settings->getU16(font_config_prefix + "font_shadow"); + } catch (SettingNotFoundException&) {} + try { + font_shadow_alpha = + g_settings->getU16(font_config_prefix + "font_shadow_alpha"); + } catch (SettingNotFoundException&) {} + + std::string font_path = g_settings->get(font_config_prefix + "font_path"); + + irr::gui::IGUIFont* font = gui::CGUITTFont::createTTFont(m_env, + font_path.c_str(), size, true, true, font_shadow, + font_shadow_alpha); + + if (font != NULL) { + m_font_cache[mode][basesize] = font; + } + else { + errorstream << "FontEngine: failed to load freetype font: " + << font_path << std::endl; + } + } +#endif +} + +/** initialize a font without freetype */ +void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode) +{ + assert(mode == FM_Simple || mode == FM_SimpleMono); // pre-condition + + std::string font_path = ""; + if (mode == FM_Simple) { + font_path = m_settings->get("font_path"); + } else { + font_path = m_settings->get("mono_font_path"); + } + std::string basename = font_path; + std::string ending = font_path.substr(font_path.length() -4); + + if (ending == ".ttf") { + errorstream << "FontEngine: Not trying to open \"" << font_path + << "\" which seems to be a truetype font." << std::endl; + return; + } + + if ((ending == ".xml") || (ending == ".png")) { + basename = font_path.substr(0,font_path.length()-4); + } + + if (basesize == FONT_SIZE_UNSPECIFIED) + basesize = DEFAULT_FONT_SIZE; + + unsigned int size = floor( + porting::getDisplayDensity() * + m_settings->getFloat("gui_scaling") * + basesize); + + irr::gui::IGUIFont* font = NULL; + + for(unsigned int offset = 0; offset < MAX_FONT_SIZE_OFFSET; offset++) { + + // try opening positive offset + std::stringstream fontsize_plus_png; + fontsize_plus_png << basename << "_" << (size + offset) << ".png"; + + if (fs::PathExists(fontsize_plus_png.str())) { + font = m_env->getFont(fontsize_plus_png.str().c_str()); + + if (font) { + verbosestream << "FontEngine: found font: " << fontsize_plus_png.str() << std::endl; + break; + } + } + + std::stringstream fontsize_plus_xml; + fontsize_plus_xml << basename << "_" << (size + offset) << ".xml"; + + if (fs::PathExists(fontsize_plus_xml.str())) { + font = m_env->getFont(fontsize_plus_xml.str().c_str()); + + if (font) { + verbosestream << "FontEngine: found font: " << fontsize_plus_xml.str() << std::endl; + break; + } + } + + // try negative offset + std::stringstream fontsize_minus_png; + fontsize_minus_png << basename << "_" << (size - offset) << ".png"; + + if (fs::PathExists(fontsize_minus_png.str())) { + font = m_env->getFont(fontsize_minus_png.str().c_str()); + + if (font) { + verbosestream << "FontEngine: found font: " << fontsize_minus_png.str() << std::endl; + break; + } + } + + std::stringstream fontsize_minus_xml; + fontsize_minus_xml << basename << "_" << (size - offset) << ".xml"; + + if (fs::PathExists(fontsize_minus_xml.str())) { + font = m_env->getFont(fontsize_minus_xml.str().c_str()); + + if (font) { + verbosestream << "FontEngine: found font: " << fontsize_minus_xml.str() << std::endl; + break; + } + } + } + + // try name direct + if (font == NULL) { + if (fs::PathExists(font_path)) { + font = m_env->getFont(font_path.c_str()); + if (font) + verbosestream << "FontEngine: found font: " << font_path << std::endl; + } + } + + if (font != NULL) { + font->grab(); + m_font_cache[mode][basesize] = font; + } +} diff --git a/src/fontengine.h b/src/fontengine.h new file mode 100644 index 0000000..9edde05 --- /dev/null +++ b/src/fontengine.h @@ -0,0 +1,139 @@ +/* +Minetest +Copyright (C) 2010-2014 sapier + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef __FONTENGINE_H__ +#define __FONTENGINE_H__ + +#include +#include +#include "IGUIFont.h" +#include "IGUISkin.h" +#include "IGUIEnvironment.h" +#include "settings.h" + +#define FONT_SIZE_UNSPECIFIED 0xFFFFFFFF + +enum FontMode { + FM_Standard = 0, + FM_Mono, + FM_Fallback, + FM_Simple, + FM_SimpleMono, + FM_MaxMode, + FM_Unspecified +}; + +class FontEngine +{ +public: + + FontEngine(Settings* main_settings, gui::IGUIEnvironment* env); + + ~FontEngine(); + + /** get Font */ + irr::gui::IGUIFont* getFont(unsigned int font_size=FONT_SIZE_UNSPECIFIED, + FontMode mode=FM_Unspecified); + + /** get text height for a specific font */ + unsigned int getTextHeight(unsigned int font_size=FONT_SIZE_UNSPECIFIED, + FontMode mode=FM_Unspecified); + + /** get text width if a text for a specific font */ + unsigned int getTextWidth(const std::string& text, + unsigned int font_size=FONT_SIZE_UNSPECIFIED, + FontMode mode=FM_Unspecified) + { + return getTextWidth(narrow_to_wide(text)); + } + + /** get text width if a text for a specific font */ + unsigned int getTextWidth(const std::wstring& text, + unsigned int font_size=FONT_SIZE_UNSPECIFIED, + FontMode mode=FM_Unspecified); + + /** get line height for a specific font (including empty room between lines) */ + unsigned int getLineHeight(unsigned int font_size=FONT_SIZE_UNSPECIFIED, + FontMode mode=FM_Unspecified); + + /** get default font size */ + unsigned int getDefaultFontSize(); + + /** initialize font engine */ + void initialize(Settings* main_settings, gui::IGUIEnvironment* env); + + /** update internal parameters from settings */ + void readSettings(); + +private: + /** disable copy constructor */ + FontEngine() : + m_settings(NULL), + m_env(NULL), + m_font_cache(), + m_currentMode(FM_Standard), + m_lastMode(), + m_lastSize(0), + m_lastFont(NULL) + {}; + + /** update content of font cache in case of a setting change made it invalid */ + void updateFontCache(); + + /** initialize a new font */ + void initFont(unsigned int basesize, FontMode mode=FM_Unspecified); + + /** initialize a font without freetype */ + void initSimpleFont(unsigned int basesize, FontMode mode); + + /** update current minetest skin with font changes */ + void updateSkin(); + + /** clean cache */ + void cleanCache(); + + /** pointer to settings for registering callbacks or reading config */ + Settings* m_settings; + + /** pointer to irrlicht gui environment */ + gui::IGUIEnvironment* m_env; + + /** internal storage for caching fonts of different size */ + std::map m_font_cache[FM_MaxMode]; + + /** default font size to use */ + unsigned int m_default_size[FM_MaxMode]; + + /** current font engine mode */ + FontMode m_currentMode; + + /** font mode of last request */ + FontMode m_lastMode; + + /** size of last request */ + unsigned int m_lastSize; + + /** last font returned */ + irr::gui::IGUIFont* m_lastFont; + +}; + +/** interface to access main font engine*/ +extern FontEngine* g_fontengine; + +#endif diff --git a/src/game.cpp b/src/game.cpp new file mode 100644 index 0000000..a205db6 --- /dev/null +++ b/src/game.cpp @@ -0,0 +1,4266 @@ +/* +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. +*/ + +#include "game.h" +#include "irrlichttypes_extrabloated.h" +#include +#include +#include +#include +#include +#include +#include "IMeshCache.h" +#include "client.h" +#include "server.h" +#include "guiPasswordChange.h" +#include "guiVolumeChange.h" +#include "guiKeyChangeMenu.h" +#include "guiFormSpecMenu.h" +#include "tool.h" +#include "guiChatConsole.h" +#include "config.h" +#include "version.h" +#include "clouds.h" +#include "particles.h" +#include "camera.h" +#include "mapblock.h" +#include "settings.h" +#include "profiler.h" +#include "mainmenumanager.h" +#include "gettext.h" +#include "log.h" +#include "filesys.h" +// Needed for determining pointing to nodes +#include "nodedef.h" +#include "nodemetadata.h" +#include "main.h" // For g_settings +#include "itemdef.h" +#include "client/tile.h" // For TextureSource +#include "shader.h" // For ShaderSource +#include "logoutputbuffer.h" +#include "subgame.h" +#include "quicktune_shortcutter.h" +#include "clientmap.h" +#include "hud.h" +#include "sky.h" +#include "sound.h" +#if USE_SOUND +#include "sound_openal.h" +#endif +#include "event_manager.h" +#include +#include +#include "util/directiontables.h" +#include "util/pointedthing.h" +#include "drawscene.h" +#include "content_cao.h" +#include "fontengine.h" + +#ifdef HAVE_TOUCHSCREENGUI +#include "touchscreengui.h" +#endif + +/* + Text input system +*/ + +struct TextDestNodeMetadata : public TextDest { + TextDestNodeMetadata(v3s16 p, Client *client) + { + m_p = p; + m_client = client; + } + // This is deprecated I guess? -celeron55 + void gotText(std::wstring text) + { + std::string ntext = wide_to_narrow(text); + infostream << "Submitting 'text' field of node at (" << m_p.X << "," + << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl; + std::map fields; + fields["text"] = ntext; + m_client->sendNodemetaFields(m_p, "", fields); + } + void gotText(std::map fields) + { + m_client->sendNodemetaFields(m_p, "", fields); + } + + v3s16 m_p; + Client *m_client; +}; + +struct TextDestPlayerInventory : public TextDest { + TextDestPlayerInventory(Client *client) + { + m_client = client; + m_formname = ""; + } + TextDestPlayerInventory(Client *client, std::string formname) + { + m_client = client; + m_formname = formname; + } + void gotText(std::map fields) + { + m_client->sendInventoryFields(m_formname, fields); + } + + Client *m_client; +}; + +struct LocalFormspecHandler : public TextDest { + LocalFormspecHandler(); + LocalFormspecHandler(std::string formname) : + m_client(0) + { + m_formname = formname; + } + + LocalFormspecHandler(std::string formname, Client *client) : + m_client(client) + { + m_formname = formname; + } + + void gotText(std::wstring message) + { + errorstream << "LocalFormspecHandler::gotText old style message received" << std::endl; + } + + void gotText(std::map fields) + { + if (m_formname == "MT_PAUSE_MENU") { + if (fields.find("btn_sound") != fields.end()) { + g_gamecallback->changeVolume(); + return; + } + + if (fields.find("btn_key_config") != fields.end()) { + g_gamecallback->keyConfig(); + return; + } + + if (fields.find("btn_exit_menu") != fields.end()) { + g_gamecallback->disconnect(); + return; + } + + if (fields.find("btn_exit_os") != fields.end()) { + g_gamecallback->exitToOS(); + return; + } + + if (fields.find("btn_change_password") != fields.end()) { + g_gamecallback->changePassword(); + return; + } + + if (fields.find("quit") != fields.end()) { + return; + } + + if (fields.find("btn_continue") != fields.end()) { + return; + } + } + + if (m_formname == "MT_CHAT_MENU") { + assert(m_client != 0); + + if ((fields.find("btn_send") != fields.end()) || + (fields.find("quit") != fields.end())) { + if (fields.find("f_text") != fields.end()) { + m_client->typeChatMessage(narrow_to_wide(fields["f_text"])); + } + + return; + } + } + + if (m_formname == "MT_DEATH_SCREEN") { + assert(m_client != 0); + + if ((fields.find("btn_respawn") != fields.end())) { + m_client->sendRespawn(); + return; + } + + if (fields.find("quit") != fields.end()) { + m_client->sendRespawn(); + return; + } + } + + // don't show error message for unhandled cursor keys + if ((fields.find("key_up") != fields.end()) || + (fields.find("key_down") != fields.end()) || + (fields.find("key_left") != fields.end()) || + (fields.find("key_right") != fields.end())) { + return; + } + + errorstream << "LocalFormspecHandler::gotText unhandled >" << m_formname << "< event" << std::endl; + int i = 0; + + for (std::map::iterator iter = fields.begin(); + iter != fields.end(); iter++) { + errorstream << "\t" << i << ": " << iter->first << "=" << iter->second << std::endl; + i++; + } + } + + Client *m_client; +}; + +/* Form update callback */ + +class NodeMetadataFormSource: public IFormSource +{ +public: + NodeMetadataFormSource(ClientMap *map, v3s16 p): + m_map(map), + m_p(p) + { + } + std::string getForm() + { + NodeMetadata *meta = m_map->getNodeMetadata(m_p); + + if (!meta) + return ""; + + return meta->getString("formspec"); + } + std::string resolveText(std::string str) + { + NodeMetadata *meta = m_map->getNodeMetadata(m_p); + + if (!meta) + return str; + + return meta->resolveString(str); + } + + ClientMap *m_map; + v3s16 m_p; +}; + +class PlayerInventoryFormSource: public IFormSource +{ +public: + PlayerInventoryFormSource(Client *client): + m_client(client) + { + } + std::string getForm() + { + LocalPlayer *player = m_client->getEnv().getLocalPlayer(); + return player->inventory_formspec; + } + + Client *m_client; +}; + +/* + Check if a node is pointable +*/ +inline bool isPointableNode(const MapNode &n, + Client *client, bool liquids_pointable) +{ + const ContentFeatures &features = client->getNodeDefManager()->get(n); + return features.pointable || + (liquids_pointable && features.isLiquid()); +} + +/* + Find what the player is pointing at +*/ +PointedThing getPointedThing(Client *client, v3f player_position, + v3f camera_direction, v3f camera_position, core::line3d shootline, + f32 d, bool liquids_pointable, bool look_for_object, v3s16 camera_offset, + std::vector &hilightboxes, ClientActiveObject *&selected_object) +{ + PointedThing result; + + hilightboxes.clear(); + selected_object = NULL; + + INodeDefManager *nodedef = client->getNodeDefManager(); + ClientMap &map = client->getEnv().getClientMap(); + + f32 mindistance = BS * 1001; + + // First try to find a pointed at active object + if (look_for_object) { + selected_object = client->getSelectedActiveObject(d * BS, + camera_position, shootline); + + if (selected_object != NULL) { + if (selected_object->doShowSelectionBox()) { + aabb3f *selection_box = selected_object->getSelectionBox(); + // Box should exist because object was + // returned in the first place + assert(selection_box); + + v3f pos = selected_object->getPosition(); + hilightboxes.push_back(aabb3f( + selection_box->MinEdge + pos - intToFloat(camera_offset, BS), + selection_box->MaxEdge + pos - intToFloat(camera_offset, BS))); + } + + mindistance = (selected_object->getPosition() - camera_position).getLength(); + + result.type = POINTEDTHING_OBJECT; + result.object_id = selected_object->getId(); + } + } + + // That didn't work, try to find a pointed at node + + + v3s16 pos_i = floatToInt(player_position, BS); + + /*infostream<<"pos_i=("< 0 ? a : 1); + s16 zend = pos_i.Z + (camera_direction.Z > 0 ? a : 1); + s16 xend = pos_i.X + (camera_direction.X > 0 ? a : 1); + + // Prevent signed number overflow + if (yend == 32767) + yend = 32766; + + if (zend == 32767) + zend = 32766; + + if (xend == 32767) + xend = 32766; + + for (s16 y = ystart; y <= yend; y++) + for (s16 z = zstart; z <= zend; z++) + for (s16 x = xstart; x <= xend; x++) { + MapNode n; + bool is_valid_position; + + n = map.getNodeNoEx(v3s16(x, y, z), &is_valid_position); + if (!is_valid_position) + continue; + + if (!isPointableNode(n, client, liquids_pointable)) + continue; + + std::vector boxes = n.getSelectionBoxes(nodedef); + + v3s16 np(x, y, z); + v3f npf = intToFloat(np, BS); + + for (std::vector::const_iterator + i = boxes.begin(); + i != boxes.end(); i++) { + aabb3f box = *i; + box.MinEdge += npf; + box.MaxEdge += npf; + + for (u16 j = 0; j < 6; j++) { + v3s16 facedir = g_6dirs[j]; + aabb3f facebox = box; + + f32 d = 0.001 * BS; + + if (facedir.X > 0) + facebox.MinEdge.X = facebox.MaxEdge.X - d; + else if (facedir.X < 0) + facebox.MaxEdge.X = facebox.MinEdge.X + d; + else if (facedir.Y > 0) + facebox.MinEdge.Y = facebox.MaxEdge.Y - d; + else if (facedir.Y < 0) + facebox.MaxEdge.Y = facebox.MinEdge.Y + d; + else if (facedir.Z > 0) + facebox.MinEdge.Z = facebox.MaxEdge.Z - d; + else if (facedir.Z < 0) + facebox.MaxEdge.Z = facebox.MinEdge.Z + d; + + v3f centerpoint = facebox.getCenter(); + f32 distance = (centerpoint - camera_position).getLength(); + + if (distance >= mindistance) + continue; + + if (!facebox.intersectsWithLine(shootline)) + continue; + + v3s16 np_above = np + facedir; + + result.type = POINTEDTHING_NODE; + result.node_undersurface = np; + result.node_abovesurface = np_above; + mindistance = distance; + + hilightboxes.clear(); + + if (!g_settings->getBool("enable_node_highlighting")) { + for (std::vector::const_iterator + i2 = boxes.begin(); + i2 != boxes.end(); i2++) { + aabb3f box = *i2; + box.MinEdge += npf + v3f(-d, -d, -d) - intToFloat(camera_offset, BS); + box.MaxEdge += npf + v3f(d, d, d) - intToFloat(camera_offset, BS); + hilightboxes.push_back(box); + } + } + } + } + } // for coords + + return result; +} + +/* Profiler display */ + +void update_profiler_gui(gui::IGUIStaticText *guitext_profiler, FontEngine *fe, + u32 show_profiler, u32 show_profiler_max, s32 screen_height) +{ + if (show_profiler == 0) { + guitext_profiler->setVisible(false); + } else { + + std::ostringstream os(std::ios_base::binary); + g_profiler->printPage(os, show_profiler, show_profiler_max); + std::wstring text = narrow_to_wide(os.str()); + guitext_profiler->setText(text.c_str()); + guitext_profiler->setVisible(true); + + s32 w = fe->getTextWidth(text.c_str()); + + if (w < 400) + w = 400; + + unsigned text_height = fe->getTextHeight(); + + core::position2di upper_left, lower_right; + + upper_left.X = 6; + upper_left.Y = (text_height + 5) * 2; + lower_right.X = 12 + w; + lower_right.Y = upper_left.Y + (text_height + 1) * MAX_PROFILER_TEXT_ROWS; + + if (lower_right.Y > screen_height * 2 / 3) + lower_right.Y = screen_height * 2 / 3; + + core::rect rect(upper_left, lower_right); + + guitext_profiler->setRelativePosition(rect); + guitext_profiler->setVisible(true); + } +} + +class ProfilerGraph +{ +private: + struct Piece { + Profiler::GraphValues values; + }; + struct Meta { + float min; + float max; + video::SColor color; + Meta(float initial = 0, + video::SColor color = video::SColor(255, 255, 255, 255)): + min(initial), + max(initial), + color(color) + {} + }; + std::vector m_log; +public: + u32 m_log_max_size; + + ProfilerGraph(): + m_log_max_size(200) + {} + + void put(const Profiler::GraphValues &values) + { + Piece piece; + piece.values = values; + m_log.push_back(piece); + + while (m_log.size() > m_log_max_size) + m_log.erase(m_log.begin()); + } + + void draw(s32 x_left, s32 y_bottom, video::IVideoDriver *driver, + gui::IGUIFont *font) const + { + std::map m_meta; + + for (std::vector::const_iterator k = m_log.begin(); + k != m_log.end(); k++) { + const Piece &piece = *k; + + for (Profiler::GraphValues::const_iterator i = piece.values.begin(); + i != piece.values.end(); i++) { + const std::string &id = i->first; + const float &value = i->second; + std::map::iterator j = + m_meta.find(id); + + if (j == m_meta.end()) { + m_meta[id] = Meta(value); + continue; + } + + if (value < j->second.min) + j->second.min = value; + + if (value > j->second.max) + j->second.max = value; + } + } + + // Assign colors + static const video::SColor usable_colors[] = { + video::SColor(255, 255, 100, 100), + video::SColor(255, 90, 225, 90), + video::SColor(255, 100, 100, 255), + video::SColor(255, 255, 150, 50), + video::SColor(255, 220, 220, 100) + }; + static const u32 usable_colors_count = + sizeof(usable_colors) / sizeof(*usable_colors); + u32 next_color_i = 0; + + for (std::map::iterator i = m_meta.begin(); + i != m_meta.end(); i++) { + Meta &meta = i->second; + video::SColor color(255, 200, 200, 200); + + if (next_color_i < usable_colors_count) + color = usable_colors[next_color_i++]; + + meta.color = color; + } + + s32 graphh = 50; + s32 textx = x_left + m_log_max_size + 15; + s32 textx2 = textx + 200 - 15; + + // Draw background + /*{ + u32 num_graphs = m_meta.size(); + core::rect rect(x_left, y_bottom - num_graphs*graphh, + textx2, y_bottom); + video::SColor bgcolor(120,0,0,0); + driver->draw2DRectangle(bgcolor, rect, NULL); + }*/ + + s32 meta_i = 0; + + for (std::map::const_iterator i = m_meta.begin(); + i != m_meta.end(); i++) { + const std::string &id = i->first; + const Meta &meta = i->second; + s32 x = x_left; + s32 y = y_bottom - meta_i * 50; + float show_min = meta.min; + float show_max = meta.max; + + if (show_min >= -0.0001 && show_max >= -0.0001) { + if (show_min <= show_max * 0.5) + show_min = 0; + } + + s32 texth = 15; + char buf[10]; + snprintf(buf, 10, "%.3g", show_max); + font->draw(narrow_to_wide(buf).c_str(), + core::rect(textx, y - graphh, + textx2, y - graphh + texth), + meta.color); + snprintf(buf, 10, "%.3g", show_min); + font->draw(narrow_to_wide(buf).c_str(), + core::rect(textx, y - texth, + textx2, y), + meta.color); + font->draw(narrow_to_wide(id).c_str(), + core::rect(textx, y - graphh / 2 - texth / 2, + textx2, y - graphh / 2 + texth / 2), + meta.color); + s32 graph1y = y; + s32 graph1h = graphh; + bool relativegraph = (show_min != 0 && show_min != show_max); + float lastscaledvalue = 0.0; + bool lastscaledvalue_exists = false; + + for (std::vector::const_iterator j = m_log.begin(); + j != m_log.end(); j++) { + const Piece &piece = *j; + float value = 0; + bool value_exists = false; + Profiler::GraphValues::const_iterator k = + piece.values.find(id); + + if (k != piece.values.end()) { + value = k->second; + value_exists = true; + } + + if (!value_exists) { + x++; + lastscaledvalue_exists = false; + continue; + } + + float scaledvalue = 1.0; + + if (show_max != show_min) + scaledvalue = (value - show_min) / (show_max - show_min); + + if (scaledvalue == 1.0 && value == 0) { + x++; + lastscaledvalue_exists = false; + continue; + } + + if (relativegraph) { + if (lastscaledvalue_exists) { + s32 ivalue1 = lastscaledvalue * graph1h; + s32 ivalue2 = scaledvalue * graph1h; + driver->draw2DLine(v2s32(x - 1, graph1y - ivalue1), + v2s32(x, graph1y - ivalue2), meta.color); + } + + lastscaledvalue = scaledvalue; + lastscaledvalue_exists = true; + } else { + s32 ivalue = scaledvalue * graph1h; + driver->draw2DLine(v2s32(x, graph1y), + v2s32(x, graph1y - ivalue), meta.color); + } + + x++; + } + + meta_i++; + } + } +}; + +class NodeDugEvent: public MtEvent +{ +public: + v3s16 p; + MapNode n; + + NodeDugEvent(v3s16 p, MapNode n): + p(p), + n(n) + {} + const char *getType() const + { + return "NodeDug"; + } +}; + +class SoundMaker +{ + ISoundManager *m_sound; + INodeDefManager *m_ndef; +public: + float m_player_step_timer; + + SimpleSoundSpec m_player_step_sound; + SimpleSoundSpec m_player_leftpunch_sound; + SimpleSoundSpec m_player_rightpunch_sound; + + SoundMaker(ISoundManager *sound, INodeDefManager *ndef): + m_sound(sound), + m_ndef(ndef), + m_player_step_timer(0) + { + } + + void playPlayerStep() + { + if (m_player_step_timer <= 0 && m_player_step_sound.exists()) { + m_player_step_timer = 0.03; + m_sound->playSound(m_player_step_sound, false); + } + } + + static void viewBobbingStep(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->playPlayerStep(); + } + + static void playerRegainGround(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->playPlayerStep(); + } + + static void playerJump(MtEvent *e, void *data) + { + //SoundMaker *sm = (SoundMaker*)data; + } + + static void cameraPunchLeft(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->m_sound->playSound(sm->m_player_leftpunch_sound, false); + } + + static void cameraPunchRight(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->m_sound->playSound(sm->m_player_rightpunch_sound, false); + } + + static void nodeDug(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + NodeDugEvent *nde = (NodeDugEvent *)e; + sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug, false); + } + + static void playerDamage(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false); + } + + static void playerFallingDamage(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false); + } + + void registerReceiver(MtEventManager *mgr) + { + mgr->reg("ViewBobbingStep", SoundMaker::viewBobbingStep, this); + mgr->reg("PlayerRegainGround", SoundMaker::playerRegainGround, this); + mgr->reg("PlayerJump", SoundMaker::playerJump, this); + mgr->reg("CameraPunchLeft", SoundMaker::cameraPunchLeft, this); + mgr->reg("CameraPunchRight", SoundMaker::cameraPunchRight, this); + mgr->reg("NodeDug", SoundMaker::nodeDug, this); + mgr->reg("PlayerDamage", SoundMaker::playerDamage, this); + mgr->reg("PlayerFallingDamage", SoundMaker::playerFallingDamage, this); + } + + void step(float dtime) + { + m_player_step_timer -= dtime; + } +}; + +// Locally stored sounds don't need to be preloaded because of this +class GameOnDemandSoundFetcher: public OnDemandSoundFetcher +{ + std::set m_fetched; +public: + void fetchSounds(const std::string &name, + std::set &dst_paths, + std::set &dst_datas) + { + if (m_fetched.count(name)) + return; + + m_fetched.insert(name); + std::string base = porting::path_share + DIR_DELIM + "testsounds"; + dst_paths.insert(base + DIR_DELIM + name + ".ogg"); + dst_paths.insert(base + DIR_DELIM + name + ".0.ogg"); + dst_paths.insert(base + DIR_DELIM + name + ".1.ogg"); + dst_paths.insert(base + DIR_DELIM + name + ".2.ogg"); + dst_paths.insert(base + DIR_DELIM + name + ".3.ogg"); + dst_paths.insert(base + DIR_DELIM + name + ".4.ogg"); + dst_paths.insert(base + DIR_DELIM + name + ".5.ogg"); + dst_paths.insert(base + DIR_DELIM + name + ".6.ogg"); + dst_paths.insert(base + DIR_DELIM + name + ".7.ogg"); + dst_paths.insert(base + DIR_DELIM + name + ".8.ogg"); + dst_paths.insert(base + DIR_DELIM + name + ".9.ogg"); + } +}; + +class GameGlobalShaderConstantSetter : public IShaderConstantSetter +{ + Sky *m_sky; + bool *m_force_fog_off; + f32 *m_fog_range; + Client *m_client; + bool m_fogEnabled; + +public: + void onSettingsChange(const std::string &name) + { + if (name == "enable_fog") + m_fogEnabled = g_settings->getBool("enable_fog"); + } + + static void SettingsCallback(const std::string name, void *userdata) + { + reinterpret_cast(userdata)->onSettingsChange(name); + } + + GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off, + f32 *fog_range, Client *client) : + m_sky(sky), + m_force_fog_off(force_fog_off), + m_fog_range(fog_range), + m_client(client) + { + g_settings->registerChangedCallback("enable_fog", SettingsCallback, this); + m_fogEnabled = g_settings->getBool("enable_fog"); + } + + ~GameGlobalShaderConstantSetter() + { + g_settings->deregisterChangedCallback("enable_fog", SettingsCallback, this); + } + + virtual void onSetConstants(video::IMaterialRendererServices *services, + bool is_highlevel) + { + if (!is_highlevel) + return; + + // Background color + video::SColor bgcolor = m_sky->getBgColor(); + video::SColorf bgcolorf(bgcolor); + float bgcolorfa[4] = { + bgcolorf.r, + bgcolorf.g, + bgcolorf.b, + bgcolorf.a, + }; + services->setPixelShaderConstant("skyBgColor", bgcolorfa, 4); + + // Fog distance + float fog_distance = 10000 * BS; + + if (m_fogEnabled && !*m_force_fog_off) + fog_distance = *m_fog_range; + + services->setPixelShaderConstant("fogDistance", &fog_distance, 1); + + // Day-night ratio + u32 daynight_ratio = m_client->getEnv().getDayNightRatio(); + float daynight_ratio_f = (float)daynight_ratio / 1000.0; + services->setPixelShaderConstant("dayNightRatio", &daynight_ratio_f, 1); + + u32 animation_timer = porting::getTimeMs() % 100000; + float animation_timer_f = (float)animation_timer / 100000.0; + services->setPixelShaderConstant("animationTimer", &animation_timer_f, 1); + services->setVertexShaderConstant("animationTimer", &animation_timer_f, 1); + + LocalPlayer *player = m_client->getEnv().getLocalPlayer(); + v3f eye_position = player->getEyePosition(); + services->setPixelShaderConstant("eyePosition", (irr::f32 *)&eye_position, 3); + services->setVertexShaderConstant("eyePosition", (irr::f32 *)&eye_position, 3); + + // Uniform sampler layers + int layer0 = 0; + int layer1 = 1; + int layer2 = 2; + // before 1.8 there isn't a "integer interface", only float +#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) + services->setPixelShaderConstant("baseTexture" , (irr::f32 *)&layer0, 1); + services->setPixelShaderConstant("normalTexture" , (irr::f32 *)&layer1, 1); + services->setPixelShaderConstant("useNormalmap" , (irr::f32 *)&layer2, 1); +#else + services->setPixelShaderConstant("baseTexture" , (irr::s32 *)&layer0, 1); + services->setPixelShaderConstant("normalTexture" , (irr::s32 *)&layer1, 1); + services->setPixelShaderConstant("useNormalmap" , (irr::s32 *)&layer2, 1); +#endif + } +}; + +bool nodePlacementPrediction(Client &client, + const ItemDefinition &playeritem_def, v3s16 nodepos, v3s16 neighbourpos) +{ + std::string prediction = playeritem_def.node_placement_prediction; + INodeDefManager *nodedef = client.ndef(); + ClientMap &map = client.getEnv().getClientMap(); + MapNode node; + bool is_valid_position; + + node = map.getNodeNoEx(nodepos, &is_valid_position); + if (!is_valid_position) + return false; + + if (prediction != "" && !nodedef->get(node).rightclickable) { + verbosestream << "Node placement prediction for " + << playeritem_def.name << " is " + << prediction << std::endl; + v3s16 p = neighbourpos; + + // Place inside node itself if buildable_to + MapNode n_under = map.getNodeNoEx(nodepos, &is_valid_position); + if (is_valid_position) + { + if (nodedef->get(n_under).buildable_to) + p = nodepos; + else { + node = map.getNodeNoEx(p, &is_valid_position); + if (is_valid_position &&!nodedef->get(node).buildable_to) + return false; + } + } + + // Find id of predicted node + content_t id; + bool found = nodedef->getId(prediction, id); + + if (!found) { + errorstream << "Node placement prediction failed for " + << playeritem_def.name << " (places " + << prediction + << ") - Name not known" << std::endl; + return false; + } + + // Predict param2 for facedir and wallmounted nodes + u8 param2 = 0; + + if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED) { + v3s16 dir = nodepos - neighbourpos; + + if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) { + param2 = dir.Y < 0 ? 1 : 0; + } else if (abs(dir.X) > abs(dir.Z)) { + param2 = dir.X < 0 ? 3 : 2; + } else { + param2 = dir.Z < 0 ? 5 : 4; + } + } + + if (nodedef->get(id).param_type_2 == CPT2_FACEDIR) { + v3s16 dir = nodepos - floatToInt(client.getEnv().getLocalPlayer()->getPosition(), BS); + + if (abs(dir.X) > abs(dir.Z)) { + param2 = dir.X < 0 ? 3 : 1; + } else { + param2 = dir.Z < 0 ? 2 : 0; + } + } + + assert(param2 <= 5); + + //Check attachment if node is in group attached_node + if (((ItemGroupList) nodedef->get(id).groups)["attached_node"] != 0) { + static v3s16 wallmounted_dirs[8] = { + v3s16(0, 1, 0), + v3s16(0, -1, 0), + v3s16(1, 0, 0), + v3s16(-1, 0, 0), + v3s16(0, 0, 1), + v3s16(0, 0, -1), + }; + v3s16 pp; + + if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED) + pp = p + wallmounted_dirs[param2]; + else + pp = p + v3s16(0, -1, 0); + + if (!nodedef->get(map.getNodeNoEx(pp)).walkable) + return false; + } + + // Add node to client map + MapNode n(id, 0, param2); + + try { + LocalPlayer *player = client.getEnv().getLocalPlayer(); + + // Dont place node when player would be inside new node + // NOTE: This is to be eventually implemented by a mod as client-side Lua + if (!nodedef->get(n).walkable || + g_settings->getBool("enable_build_where_you_stand") || + (client.checkPrivilege("noclip") && g_settings->getBool("noclip")) || + (nodedef->get(n).walkable && + neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) && + neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) { + + // This triggers the required mesh update too + client.addNode(p, n); + return true; + } + } catch (InvalidPositionException &e) { + errorstream << "Node placement prediction failed for " + << playeritem_def.name << " (places " + << prediction + << ") - Position not loaded" << std::endl; + } + } + + return false; +} + +static inline void create_formspec_menu(GUIFormSpecMenu **cur_formspec, + InventoryManager *invmgr, IGameDef *gamedef, + IWritableTextureSource *tsrc, IrrlichtDevice *device, + IFormSource *fs_src, TextDest *txt_dest, Client *client) +{ + + if (*cur_formspec == 0) { + *cur_formspec = new GUIFormSpecMenu(device, guiroot, -1, &g_menumgr, + invmgr, gamedef, tsrc, fs_src, txt_dest, client); + (*cur_formspec)->doPause = false; + + /* + Caution: do not call (*cur_formspec)->drop() here -- + the reference might outlive the menu, so we will + periodically check if *cur_formspec is the only + remaining reference (i.e. the menu was removed) + and delete it in that case. + */ + + } else { + (*cur_formspec)->setFormSource(fs_src); + (*cur_formspec)->setTextDest(txt_dest); + } +} + +#ifdef __ANDROID__ +#define SIZE_TAG "size[11,5.5]" +#else +#define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop +#endif + +static void show_chat_menu(GUIFormSpecMenu **cur_formspec, + InventoryManager *invmgr, IGameDef *gamedef, + IWritableTextureSource *tsrc, IrrlichtDevice *device, + Client *client, std::string text) +{ + std::string formspec = + FORMSPEC_VERSION_STRING + SIZE_TAG + "field[3,2.35;6,0.5;f_text;;" + text + "]" + "button_exit[4,3;3,0.5;btn_send;" + wide_to_narrow(wstrgettext("Proceed")) + "]" + ; + + /* Create menu */ + /* Note: FormspecFormSource and LocalFormspecHandler + * are deleted by guiFormSpecMenu */ + FormspecFormSource *fs_src = new FormspecFormSource(formspec); + LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_CHAT_MENU", client); + + create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, fs_src, txt_dst, NULL); +} + +static void show_deathscreen(GUIFormSpecMenu **cur_formspec, + InventoryManager *invmgr, IGameDef *gamedef, + IWritableTextureSource *tsrc, IrrlichtDevice *device, Client *client) +{ + std::string formspec = + std::string(FORMSPEC_VERSION_STRING) + + SIZE_TAG + "bgcolor[#320000b4;true]" + "label[4.85,1.35;" + gettext("You died.") + "]" + "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]" + ; + + /* Create menu */ + /* Note: FormspecFormSource and LocalFormspecHandler + * are deleted by guiFormSpecMenu */ + FormspecFormSource *fs_src = new FormspecFormSource(formspec); + LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client); + + create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, fs_src, txt_dst, NULL); +} + +/******************************************************************************/ +static void show_pause_menu(GUIFormSpecMenu **cur_formspec, + InventoryManager *invmgr, IGameDef *gamedef, + IWritableTextureSource *tsrc, IrrlichtDevice *device, + bool singleplayermode) +{ +#ifdef __ANDROID__ + std::string control_text = wide_to_narrow(wstrgettext("Default Controls:\n" + "No menu visible:\n" + "- single tap: button activate\n" + "- double tap: place/use\n" + "- slide finger: look around\n" + "Menu/Inventory visible:\n" + "- double tap (outside):\n" + " -->close\n" + "- touch stack, touch slot:\n" + " --> move stack\n" + "- touch&drag, tap 2nd finger\n" + " --> place single item to slot\n" + )); +#else + std::string control_text = wide_to_narrow(wstrgettext("Default Controls:\n" + "- WASD: move\n" + "- E: Run\n" + "- Space: jump/climb\n" + "- Shift: sneak/go down\n" + "- Q: drop item\n" + "- I: inventory\n" + "- Mouse: turn/look\n" + "- Mouse left: dig/punch\n" + "- Mouse right: place/use\n" + "- Mouse wheel: select item\n" + "- T: chat\n" + )); +#endif + + float ypos = singleplayermode ? 0.5 : 0.1; + std::ostringstream os; + + os << FORMSPEC_VERSION_STRING << SIZE_TAG + << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;" + << wide_to_narrow(wstrgettext("Continue")) << "]"; + + if (!singleplayermode) { + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;" + << wide_to_narrow(wstrgettext("Change Password")) << "]"; + } + +#ifndef __ANDROID__ + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;" + << wide_to_narrow(wstrgettext("Sound Volume")) << "]"; + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;" + << wide_to_narrow(wstrgettext("Change Keys")) << "]"; +#endif + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;" + << wide_to_narrow(wstrgettext("Exit to Menu")) << "]"; + os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" + << wide_to_narrow(wstrgettext("Exit to OS")) << "]" + << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]" + << "textarea[0.4,0.25;3.5,6;;" << "Blokel\n" + << "\n;]"; + + /* Create menu */ + /* Note: FormspecFormSource and LocalFormspecHandler * + * are deleted by guiFormSpecMenu */ + FormspecFormSource *fs_src = new FormspecFormSource(os.str()); + LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); + + create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, fs_src, txt_dst, NULL); + (*cur_formspec)->setFocus(L"btn_continue"); + (*cur_formspec)->doPause = true; +} + +/******************************************************************************/ +static void updateChat(Client &client, f32 dtime, bool show_debug, + const v2u32 &screensize, bool show_chat, u32 show_profiler, + ChatBackend &chat_backend, gui::IGUIStaticText *guitext_chat) +{ + // Add chat log output for errors to be shown in chat + static LogOutputBuffer chat_log_error_buf(LMT_ERROR); + + // Get new messages from error log buffer + while (!chat_log_error_buf.empty()) { + chat_backend.addMessage(L"", narrow_to_wide(chat_log_error_buf.get())); + } + + // Get new messages from client + std::wstring message; + + while (client.getChatMessage(message)) { + chat_backend.addUnparsedMessage(message); + } + + // Remove old messages + chat_backend.step(dtime); + + // Display all messages in a static text element + unsigned int recent_chat_count = chat_backend.getRecentBuffer().getLineCount(); + std::wstring recent_chat = chat_backend.getRecentChat(); + unsigned int line_height = g_fontengine->getLineHeight(); + + guitext_chat->setText(recent_chat.c_str()); + + // Update gui element size and position + s32 chat_y = 5 + line_height; + + if (show_debug) + chat_y += line_height; + + // first pass to calculate height of text to be set + s32 width = std::min(g_fontengine->getTextWidth(recent_chat) + 10, + porting::getWindowSize().X - 20); + core::rect rect(10, chat_y, width, chat_y + porting::getWindowSize().Y); + guitext_chat->setRelativePosition(rect); + + //now use real height of text and adjust rect according to this size + rect = core::rect(10, chat_y, width, + chat_y + guitext_chat->getTextHeight()); + + + guitext_chat->setRelativePosition(rect); + // Don't show chat if disabled or empty or profiler is enabled + guitext_chat->setVisible( + show_chat && recent_chat_count != 0 && !show_profiler); +} + + +/**************************************************************************** + Fast key cache for main game loop + ****************************************************************************/ + +/* This is faster than using getKeySetting with the tradeoff that functions + * using it must make sure that it's initialised before using it and there is + * no error handling (for example bounds checking). This is really intended for + * use only in the main running loop of the client (the_game()) where the faster + * (up to 10x faster) key lookup is an asset. Other parts of the codebase + * (e.g. formspecs) should continue using getKeySetting(). + */ +struct KeyCache { + + KeyCache() { populate(); } + + enum { + // Player movement + KEYMAP_ID_FORWARD, + KEYMAP_ID_BACKWARD, + KEYMAP_ID_LEFT, + KEYMAP_ID_RIGHT, + KEYMAP_ID_JUMP, + KEYMAP_ID_SPECIAL1, + KEYMAP_ID_SNEAK, + + // Other + KEYMAP_ID_DROP, + KEYMAP_ID_INVENTORY, + KEYMAP_ID_CHAT, + KEYMAP_ID_CMD, + KEYMAP_ID_CONSOLE, + KEYMAP_ID_FREEMOVE, + KEYMAP_ID_FASTMOVE, + KEYMAP_ID_NOCLIP, + KEYMAP_ID_CINEMATIC, + KEYMAP_ID_SCREENSHOT, + KEYMAP_ID_TOGGLE_HUD, + KEYMAP_ID_TOGGLE_CHAT, + KEYMAP_ID_TOGGLE_FORCE_FOG_OFF, + KEYMAP_ID_TOGGLE_UPDATE_CAMERA, + KEYMAP_ID_TOGGLE_DEBUG, + KEYMAP_ID_TOGGLE_PROFILER, + KEYMAP_ID_CAMERA_MODE, + KEYMAP_ID_INCREASE_VIEWING_RANGE, + KEYMAP_ID_DECREASE_VIEWING_RANGE, + KEYMAP_ID_RANGESELECT, + + KEYMAP_ID_QUICKTUNE_NEXT, + KEYMAP_ID_QUICKTUNE_PREV, + KEYMAP_ID_QUICKTUNE_INC, + KEYMAP_ID_QUICKTUNE_DEC, + + KEYMAP_ID_DEBUG_STACKS, + + // Fake keycode for array size and internal checks + KEYMAP_INTERNAL_ENUM_COUNT + + + }; + + void populate(); + + KeyPress key[KEYMAP_INTERNAL_ENUM_COUNT]; +}; + +void KeyCache::populate() +{ + key[KEYMAP_ID_FORWARD] = getKeySetting("keymap_forward"); + key[KEYMAP_ID_BACKWARD] = getKeySetting("keymap_backward"); + key[KEYMAP_ID_LEFT] = getKeySetting("keymap_left"); + key[KEYMAP_ID_RIGHT] = getKeySetting("keymap_right"); + key[KEYMAP_ID_JUMP] = getKeySetting("keymap_jump"); + key[KEYMAP_ID_SPECIAL1] = getKeySetting("keymap_special1"); + key[KEYMAP_ID_SNEAK] = getKeySetting("keymap_sneak"); + + key[KEYMAP_ID_DROP] = getKeySetting("keymap_drop"); + key[KEYMAP_ID_INVENTORY] = getKeySetting("keymap_inventory"); + key[KEYMAP_ID_CHAT] = getKeySetting("keymap_chat"); + key[KEYMAP_ID_CMD] = getKeySetting("keymap_cmd"); + key[KEYMAP_ID_CONSOLE] = getKeySetting("keymap_console"); + key[KEYMAP_ID_FREEMOVE] = getKeySetting("keymap_freemove"); + key[KEYMAP_ID_FASTMOVE] = getKeySetting("keymap_fastmove"); + key[KEYMAP_ID_NOCLIP] = getKeySetting("keymap_noclip"); + key[KEYMAP_ID_CINEMATIC] = getKeySetting("keymap_cinematic"); + key[KEYMAP_ID_SCREENSHOT] = getKeySetting("keymap_screenshot"); + key[KEYMAP_ID_TOGGLE_HUD] = getKeySetting("keymap_toggle_hud"); + key[KEYMAP_ID_TOGGLE_CHAT] = getKeySetting("keymap_toggle_chat"); + key[KEYMAP_ID_TOGGLE_FORCE_FOG_OFF] + = getKeySetting("keymap_toggle_force_fog_off"); + key[KEYMAP_ID_TOGGLE_UPDATE_CAMERA] + = getKeySetting("keymap_toggle_update_camera"); + key[KEYMAP_ID_TOGGLE_DEBUG] + = getKeySetting("keymap_toggle_debug"); + key[KEYMAP_ID_TOGGLE_PROFILER] + = getKeySetting("keymap_toggle_profiler"); + key[KEYMAP_ID_CAMERA_MODE] + = getKeySetting("keymap_camera_mode"); + key[KEYMAP_ID_INCREASE_VIEWING_RANGE] + = getKeySetting("keymap_increase_viewing_range_min"); + key[KEYMAP_ID_DECREASE_VIEWING_RANGE] + = getKeySetting("keymap_decrease_viewing_range_min"); + key[KEYMAP_ID_RANGESELECT] + = getKeySetting("keymap_rangeselect"); + + key[KEYMAP_ID_QUICKTUNE_NEXT] = getKeySetting("keymap_quicktune_next"); + key[KEYMAP_ID_QUICKTUNE_PREV] = getKeySetting("keymap_quicktune_prev"); + key[KEYMAP_ID_QUICKTUNE_INC] = getKeySetting("keymap_quicktune_inc"); + key[KEYMAP_ID_QUICKTUNE_DEC] = getKeySetting("keymap_quicktune_dec"); + + key[KEYMAP_ID_DEBUG_STACKS] = getKeySetting("keymap_print_debug_stacks"); +} + + +/**************************************************************************** + + ****************************************************************************/ + +const float object_hit_delay = 0.2; + +struct FpsControl { + u32 last_time, busy_time, sleep_time; +}; + + +/* The reason the following structs are not anonymous structs within the + * class is that they are not used by the majority of member functions and + * many functions that do require objects of thse types do not modify them + * (so they can be passed as a const qualified parameter) + */ +struct CameraOrientation { + f32 camera_yaw; // "right/left" + f32 camera_pitch; // "up/down" +}; + +struct GameRunData { + u16 dig_index; + u16 new_playeritem; + PointedThing pointed_old; + bool digging; + bool ldown_for_dig; + bool left_punch; + bool update_wielded_item_trigger; + bool reset_jump_timer; + float nodig_delay_timer; + float dig_time; + float dig_time_complete; + float repeat_rightclick_timer; + float object_hit_delay_timer; + float time_from_last_punch; + ClientActiveObject *selected_object; + + float jump_timer; + float damage_flash; + float update_draw_list_timer; + float statustext_time; + + f32 fog_range; + + v3f update_draw_list_last_cam_dir; + + u32 profiler_current_page; + u32 profiler_max_page; // Number of pages + + float time_of_day; + float time_of_day_smooth; +}; + +struct Jitter { + f32 max, min, avg, counter, max_sample, min_sample, max_fraction; +}; + +struct RunStats { + u32 drawtime; + u32 beginscenetime; + u32 endscenetime; + + Jitter dtime_jitter, busy_time_jitter; +}; + +/* Flags that can, or may, change during main game loop + */ +struct VolatileRunFlags { + bool invert_mouse; + bool show_chat; + bool show_hud; + bool force_fog_off; + bool show_debug; + bool show_profiler_graph; + bool disable_camera_update; + bool first_loop_after_window_activation; + bool camera_offset_changed; +}; + + +/**************************************************************************** + THE GAME + ****************************************************************************/ + +/* This is not intended to be a public class. If a public class becomes + * desirable then it may be better to create another 'wrapper' class that + * hides most of the stuff in this class (nothing in this class is required + * by any other file) but exposes the public methods/data only. + */ +class Game +{ +public: + Game(); + ~Game(); + + bool startup(bool *kill, + bool random_input, + InputHandler *input, + IrrlichtDevice *device, + const std::string &map_dir, + const std::string &playername, + const std::string &password, + // If address is "", local server is used and address is updated + std::string *address, + u16 port, + std::wstring *error_message, + ChatBackend *chat_backend, + const SubgameSpec &gamespec, // Used for local game + bool simple_singleplayer_mode); + + void run(); + void shutdown(); + +protected: + + void extendedResourceCleanup(); + + // Basic initialisation + bool init(const std::string &map_dir, std::string *address, + u16 port, + const SubgameSpec &gamespec); + bool initSound(); + bool createSingleplayerServer(const std::string map_dir, + const SubgameSpec &gamespec, u16 port, std::string *address); + + // Client creation + bool createClient(const std::string &playername, + const std::string &password, std::string *address, u16 port, + std::wstring *error_message); + bool initGui(std::wstring *error_message); + + // Client connection + bool connectToServer(const std::string &playername, + const std::string &password, std::string *address, u16 port, + bool *connect_ok, bool *aborted); + bool getServerContent(bool *aborted); + + // Main loop + + void updateInteractTimers(GameRunData *args, f32 dtime); + bool checkConnection(); + bool handleCallbacks(); + void processQueues(); + void updateProfilers(const GameRunData &run_data, const RunStats &stats, + const FpsControl &draw_times, f32 dtime); + void addProfilerGraphs(const RunStats &stats, const FpsControl &draw_times, + f32 dtime); + void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime); + + void processUserInput(VolatileRunFlags *flags, GameRunData *interact_args, + f32 dtime); + void processKeyboardInput(VolatileRunFlags *flags, + float *statustext_time, + float *jump_timer, + bool *reset_jump_timer, + u32 *profiler_current_page, + u32 profiler_max_page); + void processItemSelection(u16 *new_playeritem); + + void dropSelectedItem(); + void openInventory(); + void openConsole(); + void toggleFreeMove(float *statustext_time); + void toggleFreeMoveAlt(float *statustext_time, float *jump_timer); + void toggleFast(float *statustext_time); + void toggleNoClip(float *statustext_time); + void toggleCinematic(float *statustext_time); + + void toggleChat(float *statustext_time, bool *flag); + void toggleHud(float *statustext_time, bool *flag); + void toggleFog(float *statustext_time, bool *flag); + void toggleDebug(float *statustext_time, bool *show_debug, + bool *show_profiler_graph); + void toggleUpdateCamera(float *statustext_time, bool *flag); + void toggleProfiler(float *statustext_time, u32 *profiler_current_page, + u32 profiler_max_page); + + void increaseViewRange(float *statustext_time); + void decreaseViewRange(float *statustext_time); + void toggleFullViewRange(float *statustext_time); + + void updateCameraDirection(CameraOrientation *cam, VolatileRunFlags *flags); + void updateCameraOrientation(CameraOrientation *cam, + const VolatileRunFlags &flags); + void updatePlayerControl(const CameraOrientation &cam); + void step(f32 *dtime); + void processClientEvents(CameraOrientation *cam, float *damage_flash); + void updateCamera(VolatileRunFlags *flags, u32 busy_time, f32 dtime, + float time_from_last_punch); + void updateSound(f32 dtime); + void processPlayerInteraction(std::vector &highlight_boxes, + GameRunData *runData, f32 dtime, bool show_hud, + bool show_debug); + void handlePointingAtNode(GameRunData *runData, + const PointedThing &pointed, const ItemDefinition &playeritem_def, + const ToolCapabilities &playeritem_toolcap, f32 dtime); + void handlePointingAtObject(GameRunData *runData, + const PointedThing &pointed, const ItemStack &playeritem, + const v3f &player_position, bool show_debug); + void handleDigging(GameRunData *runData, const PointedThing &pointed, + const v3s16 &nodepos, const ToolCapabilities &playeritem_toolcap, + f32 dtime); + void updateFrame(std::vector &highlight_boxes, ProfilerGraph *graph, + RunStats *stats, GameRunData *runData, + f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam); + void updateGui(float *statustext_time, const RunStats &stats, + const GameRunData& runData, f32 dtime, const VolatileRunFlags &flags, + const CameraOrientation &cam); + void updateProfilerGraphs(ProfilerGraph *graph); + + // Misc + void limitFps(FpsControl *fps_timings, f32 *dtime); + + void showOverlayMessage(const wchar_t *msg, float dtime, int percent, + bool draw_clouds = true); + +private: + InputHandler *input; + + Client *client; + Server *server; + + IWritableTextureSource *texture_src; + IWritableShaderSource *shader_src; + + // When created, these will be filled with data received from the server + IWritableItemDefManager *itemdef_manager; + IWritableNodeDefManager *nodedef_manager; + + GameOnDemandSoundFetcher soundfetcher; // useful when testing + ISoundManager *sound; + bool sound_is_dummy; + SoundMaker *soundmaker; + + ChatBackend *chat_backend; + + GUIFormSpecMenu *current_formspec; + + EventManager *eventmgr; + QuicktuneShortcutter *quicktune; + + GUIChatConsole *gui_chat_console; // Free using ->Drop() + MapDrawControl *draw_control; + Camera *camera; + Clouds *clouds; // Free using ->Drop() + Sky *sky; // Free using ->Drop() + Inventory *local_inventory; + Hud *hud; + + /* 'cache' + This class does take ownership/responsibily for cleaning up etc of any of + these items (e.g. device) + */ + IrrlichtDevice *device; + video::IVideoDriver *driver; + scene::ISceneManager *smgr; + bool *kill; + std::wstring *error_message; + IGameDef *gamedef; // Convenience (same as *client) + scene::ISceneNode *skybox; + + bool random_input; + bool simple_singleplayer_mode; + /* End 'cache' */ + + /* Pre-calculated values + */ + int crack_animation_length; + + /* GUI stuff + */ + gui::IGUIStaticText *guitext; // First line of debug text + gui::IGUIStaticText *guitext2; // Second line of debug text + gui::IGUIStaticText *guitext_info; // At the middle of the screen + gui::IGUIStaticText *guitext_status; + gui::IGUIStaticText *guitext_chat; // Chat text + gui::IGUIStaticText *guitext_profiler; // Profiler text + + std::wstring infotext; + std::wstring statustext; + + KeyCache keycache; + + IntervalLimiter profiler_interval; + + /* TODO: Add a callback function so these can be updated when a setting + * changes. At this point in time it doesn't matter (e.g. /set + * is documented to change server settings only) + * + * TODO: Local caching of settings is not optimal and should at some stage + * be updated to use a global settings object for getting thse values + * (as opposed to the this local caching). This can be addressed in + * a later release. + */ + bool m_cache_doubletap_jump; + bool m_cache_enable_node_highlighting; + bool m_cache_enable_clouds; + bool m_cache_enable_particles; + bool m_cache_enable_fog; + f32 m_cache_mouse_sensitivity; + f32 m_repeat_right_click_time; +}; + +Game::Game() : + client(NULL), + server(NULL), + texture_src(NULL), + shader_src(NULL), + itemdef_manager(NULL), + nodedef_manager(NULL), + sound(NULL), + sound_is_dummy(false), + soundmaker(NULL), + chat_backend(NULL), + current_formspec(NULL), + eventmgr(NULL), + quicktune(NULL), + gui_chat_console(NULL), + draw_control(NULL), + camera(NULL), + clouds(NULL), + sky(NULL), + local_inventory(NULL), + hud(NULL) +{ + m_cache_doubletap_jump = g_settings->getBool("doubletap_jump"); + m_cache_enable_node_highlighting = g_settings->getBool("enable_node_highlighting"); + m_cache_enable_clouds = g_settings->getBool("enable_clouds"); + m_cache_enable_particles = g_settings->getBool("enable_particles"); + m_cache_enable_fog = g_settings->getBool("enable_fog"); + m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity"); + m_repeat_right_click_time = g_settings->getFloat("repeat_rightclick_time"); + + m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0); +} + + + +/**************************************************************************** + MinetestApp Public + ****************************************************************************/ + +Game::~Game() +{ + delete client; + delete soundmaker; + if (!sound_is_dummy) + delete sound; + + delete server; // deleted first to stop all server threads + + delete hud; + delete local_inventory; + delete camera; + delete quicktune; + delete eventmgr; + delete texture_src; + delete shader_src; + delete nodedef_manager; + delete itemdef_manager; + delete draw_control; + + extendedResourceCleanup(); +} + +bool Game::startup(bool *kill, + bool random_input, + InputHandler *input, + IrrlichtDevice *device, + const std::string &map_dir, + const std::string &playername, + const std::string &password, + std::string *address, // can change if simple_singleplayer_mode + u16 port, + std::wstring *error_message, + ChatBackend *chat_backend, + const SubgameSpec &gamespec, + bool simple_singleplayer_mode) +{ + // "cache" + this->device = device; + this->kill = kill; + this->error_message = error_message; + this->random_input = random_input; + this->input = input; + this->chat_backend = chat_backend; + this->simple_singleplayer_mode = simple_singleplayer_mode; + + driver = device->getVideoDriver(); + smgr = device->getSceneManager(); + + smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true); + + if (!init(map_dir, address, port, gamespec)) + return false; + + if (!createClient(playername, password, address, port, error_message)) + return false; + + return true; +} + + +void Game::run() +{ + ProfilerGraph graph; + RunStats stats = { 0 }; + CameraOrientation cam_view_target = { 0 }; + CameraOrientation cam_view = { 0 }; + GameRunData runData = { 0 }; + FpsControl draw_times = { 0 }; + VolatileRunFlags flags = { 0 }; + f32 dtime; // in seconds + + runData.time_from_last_punch = 10.0; + runData.profiler_max_page = 3; + runData.update_wielded_item_trigger = true; + + flags.show_chat = true; + flags.show_hud = true; + flags.show_debug = g_settings->getBool("show_debug"); + flags.invert_mouse = g_settings->getBool("invert_mouse"); + flags.first_loop_after_window_activation = true; + + /* Clear the profiler */ + Profiler::GraphValues dummyvalues; + g_profiler->graphGet(dummyvalues); + + draw_times.last_time = device->getTimer()->getTime(); + + shader_src->addGlobalConstantSetter(new GameGlobalShaderConstantSetter( + sky, + &flags.force_fog_off, + &runData.fog_range, + client)); + + std::vector highlight_boxes; + + set_light_table(g_settings->getFloat("display_gamma")); + + while (device->run() && !(*kill || g_gamecallback->shutdown_requested)) { + + /* Must be called immediately after a device->run() call because it + * uses device->getTimer()->getTime() + */ + limitFps(&draw_times, &dtime); + + updateStats(&stats, draw_times, dtime); + updateInteractTimers(&runData, dtime); + + if (!checkConnection()) + break; + if (!handleCallbacks()) + break; + + processQueues(); + + infotext = L""; + hud->resizeHotbar(); + + updateProfilers(runData, stats, draw_times, dtime); + processUserInput(&flags, &runData, dtime); + // Update camera before player movement to avoid camera lag of one frame + updateCameraDirection(&cam_view_target, &flags); + float cam_smoothing = 0; + if (g_settings->getBool("cinematic")) + cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing"); + else + cam_smoothing = 1 - g_settings->getFloat("camera_smoothing"); + cam_smoothing = rangelim(cam_smoothing, 0.01f, 1.0f); + cam_view.camera_yaw += (cam_view_target.camera_yaw - + cam_view.camera_yaw) * cam_smoothing; + cam_view.camera_pitch += (cam_view_target.camera_pitch - + cam_view.camera_pitch) * cam_smoothing; + updatePlayerControl(cam_view); + step(&dtime); + processClientEvents(&cam_view, &runData.damage_flash); + updateCamera(&flags, draw_times.busy_time, dtime, + runData.time_from_last_punch); + updateSound(dtime); + processPlayerInteraction(highlight_boxes, &runData, dtime, + flags.show_hud, flags.show_debug); + updateFrame(highlight_boxes, &graph, &stats, &runData, dtime, + flags, cam_view); + updateProfilerGraphs(&graph); + } +} + + +void Game::shutdown() +{ + showOverlayMessage(wgettext("Shutting down..."), 0, 0, false); + + if (clouds) + clouds->drop(); + + if (gui_chat_console) + gui_chat_console->drop(); + + if (sky) + sky->drop(); + + /* cleanup menus */ + while (g_menumgr.menuCount() > 0) { + g_menumgr.m_stack.front()->setVisible(false); + g_menumgr.deletingMenu(g_menumgr.m_stack.front()); + } + + if (current_formspec) { + current_formspec->drop(); + current_formspec = NULL; + } + + chat_backend->addMessage(L"", L"# Disconnected."); + chat_backend->addMessage(L"", L""); + + if (client) { + client->Stop(); + while (!client->isShutdown()) { + assert(texture_src != NULL); + assert(shader_src != NULL); + texture_src->processQueue(); + shader_src->processQueue(); + sleep_ms(100); + } + } +} + + + +/**************************************************************************** + Startup + ****************************************************************************/ + +bool Game::init( + const std::string &map_dir, + std::string *address, + u16 port, + const SubgameSpec &gamespec) +{ + showOverlayMessage(wgettext("Loading..."), 0, 0); + + texture_src = createTextureSource(device); + shader_src = createShaderSource(device); + + itemdef_manager = createItemDefManager(); + nodedef_manager = createNodeDefManager(); + + eventmgr = new EventManager(); + quicktune = new QuicktuneShortcutter(); + + if (!(texture_src && shader_src && itemdef_manager && nodedef_manager + && eventmgr && quicktune)) + return false; + + if (!initSound()) + return false; + + // Create a server if not connecting to an existing one + if (*address == "") { + if (!createSingleplayerServer(map_dir, gamespec, port, address)) + return false; + } + + return true; +} + +bool Game::initSound() +{ +#if USE_SOUND + if (g_settings->getBool("enable_sound")) { + infostream << "Attempting to use OpenAL audio" << std::endl; + sound = createOpenALSoundManager(&soundfetcher); + if (!sound) + infostream << "Failed to initialize OpenAL audio" << std::endl; + } else + infostream << "Sound disabled." << std::endl; +#endif + + if (!sound) { + infostream << "Using dummy audio." << std::endl; + sound = &dummySoundManager; + sound_is_dummy = true; + } + + soundmaker = new SoundMaker(sound, nodedef_manager); + if (!soundmaker) + return false; + + soundmaker->registerReceiver(eventmgr); + + return true; +} + +bool Game::createSingleplayerServer(const std::string map_dir, + const SubgameSpec &gamespec, u16 port, std::string *address) +{ + showOverlayMessage(wgettext("Creating server..."), 0, 5); + + std::string bind_str = g_settings->get("bind_address"); + Address bind_addr(0, 0, 0, 0, port); + + if (g_settings->getBool("ipv6_server")) { + bind_addr.setAddress((IPv6AddressBytes *) NULL); + } + + try { + bind_addr.Resolve(bind_str.c_str()); + } catch (ResolveError &e) { + infostream << "Resolving bind address \"" << bind_str + << "\" failed: " << e.what() + << " -- Listening on all addresses." << std::endl; + } + + if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) { + *error_message = L"Unable to listen on " + + narrow_to_wide(bind_addr.serializeString()) + + L" because IPv6 is disabled"; + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + server = new Server(map_dir, gamespec, simple_singleplayer_mode, + bind_addr.isIPv6()); + + server->start(bind_addr); + + return true; +} + +bool Game::createClient(const std::string &playername, + const std::string &password, std::string *address, u16 port, + std::wstring *error_message) +{ + showOverlayMessage(wgettext("Creating client..."), 0, 10); + + draw_control = new MapDrawControl; + if (!draw_control) + return false; + + bool could_connect, connect_aborted; + + if (!connectToServer(playername, password, address, port, + &could_connect, &connect_aborted)) + return false; + + if (!could_connect) { + if (*error_message == L"" && !connect_aborted) { + // Should not happen if error messages are set properly + *error_message = L"Connection failed for unknown reason"; + errorstream << wide_to_narrow(*error_message) << std::endl; + } + return false; + } + + if (!getServerContent(&connect_aborted)) { + if (*error_message == L"" && !connect_aborted) { + // Should not happen if error messages are set properly + *error_message = L"Connection failed for unknown reason"; + errorstream << wide_to_narrow(*error_message) << std::endl; + } + return false; + } + + // Update cached textures, meshes and materials + client->afterContentReceived(device, g_fontengine->getFont()); + + /* Camera + */ + camera = new Camera(smgr, *draw_control, gamedef); + if (!camera || !camera->successfullyCreated(*error_message)) + return false; + + /* Clouds + */ + if (m_cache_enable_clouds) { + clouds = new Clouds(smgr->getRootSceneNode(), smgr, -1, time(0)); + if (!clouds) { + *error_message = L"Memory allocation error"; + *error_message += narrow_to_wide(" (clouds)"); + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + } + + /* Skybox + */ + sky = new Sky(smgr->getRootSceneNode(), smgr, -1, texture_src); + skybox = NULL; // This is used/set later on in the main run loop + + local_inventory = new Inventory(itemdef_manager); + + if (!(sky && local_inventory)) { + *error_message = L"Memory allocation error"; + *error_message += narrow_to_wide(" (sky or local inventory)"); + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + /* Pre-calculated values + */ + video::ITexture *t = texture_src->getTexture("crack_anylength.png"); + if (t) { + v2u32 size = t->getOriginalSize(); + crack_animation_length = size.Y / size.X; + } else { + crack_animation_length = 5; + } + + if (!initGui(error_message)) + return false; + + /* Set window caption + */ + core::stringw str = L"Blokel ["; + str += driver->getName(); + str += "]"; + device->setWindowCaption(str.c_str()); + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + player->hurt_tilt_timer = 0; + player->hurt_tilt_strength = 0; + + hud = new Hud(driver, smgr, guienv, gamedef, player, local_inventory); + + if (!hud) { + *error_message = L"Memory error: could not create HUD"; + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + return true; +} + +bool Game::initGui(std::wstring *error_message) +{ + // First line of debug text + guitext = guienv->addStaticText( + L"Blokel", + core::rect(0, 0, 0, 0), + false, false, guiroot); + + // Second line of debug text + guitext2 = guienv->addStaticText( + L"", + core::rect(0, 0, 0, 0), + false, false, guiroot); + + // At the middle of the screen + // Object infos are shown in this + guitext_info = guienv->addStaticText( + L"", + core::rect(0, 0, 400, g_fontengine->getTextHeight() * 5 + 5) + v2s32(100, 200), + false, true, guiroot); + + // Status text (displays info when showing and hiding GUI stuff, etc.) + guitext_status = guienv->addStaticText( + L"", + core::rect(0, 0, 0, 0), + false, false, guiroot); + guitext_status->setVisible(false); + + // Chat text + guitext_chat = guienv->addStaticText( + L"", + core::rect(0, 0, 0, 0), + //false, false); // Disable word wrap as of now + false, true, guiroot); + // Remove stale "recent" chat messages from previous connections + chat_backend->clearRecentChat(); + + // Chat backend and console + gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), + -1, chat_backend, client); + if (!gui_chat_console) { + *error_message = L"Could not allocate memory for chat console"; + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + // Profiler text (size is updated when text is updated) + guitext_profiler = guienv->addStaticText( + L"", + core::rect(0, 0, 0, 0), + false, false, guiroot); + guitext_profiler->setBackgroundColor(video::SColor(120, 0, 0, 0)); + guitext_profiler->setVisible(false); + guitext_profiler->setWordWrap(true); + +#ifdef HAVE_TOUCHSCREENGUI + + if (g_touchscreengui) + g_touchscreengui->init(texture_src, porting::getDisplayDensity()); + +#endif + + return true; +} + +bool Game::connectToServer(const std::string &playername, + const std::string &password, std::string *address, u16 port, + bool *connect_ok, bool *aborted) +{ + *connect_ok = false; // Let's not be overly optimistic + *aborted = false; + bool local_server_mode = false; + + showOverlayMessage(wgettext("Resolving address..."), 0, 15); + + Address connect_address(0, 0, 0, 0, port); + + try { + connect_address.Resolve(address->c_str()); + + if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY + //connect_address.Resolve("localhost"); + if (connect_address.isIPv6()) { + IPv6AddressBytes addr_bytes; + addr_bytes.bytes[15] = 1; + connect_address.setAddress(&addr_bytes); + } else { + connect_address.setAddress(127, 0, 0, 1); + } + local_server_mode = true; + } + } catch (ResolveError &e) { + *error_message = L"Couldn't resolve address: " + narrow_to_wide(e.what()); + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) { + *error_message = L"Unable to connect to " + + narrow_to_wide(connect_address.serializeString()) + + L" because IPv6 is disabled"; + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + client = new Client(device, + playername.c_str(), password, + *draw_control, texture_src, shader_src, + itemdef_manager, nodedef_manager, sound, eventmgr, + connect_address.isIPv6()); + + if (!client) + return false; + + gamedef = client; // Client acts as our GameDef + + infostream << "Connecting to server at "; + connect_address.print(&infostream); + infostream << std::endl; + + client->connect(connect_address, *address, + simple_singleplayer_mode || local_server_mode); + + /* + Wait for server to accept connection + */ + + try { + input->clear(); + + FpsControl fps_control = { 0 }; + f32 dtime; // in seconds + + while (device->run()) { + + limitFps(&fps_control, &dtime); + + // Update client and server + client->step(dtime); + + if (server != NULL) + server->step(dtime); + + // End condition + if (client->getState() == LC_Init) { + *connect_ok = true; + break; + } + + // Break conditions + if (client->accessDenied()) { + *error_message = L"Access denied. Reason: " + + client->accessDeniedReason(); + errorstream << wide_to_narrow(*error_message) << std::endl; + break; + } + + if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { + *aborted = true; + infostream << "Connect aborted [Escape]" << std::endl; + break; + } + + // Update status + showOverlayMessage(wgettext("Connecting to server..."), dtime, 20); + } + } catch (con::PeerNotFoundException &e) { + // TODO: Should something be done here? At least an info/error + // message? + return false; + } + + return true; +} + +bool Game::getServerContent(bool *aborted) +{ + input->clear(); + + FpsControl fps_control = { 0 }; + f32 dtime; // in seconds + + while (device->run()) { + + limitFps(&fps_control, &dtime); + + // Update client and server + client->step(dtime); + + if (server != NULL) + server->step(dtime); + + // End condition + if (client->mediaReceived() && client->itemdefReceived() && + client->nodedefReceived()) { + break; + } + + // Error conditions + if (client->accessDenied()) { + *error_message = L"Access denied. Reason: " + + client->accessDeniedReason(); + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + if (client->getState() < LC_Init) { + *error_message = L"Client disconnected"; + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { + *aborted = true; + infostream << "Connect aborted [Escape]" << std::endl; + return false; + } + + // Display status + int progress = 25; + + if (!client->itemdefReceived()) { + const wchar_t *text = wgettext("Item definitions..."); + progress = 25; + draw_load_screen(text, device, guienv, dtime, progress); + delete[] text; + } else if (!client->nodedefReceived()) { + const wchar_t *text = wgettext("Node definitions..."); + progress = 30; + draw_load_screen(text, device, guienv, dtime, progress); + delete[] text; + } else { + std::stringstream message; + message.precision(3); + message << gettext("Media..."); + + if ((USE_CURL == 0) || + (!g_settings->getBool("enable_remote_media_server"))) { + float cur = client->getCurRate(); + std::string cur_unit = gettext(" KB/s"); + + if (cur > 900) { + cur /= 1024.0; + cur_unit = gettext(" MB/s"); + } + + message << " ( " << cur << cur_unit << " )"; + } + + progress = 30 + client->mediaReceiveProgress() * 35 + 0.5; + draw_load_screen(narrow_to_wide(message.str()), device, + guienv, dtime, progress); + } + } + + return true; +} + + + +/**************************************************************************** + Run + ****************************************************************************/ + +inline void Game::updateInteractTimers(GameRunData *args, f32 dtime) +{ + if (args->nodig_delay_timer >= 0) + args->nodig_delay_timer -= dtime; + + if (args->object_hit_delay_timer >= 0) + args->object_hit_delay_timer -= dtime; + + args->time_from_last_punch += dtime; +} + + +/* returns false if game should exit, otherwise true + */ +inline bool Game::checkConnection() +{ + if (client->accessDenied()) { + *error_message = L"Access denied. Reason: " + + client->accessDeniedReason(); + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + return true; +} + + +/* returns false if game should exit, otherwise true + */ +inline bool Game::handleCallbacks() +{ + if (g_gamecallback->disconnect_requested) { + g_gamecallback->disconnect_requested = false; + return false; + } + + if (g_gamecallback->changepassword_requested) { + (new GUIPasswordChange(guienv, guiroot, -1, + &g_menumgr, client))->drop(); + g_gamecallback->changepassword_requested = false; + } + + if (g_gamecallback->changevolume_requested) { + (new GUIVolumeChange(guienv, guiroot, -1, + &g_menumgr, client))->drop(); + g_gamecallback->changevolume_requested = false; + } + + if (g_gamecallback->keyconfig_requested) { + (new GUIKeyChangeMenu(guienv, guiroot, -1, + &g_menumgr))->drop(); + g_gamecallback->keyconfig_requested = false; + } + + if (g_gamecallback->keyconfig_changed) { + keycache.populate(); // update the cache with new settings + g_gamecallback->keyconfig_changed = false; + } + + return true; +} + + +void Game::processQueues() +{ + texture_src->processQueue(); + itemdef_manager->processQueue(gamedef); + shader_src->processQueue(); +} + + +void Game::updateProfilers(const GameRunData &run_data, const RunStats &stats, + const FpsControl &draw_times, f32 dtime) +{ + float profiler_print_interval = + g_settings->getFloat("profiler_print_interval"); + bool print_to_log = true; + + if (profiler_print_interval == 0) { + print_to_log = false; + profiler_print_interval = 5; + } + + if (profiler_interval.step(dtime, profiler_print_interval)) { + if (print_to_log) { + infostream << "Profiler:" << std::endl; + g_profiler->print(infostream); + } + + update_profiler_gui(guitext_profiler, g_fontengine, + run_data.profiler_current_page, run_data.profiler_max_page, + driver->getScreenSize().Height); + + g_profiler->clear(); + } + + addProfilerGraphs(stats, draw_times, dtime); +} + + +void Game::addProfilerGraphs(const RunStats &stats, + const FpsControl &draw_times, f32 dtime) +{ + g_profiler->graphAdd("mainloop_other", + draw_times.busy_time / 1000.0f - stats.drawtime / 1000.0f); + + if (draw_times.sleep_time != 0) + g_profiler->graphAdd("mainloop_sleep", draw_times.sleep_time / 1000.0f); + g_profiler->graphAdd("mainloop_dtime", dtime); + + g_profiler->add("Elapsed time", dtime); + g_profiler->avg("FPS", 1. / dtime); +} + + +void Game::updateStats(RunStats *stats, const FpsControl &draw_times, + f32 dtime) +{ + + f32 jitter; + Jitter *jp; + + /* Time average and jitter calculation + */ + jp = &stats->dtime_jitter; + jp->avg = jp->avg * 0.96 + dtime * 0.04; + + jitter = dtime - jp->avg; + + if (jitter > jp->max) + jp->max = jitter; + + jp->counter += dtime; + + if (jp->counter > 0.0) { + jp->counter -= 3.0; + jp->max_sample = jp->max; + jp->max_fraction = jp->max_sample / (jp->avg + 0.001); + jp->max = 0.0; + } + + /* Busytime average and jitter calculation + */ + jp = &stats->busy_time_jitter; + jp->avg = jp->avg + draw_times.busy_time * 0.02; + + jitter = draw_times.busy_time - jp->avg; + + if (jitter > jp->max) + jp->max = jitter; + if (jitter < jp->min) + jp->min = jitter; + + jp->counter += dtime; + + if (jp->counter > 0.0) { + jp->counter -= 3.0; + jp->max_sample = jp->max; + jp->min_sample = jp->min; + jp->max = 0.0; + jp->min = 0.0; + } +} + + + +/**************************************************************************** + Input handling + ****************************************************************************/ + +void Game::processUserInput(VolatileRunFlags *flags, + GameRunData *interact_args, f32 dtime) +{ + // Reset input if window not active or some menu is active + if (device->isWindowActive() == false + || noMenuActive() == false + || guienv->hasFocus(gui_chat_console)) { + input->clear(); + } + + if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) { + gui_chat_console->closeConsoleAtOnce(); + } + + // Input handler step() (used by the random input generator) + input->step(dtime); + +#ifdef HAVE_TOUCHSCREENGUI + + if (g_touchscreengui) { + g_touchscreengui->step(dtime); + } + +#endif +#ifdef __ANDROID__ + + if (current_formspec != 0) + current_formspec->getAndroidUIInput(); + +#endif + + // Increase timer for double tap of "keymap_jump" + if (m_cache_doubletap_jump && interact_args->jump_timer <= 0.2) + interact_args->jump_timer += dtime; + + processKeyboardInput( + flags, + &interact_args->statustext_time, + &interact_args->jump_timer, + &interact_args->reset_jump_timer, + &interact_args->profiler_current_page, + interact_args->profiler_max_page); + + processItemSelection(&interact_args->new_playeritem); +} + + +void Game::processKeyboardInput(VolatileRunFlags *flags, + float *statustext_time, + float *jump_timer, + bool *reset_jump_timer, + u32 *profiler_current_page, + u32 profiler_max_page) +{ + + //TimeTaker tt("process kybd input", NULL, PRECISION_NANO); + + if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_DROP])) { + dropSelectedItem(); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_INVENTORY])) { + openInventory(); + } else if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { + show_pause_menu(¤t_formspec, client, gamedef, texture_src, device, + simple_singleplayer_mode); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CHAT])) { + show_chat_menu(¤t_formspec, client, gamedef, texture_src, device, + client, ""); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CMD])) { + show_chat_menu(¤t_formspec, client, gamedef, texture_src, device, + client, "/"); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CONSOLE])) { + openConsole(); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_FREEMOVE])) { + toggleFreeMove(statustext_time); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP])) { + toggleFreeMoveAlt(statustext_time, jump_timer); + *reset_jump_timer = true; + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_FASTMOVE])) { + toggleFast(statustext_time); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_NOCLIP])) { + toggleNoClip(statustext_time); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CINEMATIC])) { + toggleCinematic(statustext_time); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_SCREENSHOT])) { + client->makeScreenshot(device); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_HUD])) { + toggleHud(statustext_time, &flags->show_hud); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_CHAT])) { + toggleChat(statustext_time, &flags->show_chat); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_FORCE_FOG_OFF])) { + toggleFog(statustext_time, &flags->force_fog_off); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_UPDATE_CAMERA])) { + toggleUpdateCamera(statustext_time, &flags->disable_camera_update); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_DEBUG])) { + toggleDebug(statustext_time, &flags->show_debug, &flags->show_profiler_graph); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_PROFILER])) { + toggleProfiler(statustext_time, profiler_current_page, profiler_max_page); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_INCREASE_VIEWING_RANGE])) { + increaseViewRange(statustext_time); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_DECREASE_VIEWING_RANGE])) { + decreaseViewRange(statustext_time); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_RANGESELECT])) { + toggleFullViewRange(statustext_time); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_NEXT])) + quicktune->next(); + else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_PREV])) + quicktune->prev(); + else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_INC])) + quicktune->inc(); + else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_DEC])) + quicktune->dec(); + else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_DEBUG_STACKS])) { + // Print debug stacks + dstream << "-----------------------------------------" + << std::endl; + dstream << DTIME << "Printing debug stacks:" << std::endl; + dstream << "-----------------------------------------" + << std::endl; + debug_stacks_print(); + } + + if (!input->isKeyDown(getKeySetting("keymap_jump")) && *reset_jump_timer) { + *reset_jump_timer = false; + *jump_timer = 0.0; + } + + //tt.stop(); + + if (quicktune->hasMessage()) { + std::string msg = quicktune->getMessage(); + statustext = narrow_to_wide(msg); + *statustext_time = 0; + } +} + + +void Game::processItemSelection(u16 *new_playeritem) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + /* Item selection using mouse wheel + */ + *new_playeritem = client->getPlayerItem(); + + s32 wheel = input->getMouseWheel(); + u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1, + player->hud_hotbar_itemcount - 1); + + if (wheel < 0) + *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0; + else if (wheel > 0) + *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item; + // else wheel == 0 + + + /* Item selection using keyboard + */ + for (u16 i = 0; i < 10; i++) { + static const KeyPress *item_keys[10] = { + NumberKey + 1, NumberKey + 2, NumberKey + 3, NumberKey + 4, + NumberKey + 5, NumberKey + 6, NumberKey + 7, NumberKey + 8, + NumberKey + 9, NumberKey + 0, + }; + + if (input->wasKeyDown(*item_keys[i])) { + if (i < PLAYER_INVENTORY_SIZE && i < player->hud_hotbar_itemcount) { + *new_playeritem = i; + infostream << "Selected item: " << new_playeritem << std::endl; + } + break; + } + } +} + + +void Game::dropSelectedItem() +{ + IDropAction *a = new IDropAction(); + a->count = 0; + a->from_inv.setCurrentPlayer(); + a->from_list = "main"; + a->from_i = client->getPlayerItem(); + client->inventoryAction(a); +} + + +void Game::openInventory() +{ + /* + * Don't permit to open inventory is CAO or player doesn't exists. + * This prevent showing an empty inventory at player load + */ + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + if (player == NULL || player->getCAO() == NULL) + return; + + infostream << "the_game: " << "Launching inventory" << std::endl; + + PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client); + TextDest *txt_dst = new TextDestPlayerInventory(client); + + create_formspec_menu(¤t_formspec, client, gamedef, texture_src, + device, fs_src, txt_dst, client); + + InventoryLocation inventoryloc; + inventoryloc.setCurrentPlayer(); + current_formspec->setFormSpec(fs_src->getForm(), inventoryloc); +} + + +void Game::openConsole() +{ + if (!gui_chat_console->isOpenInhibited()) { + // Open up to over half of the screen + gui_chat_console->openConsole(0.6); + guienv->setFocus(gui_chat_console); + } +} + + +void Game::toggleFreeMove(float *statustext_time) +{ + static const wchar_t *msg[] = { L"free_move disabled", L"free_move enabled" }; + + bool free_move = !g_settings->getBool("free_move"); + g_settings->set("free_move", bool_to_cstr(free_move)); + + *statustext_time = 0; + statustext = msg[free_move]; + if (free_move && !client->checkPrivilege("fly")) + statustext += L" (note: no 'fly' privilege)"; +} + + +void Game::toggleFreeMoveAlt(float *statustext_time, float *jump_timer) +{ + if (m_cache_doubletap_jump && *jump_timer < 0.2f) + toggleFreeMove(statustext_time); +} + + +void Game::toggleFast(float *statustext_time) +{ + static const wchar_t *msg[] = { L"fast_move disabled", L"fast_move enabled" }; + bool fast_move = !g_settings->getBool("fast_move"); + g_settings->set("fast_move", bool_to_cstr(fast_move)); + + *statustext_time = 0; + statustext = msg[fast_move]; + + if (fast_move && !client->checkPrivilege("fast")) + statustext += L" (note: no 'fast' privilege)"; +} + + +void Game::toggleNoClip(float *statustext_time) +{ + static const wchar_t *msg[] = { L"noclip disabled", L"noclip enabled" }; + bool noclip = !g_settings->getBool("noclip"); + g_settings->set("noclip", bool_to_cstr(noclip)); + + *statustext_time = 0; + statustext = msg[noclip]; + + if (noclip && !client->checkPrivilege("noclip")) + statustext += L" (note: no 'noclip' privilege)"; +} + +void Game::toggleCinematic(float *statustext_time) +{ + static const wchar_t *msg[] = { L"cinematic disabled", L"cinematic enabled" }; + bool cinematic = !g_settings->getBool("cinematic"); + g_settings->set("cinematic", bool_to_cstr(cinematic)); + + *statustext_time = 0; + statustext = msg[cinematic]; +} + + +void Game::toggleChat(float *statustext_time, bool *flag) +{ + static const wchar_t *msg[] = { L"Chat hidden", L"Chat shown" }; + + *flag = !*flag; + *statustext_time = 0; + statustext = msg[*flag]; +} + + +void Game::toggleHud(float *statustext_time, bool *flag) +{ + static const wchar_t *msg[] = { L"HUD hidden", L"HUD shown" }; + + *flag = !*flag; + *statustext_time = 0; + statustext = msg[*flag]; + if (g_settings->getBool("enable_node_highlighting")) + client->setHighlighted(client->getHighlighted(), *flag); +} + + +void Game::toggleFog(float *statustext_time, bool *flag) +{ + static const wchar_t *msg[] = { L"Fog enabled", L"Fog disabled" }; + + *flag = !*flag; + *statustext_time = 0; + statustext = msg[*flag]; +} + + +void Game::toggleDebug(float *statustext_time, bool *show_debug, + bool *show_profiler_graph) +{ + // Initial / 3x toggle: Chat only + // 1x toggle: Debug text with chat + // 2x toggle: Debug text with profiler graph + if (!*show_debug) { + *show_debug = true; + *show_profiler_graph = false; + statustext = L"Debug info shown"; + } else if (*show_profiler_graph) { + *show_debug = false; + *show_profiler_graph = false; + statustext = L"Debug info and profiler graph hidden"; + } else { + *show_profiler_graph = true; + statustext = L"Profiler graph shown"; + } + *statustext_time = 0; +} + + +void Game::toggleUpdateCamera(float *statustext_time, bool *flag) +{ + static const wchar_t *msg[] = { + L"Camera update enabled", + L"Camera update disabled" + }; + + *flag = !*flag; + *statustext_time = 0; + statustext = msg[*flag]; +} + + +void Game::toggleProfiler(float *statustext_time, u32 *profiler_current_page, + u32 profiler_max_page) +{ + *profiler_current_page = (*profiler_current_page + 1) % (profiler_max_page + 1); + + // FIXME: This updates the profiler with incomplete values + update_profiler_gui(guitext_profiler, g_fontengine, *profiler_current_page, + profiler_max_page, driver->getScreenSize().Height); + + if (*profiler_current_page != 0) { + std::wstringstream sstr; + sstr << "Profiler shown (page " << *profiler_current_page + << " of " << profiler_max_page << ")"; + statustext = sstr.str(); + } else { + statustext = L"Profiler hidden"; + } + *statustext_time = 0; +} + + +void Game::increaseViewRange(float *statustext_time) +{ + s16 range = g_settings->getS16("viewing_range_nodes_min"); + s16 range_new = range + 10; + g_settings->set("viewing_range_nodes_min", itos(range_new)); + statustext = narrow_to_wide("Minimum viewing range changed to " + + itos(range_new)); + *statustext_time = 0; +} + + +void Game::decreaseViewRange(float *statustext_time) +{ + s16 range = g_settings->getS16("viewing_range_nodes_min"); + s16 range_new = range - 10; + + if (range_new < 0) + range_new = range; + + g_settings->set("viewing_range_nodes_min", itos(range_new)); + statustext = narrow_to_wide("Minimum viewing range changed to " + + itos(range_new)); + *statustext_time = 0; +} + + +void Game::toggleFullViewRange(float *statustext_time) +{ + static const wchar_t *msg[] = { + L"Disabled full viewing range", + L"Enabled full viewing range" + }; + + draw_control->range_all = !draw_control->range_all; + infostream << msg[draw_control->range_all] << std::endl; + statustext = msg[draw_control->range_all]; + *statustext_time = 0; +} + + +void Game::updateCameraDirection(CameraOrientation *cam, + VolatileRunFlags *flags) +{ + if ((device->isWindowActive() && noMenuActive()) || random_input) { + +#ifndef __ANDROID__ + if (!random_input) { + // Mac OSX gets upset if this is set every frame + if (device->getCursorControl()->isVisible()) + device->getCursorControl()->setVisible(false); + } +#endif + + if (flags->first_loop_after_window_activation) + flags->first_loop_after_window_activation = false; + else + updateCameraOrientation(cam, *flags); + + input->setMousePos((driver->getScreenSize().Width / 2), + (driver->getScreenSize().Height / 2)); + } else { + +#ifndef ANDROID + // Mac OSX gets upset if this is set every frame + if (device->getCursorControl()->isVisible() == false) + device->getCursorControl()->setVisible(true); +#endif + + if (!flags->first_loop_after_window_activation) + flags->first_loop_after_window_activation = true; + + } +} + + +void Game::updateCameraOrientation(CameraOrientation *cam, + const VolatileRunFlags &flags) +{ +#ifdef HAVE_TOUCHSCREENGUI + if (g_touchscreengui) { + cam->camera_yaw = g_touchscreengui->getYaw(); + cam->camera_pitch = g_touchscreengui->getPitch(); + } else { +#endif + s32 dx = input->getMousePos().X - (driver->getScreenSize().Width / 2); + s32 dy = input->getMousePos().Y - (driver->getScreenSize().Height / 2); + + if (flags.invert_mouse + || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) { + dy = -dy; + } + + cam->camera_yaw -= dx * m_cache_mouse_sensitivity; + cam->camera_pitch += dy * m_cache_mouse_sensitivity; + +#ifdef HAVE_TOUCHSCREENGUI + } +#endif + + cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5); +} + + +void Game::updatePlayerControl(const CameraOrientation &cam) +{ + //TimeTaker tt("update player control", NULL, PRECISION_NANO); + + PlayerControl control( + input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_FORWARD]), + input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_BACKWARD]), + input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_LEFT]), + input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_RIGHT]), + input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP]), + input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SPECIAL1]), + input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK]), + input->getLeftState(), + input->getRightState(), + cam.camera_pitch, + cam.camera_yaw + ); + client->setPlayerControl(control); + LocalPlayer *player = client->getEnv().getLocalPlayer(); + player->keyPressed = + ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_FORWARD]) & 0x1) << 0) | + ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_BACKWARD]) & 0x1) << 1) | + ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_LEFT]) & 0x1) << 2) | + ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_RIGHT]) & 0x1) << 3) | + ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP]) & 0x1) << 4) | + ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SPECIAL1]) & 0x1) << 5) | + ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK]) & 0x1) << 6) | + ( (u32)(input->getLeftState() & 0x1) << 7) | + ( (u32)(input->getRightState() & 0x1) << 8 + ); + + //tt.stop(); +} + + +inline void Game::step(f32 *dtime) +{ + bool can_be_and_is_paused = + (simple_singleplayer_mode && g_menumgr.pausesGame()); + + if (can_be_and_is_paused) { // This is for a singleplayer server + *dtime = 0; // No time passes + } else { + if (server != NULL) { + //TimeTaker timer("server->step(dtime)"); + server->step(*dtime); + } + + //TimeTaker timer("client.step(dtime)"); + client->step(*dtime); + } +} + + +void Game::processClientEvents(CameraOrientation *cam, float *damage_flash) +{ + ClientEvent event = client->getClientEvent(); + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + for ( ; event.type != CE_NONE; event = client->getClientEvent()) { + + if (event.type == CE_PLAYER_DAMAGE && + client->getHP() != 0) { + //u16 damage = event.player_damage.amount; + //infostream<<"Player damage: "<hurt_tilt_timer = 1.5; + player->hurt_tilt_strength = event.player_damage.amount / 4; + player->hurt_tilt_strength = rangelim(player->hurt_tilt_strength, 1.0, 4.0); + + MtEvent *e = new SimpleTriggerEvent("PlayerDamage"); + gamedef->event()->put(e); + } else if (event.type == CE_PLAYER_FORCE_MOVE) { + cam->camera_yaw = event.player_force_move.yaw; + cam->camera_pitch = event.player_force_move.pitch; + } else if (event.type == CE_DEATHSCREEN) { + show_deathscreen(¤t_formspec, client, gamedef, texture_src, + device, client); + + chat_backend->addMessage(L"", L"You died."); + + /* Handle visualization */ + *damage_flash = 0; + player->hurt_tilt_timer = 0; + player->hurt_tilt_strength = 0; + + } else if (event.type == CE_SHOW_FORMSPEC) { + FormspecFormSource *fs_src = + new FormspecFormSource(*(event.show_formspec.formspec)); + TextDestPlayerInventory *txt_dst = + new TextDestPlayerInventory(client, *(event.show_formspec.formname)); + + create_formspec_menu(¤t_formspec, client, gamedef, + texture_src, device, fs_src, txt_dst, client); + + delete(event.show_formspec.formspec); + delete(event.show_formspec.formname); + } else if ((event.type == CE_SPAWN_PARTICLE) || + (event.type == CE_ADD_PARTICLESPAWNER) || + (event.type == CE_DELETE_PARTICLESPAWNER)) { + client->getParticleManager()->handleParticleEvent(&event, gamedef, + smgr, player); + } else if (event.type == CE_HUDADD) { + u32 id = event.hudadd.id; + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + HudElement *e = player->getHud(id); + + if (e != NULL) { + delete event.hudadd.pos; + delete event.hudadd.name; + delete event.hudadd.scale; + delete event.hudadd.text; + delete event.hudadd.align; + delete event.hudadd.offset; + delete event.hudadd.world_pos; + delete event.hudadd.size; + continue; + } + + e = new HudElement; + e->type = (HudElementType)event.hudadd.type; + e->pos = *event.hudadd.pos; + e->name = *event.hudadd.name; + e->scale = *event.hudadd.scale; + e->text = *event.hudadd.text; + e->number = event.hudadd.number; + e->item = event.hudadd.item; + e->dir = event.hudadd.dir; + e->align = *event.hudadd.align; + e->offset = *event.hudadd.offset; + e->world_pos = *event.hudadd.world_pos; + e->size = *event.hudadd.size; + + u32 new_id = player->addHud(e); + //if this isn't true our huds aren't consistent + sanity_check(new_id == id); + + delete event.hudadd.pos; + delete event.hudadd.name; + delete event.hudadd.scale; + delete event.hudadd.text; + delete event.hudadd.align; + delete event.hudadd.offset; + delete event.hudadd.world_pos; + delete event.hudadd.size; + } else if (event.type == CE_HUDRM) { + HudElement *e = player->removeHud(event.hudrm.id); + + if (e != NULL) + delete(e); + } else if (event.type == CE_HUDCHANGE) { + u32 id = event.hudchange.id; + HudElement *e = player->getHud(id); + + if (e == NULL) { + delete event.hudchange.v3fdata; + delete event.hudchange.v2fdata; + delete event.hudchange.sdata; + delete event.hudchange.v2s32data; + continue; + } + + switch (event.hudchange.stat) { + case HUD_STAT_POS: + e->pos = *event.hudchange.v2fdata; + break; + + case HUD_STAT_NAME: + e->name = *event.hudchange.sdata; + break; + + case HUD_STAT_SCALE: + e->scale = *event.hudchange.v2fdata; + break; + + case HUD_STAT_TEXT: + e->text = *event.hudchange.sdata; + break; + + case HUD_STAT_NUMBER: + e->number = event.hudchange.data; + break; + + case HUD_STAT_ITEM: + e->item = event.hudchange.data; + break; + + case HUD_STAT_DIR: + e->dir = event.hudchange.data; + break; + + case HUD_STAT_ALIGN: + e->align = *event.hudchange.v2fdata; + break; + + case HUD_STAT_OFFSET: + e->offset = *event.hudchange.v2fdata; + break; + + case HUD_STAT_WORLD_POS: + e->world_pos = *event.hudchange.v3fdata; + break; + + case HUD_STAT_SIZE: + e->size = *event.hudchange.v2s32data; + break; + } + + delete event.hudchange.v3fdata; + delete event.hudchange.v2fdata; + delete event.hudchange.sdata; + delete event.hudchange.v2s32data; + } else if (event.type == CE_SET_SKY) { + sky->setVisible(false); + + if (skybox) { + skybox->remove(); + skybox = NULL; + } + + // Handle according to type + if (*event.set_sky.type == "regular") { + sky->setVisible(true); + } else if (*event.set_sky.type == "skybox" && + event.set_sky.params->size() == 6) { + sky->setFallbackBgColor(*event.set_sky.bgcolor); + skybox = smgr->addSkyBoxSceneNode( + texture_src->getTexture((*event.set_sky.params)[0]), + texture_src->getTexture((*event.set_sky.params)[1]), + texture_src->getTexture((*event.set_sky.params)[2]), + texture_src->getTexture((*event.set_sky.params)[3]), + texture_src->getTexture((*event.set_sky.params)[4]), + texture_src->getTexture((*event.set_sky.params)[5])); + } + // Handle everything else as plain color + else { + if (*event.set_sky.type != "plain") + infostream << "Unknown sky type: " + << (*event.set_sky.type) << std::endl; + + sky->setFallbackBgColor(*event.set_sky.bgcolor); + } + + delete event.set_sky.bgcolor; + delete event.set_sky.type; + delete event.set_sky.params; + } else if (event.type == CE_OVERRIDE_DAY_NIGHT_RATIO) { + bool enable = event.override_day_night_ratio.do_override; + u32 value = event.override_day_night_ratio.ratio_f * 1000; + client->getEnv().setDayNightRatioOverride(enable, value); + } + } +} + + +void Game::updateCamera(VolatileRunFlags *flags, u32 busy_time, + f32 dtime, float time_from_last_punch) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + /* + For interaction purposes, get info about the held item + - What item is it? + - Is it a usable item? + - Can it point to liquids? + */ + ItemStack playeritem; + { + InventoryList *mlist = local_inventory->getList("main"); + + if (mlist && client->getPlayerItem() < mlist->getSize()) + playeritem = mlist->getItem(client->getPlayerItem()); + } + + ToolCapabilities playeritem_toolcap = + playeritem.getToolCapabilities(itemdef_manager); + + v3s16 old_camera_offset = camera->getOffset(); + + if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CAMERA_MODE])) { + GenericCAO *playercao = player->getCAO(); + + // If playercao not loaded, don't change camera + if (playercao == NULL) + return; + + camera->toggleCameraMode(); + + playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); + } + + float full_punch_interval = playeritem_toolcap.full_punch_interval; + float tool_reload_ratio = time_from_last_punch / full_punch_interval; + + tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0); + camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio, + client->getEnv()); + camera->step(dtime); + + v3f camera_position = camera->getPosition(); + v3f camera_direction = camera->getDirection(); + f32 camera_fov = camera->getFovMax(); + v3s16 camera_offset = camera->getOffset(); + + flags->camera_offset_changed = (camera_offset != old_camera_offset); + + if (!flags->disable_camera_update) { + client->getEnv().getClientMap().updateCamera(camera_position, + camera_direction, camera_fov, camera_offset); + + if (flags->camera_offset_changed) { + client->updateCameraOffset(camera_offset); + client->getEnv().updateCameraOffset(camera_offset); + + if (clouds) + clouds->updateCameraOffset(camera_offset); + } + } +} + + +void Game::updateSound(f32 dtime) +{ + // Update sound listener + v3s16 camera_offset = camera->getOffset(); + sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS), + v3f(0, 0, 0), // velocity + camera->getDirection(), + camera->getCameraNode()->getUpVector()); + sound->setListenerGain(g_settings->getFloat("sound_volume")); + + + // Update sound maker + soundmaker->step(dtime); + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + ClientMap &map = client->getEnv().getClientMap(); + MapNode n = map.getNodeNoEx(player->getStandingNodePos()); + soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep; +} + + +void Game::processPlayerInteraction(std::vector &highlight_boxes, + GameRunData *runData, f32 dtime, bool show_hud, bool show_debug) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + ItemStack playeritem; + { + InventoryList *mlist = local_inventory->getList("main"); + + if (mlist && client->getPlayerItem() < mlist->getSize()) + playeritem = mlist->getItem(client->getPlayerItem()); + } + + const ItemDefinition &playeritem_def = + playeritem.getDefinition(itemdef_manager); + + v3f player_position = player->getPosition(); + v3f camera_position = camera->getPosition(); + v3f camera_direction = camera->getDirection(); + v3s16 camera_offset = camera->getOffset(); + + + /* + Calculate what block is the crosshair pointing to + */ + + f32 d = playeritem_def.range; // max. distance + f32 d_hand = itemdef_manager->get("").range; + + if (d < 0 && d_hand >= 0) + d = d_hand; + else if (d < 0) + d = 4.0; + + core::line3d shootline; + + if (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT) { + + shootline = core::line3d(camera_position, + camera_position + camera_direction * BS * (d + 1)); + + } else { + // prevent player pointing anything in front-view + if (camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) + shootline = core::line3d(0, 0, 0, 0, 0, 0); + } + +#ifdef HAVE_TOUCHSCREENGUI + + if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) { + shootline = g_touchscreengui->getShootline(); + shootline.start += intToFloat(camera_offset, BS); + shootline.end += intToFloat(camera_offset, BS); + } + +#endif + + PointedThing pointed = getPointedThing( + // input + client, player_position, camera_direction, + camera_position, shootline, d, + playeritem_def.liquids_pointable, + !runData->ldown_for_dig, + camera_offset, + // output + highlight_boxes, + runData->selected_object); + + if (pointed != runData->pointed_old) { + infostream << "Pointing at " << pointed.dump() << std::endl; + + if (m_cache_enable_node_highlighting) { + if (pointed.type == POINTEDTHING_NODE) { + client->setHighlighted(pointed.node_undersurface, show_hud); + } else { + client->setHighlighted(pointed.node_undersurface, false); + } + } + } + + /* + Stop digging when + - releasing left mouse button + - pointing away from node + */ + if (runData->digging) { + if (input->getLeftReleased()) { + infostream << "Left button released" + << " (stopped digging)" << std::endl; + runData->digging = false; + } else if (pointed != runData->pointed_old) { + if (pointed.type == POINTEDTHING_NODE + && runData->pointed_old.type == POINTEDTHING_NODE + && pointed.node_undersurface + == runData->pointed_old.node_undersurface) { + // Still pointing to the same node, but a different face. + // Don't reset. + } else { + infostream << "Pointing away from node" + << " (stopped digging)" << std::endl; + runData->digging = false; + } + } + + if (!runData->digging) { + client->interact(1, runData->pointed_old); + client->setCrack(-1, v3s16(0, 0, 0)); + runData->dig_time = 0.0; + } + } + + if (!runData->digging && runData->ldown_for_dig && !input->getLeftState()) { + runData->ldown_for_dig = false; + } + + runData->left_punch = false; + + soundmaker->m_player_leftpunch_sound.name = ""; + + if (input->getRightState()) + runData->repeat_rightclick_timer += dtime; + else + runData->repeat_rightclick_timer = 0; + + if (playeritem_def.usable && input->getLeftState()) { + if (input->getLeftClicked()) + client->interact(4, pointed); + } else if (pointed.type == POINTEDTHING_NODE) { + ToolCapabilities playeritem_toolcap = + playeritem.getToolCapabilities(itemdef_manager); + handlePointingAtNode(runData, pointed, playeritem_def, + playeritem_toolcap, dtime); + } else if (pointed.type == POINTEDTHING_OBJECT) { + handlePointingAtObject(runData, pointed, playeritem, + player_position, show_debug); + } else if (input->getLeftState()) { + // When button is held down in air, show continuous animation + runData->left_punch = true; + } + + runData->pointed_old = pointed; + + if (runData->left_punch || input->getLeftClicked()) + camera->setDigging(0); // left click animation + + input->resetLeftClicked(); + input->resetRightClicked(); + + input->resetLeftReleased(); + input->resetRightReleased(); +} + + +void Game::handlePointingAtNode(GameRunData *runData, + const PointedThing &pointed, const ItemDefinition &playeritem_def, + const ToolCapabilities &playeritem_toolcap, f32 dtime) +{ + v3s16 nodepos = pointed.node_undersurface; + v3s16 neighbourpos = pointed.node_abovesurface; + + /* + Check information text of node + */ + + ClientMap &map = client->getEnv().getClientMap(); + NodeMetadata *meta = map.getNodeMetadata(nodepos); + + if (meta) { + infotext = narrow_to_wide(meta->getString("infotext")); + } else { + MapNode n = map.getNodeNoEx(nodepos); + + if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") { + infotext = L"Unknown node: "; + infotext += narrow_to_wide(nodedef_manager->get(n).name); + } + } + + if (runData->nodig_delay_timer <= 0.0 && input->getLeftState() + && client->checkPrivilege("interact")) { + handleDigging(runData, pointed, nodepos, playeritem_toolcap, dtime); + } + + if ((input->getRightClicked() || + runData->repeat_rightclick_timer >= m_repeat_right_click_time) && + client->checkPrivilege("interact")) { + runData->repeat_rightclick_timer = 0; + infostream << "Ground right-clicked" << std::endl; + + if (meta && meta->getString("formspec") != "" && !random_input + && !input->isKeyDown(getKeySetting("keymap_sneak"))) { + infostream << "Launching custom inventory view" << std::endl; + + InventoryLocation inventoryloc; + inventoryloc.setNodeMeta(nodepos); + + NodeMetadataFormSource *fs_src = new NodeMetadataFormSource( + &client->getEnv().getClientMap(), nodepos); + TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client); + + create_formspec_menu(¤t_formspec, client, gamedef, + texture_src, device, fs_src, txt_dst, client); + + current_formspec->setFormSpec(meta->getString("formspec"), inventoryloc); + } else { + // Report right click to server + + camera->setDigging(1); // right click animation (always shown for feedback) + + // If the wielded item has node placement prediction, + // make that happen + bool placed = nodePlacementPrediction(*client, + playeritem_def, + nodepos, neighbourpos); + + if (placed) { + // Report to server + client->interact(3, pointed); + // Read the sound + soundmaker->m_player_rightpunch_sound = + playeritem_def.sound_place; + } else { + soundmaker->m_player_rightpunch_sound = + SimpleSoundSpec(); + } + + if (playeritem_def.node_placement_prediction == "" || + nodedef_manager->get(map.getNodeNoEx(nodepos)).rightclickable) + client->interact(3, pointed); // Report to server + } + } +} + + +void Game::handlePointingAtObject(GameRunData *runData, + const PointedThing &pointed, + const ItemStack &playeritem, + const v3f &player_position, + bool show_debug) +{ + infotext = narrow_to_wide(runData->selected_object->infoText()); + + if (infotext == L"" && show_debug) { + infotext = narrow_to_wide(runData->selected_object->debugInfoText()); + } + + if (input->getLeftState()) { + bool do_punch = false; + bool do_punch_damage = false; + + if (runData->object_hit_delay_timer <= 0.0) { + do_punch = true; + do_punch_damage = true; + runData->object_hit_delay_timer = object_hit_delay; + } + + if (input->getLeftClicked()) + do_punch = true; + + if (do_punch) { + infostream << "Left-clicked object" << std::endl; + runData->left_punch = true; + } + + if (do_punch_damage) { + // Report direct punch + v3f objpos = runData->selected_object->getPosition(); + v3f dir = (objpos - player_position).normalize(); + + bool disable_send = runData->selected_object->directReportPunch( + dir, &playeritem, runData->time_from_last_punch); + runData->time_from_last_punch = 0; + + if (!disable_send) + client->interact(0, pointed); + } + } else if (input->getRightClicked()) { + infostream << "Right-clicked object" << std::endl; + client->interact(3, pointed); // place + } +} + + +void Game::handleDigging(GameRunData *runData, + const PointedThing &pointed, const v3s16 &nodepos, + const ToolCapabilities &playeritem_toolcap, f32 dtime) +{ + if (!runData->digging) { + infostream << "Started digging" << std::endl; + client->interact(0, pointed); + runData->digging = true; + runData->ldown_for_dig = true; + } + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + ClientMap &map = client->getEnv().getClientMap(); + MapNode n = client->getEnv().getClientMap().getNodeNoEx(nodepos); + + // NOTE: Similar piece of code exists on the server side for + // cheat detection. + // Get digging parameters + DigParams params = getDigParams(nodedef_manager->get(n).groups, + &playeritem_toolcap); + + // If can't dig, try hand + if (!params.diggable) { + const ItemDefinition &hand = itemdef_manager->get(""); + const ToolCapabilities *tp = hand.tool_capabilities; + + if (tp) + params = getDigParams(nodedef_manager->get(n).groups, tp); + } + + if (params.diggable == false) { + // I guess nobody will wait for this long + runData->dig_time_complete = 10000000.0; + } else { + runData->dig_time_complete = params.time; + + if (m_cache_enable_particles) { + const ContentFeatures &features = + client->getNodeDefManager()->get(n); + client->getParticleManager()->addPunchingParticles(gamedef, smgr, + player, nodepos, features.tiles); + } + } + + if (runData->dig_time_complete >= 0.001) { + runData->dig_index = (float)crack_animation_length + * runData->dig_time + / runData->dig_time_complete; + } else { + // This is for torches + runData->dig_index = crack_animation_length; + } + + SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig; + + if (sound_dig.exists() && params.diggable) { + if (sound_dig.name == "__group") { + if (params.main_group != "") { + soundmaker->m_player_leftpunch_sound.gain = 0.5; + soundmaker->m_player_leftpunch_sound.name = + std::string("default_dig_") + + params.main_group; + } + } else { + soundmaker->m_player_leftpunch_sound = sound_dig; + } + } + + // Don't show cracks if not diggable + if (runData->dig_time_complete >= 100000.0) { + } else if (runData->dig_index < crack_animation_length) { + //TimeTaker timer("client.setTempMod"); + //infostream<<"dig_index="<setCrack(runData->dig_index, nodepos); + } else { + infostream << "Digging completed" << std::endl; + client->interact(2, pointed); + client->setCrack(-1, v3s16(0, 0, 0)); + bool is_valid_position; + MapNode wasnode = map.getNodeNoEx(nodepos, &is_valid_position); + if (is_valid_position) + client->removeNode(nodepos); + + if (m_cache_enable_particles) { + const ContentFeatures &features = + client->getNodeDefManager()->get(wasnode); + client->getParticleManager()->addDiggingParticles(gamedef, smgr, + player, nodepos, features.tiles); + } + + runData->dig_time = 0; + runData->digging = false; + + runData->nodig_delay_timer = + runData->dig_time_complete / (float)crack_animation_length; + + // We don't want a corresponding delay to + // very time consuming nodes + if (runData->nodig_delay_timer > 0.3) + runData->nodig_delay_timer = 0.3; + + // We want a slight delay to very little + // time consuming nodes + const float mindelay = 0.15; + + if (runData->nodig_delay_timer < mindelay) + runData->nodig_delay_timer = mindelay; + + // Send event to trigger sound + MtEvent *e = new NodeDugEvent(nodepos, wasnode); + gamedef->event()->put(e); + } + + if (runData->dig_time_complete < 100000.0) { + runData->dig_time += dtime; + } else { + runData->dig_time = 0; + client->setCrack(-1, nodepos); + } + + camera->setDigging(0); // left click animation +} + + +void Game::updateFrame(std::vector &highlight_boxes, + ProfilerGraph *graph, RunStats *stats, GameRunData *runData, + f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + /* + Fog range + */ + + if (draw_control->range_all) { + runData->fog_range = 100000 * BS; + } else { + runData->fog_range = draw_control->wanted_range * BS + + 0.0 * MAP_BLOCKSIZE * BS; + runData->fog_range = MYMIN( + runData->fog_range, + (draw_control->farthest_drawn + 20) * BS); + runData->fog_range *= 0.9; + } + + /* + Calculate general brightness + */ + u32 daynight_ratio = client->getEnv().getDayNightRatio(); + float time_brightness = decode_light_f((float)daynight_ratio / 1000.0); + float direct_brightness; + bool sunlight_seen; + + if (g_settings->getBool("free_move")) { + direct_brightness = time_brightness; + sunlight_seen = true; + } else { + ScopeProfiler sp(g_profiler, "Detecting background light", SPT_AVG); + float old_brightness = sky->getBrightness(); + direct_brightness = client->getEnv().getClientMap() + .getBackgroundBrightness(MYMIN(runData->fog_range * 1.2, 60 * BS), + daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen) + / 255.0; + } + + float time_of_day = runData->time_of_day; + float time_of_day_smooth = runData->time_of_day_smooth; + + time_of_day = client->getEnv().getTimeOfDayF(); + + const float maxsm = 0.05; + const float todsm = 0.05; + + if (fabs(time_of_day - time_of_day_smooth) > maxsm && + fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm && + fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm) + time_of_day_smooth = time_of_day; + + if (time_of_day_smooth > 0.8 && time_of_day < 0.2) + time_of_day_smooth = time_of_day_smooth * (1.0 - todsm) + + (time_of_day + 1.0) * todsm; + else + time_of_day_smooth = time_of_day_smooth * (1.0 - todsm) + + time_of_day * todsm; + + runData->time_of_day = time_of_day; + runData->time_of_day_smooth = time_of_day_smooth; + + sky->update(time_of_day_smooth, time_brightness, direct_brightness, + sunlight_seen, camera->getCameraMode(), player->getYaw(), + player->getPitch()); + + /* + Update clouds + */ + if (clouds) { + v3f player_position = player->getPosition(); + if (sky->getCloudsVisible()) { + clouds->setVisible(true); + clouds->step(dtime); + clouds->update(v2f(player_position.X, player_position.Z), + sky->getCloudColor()); + } else { + clouds->setVisible(false); + } + } + + /* + Update particles + */ + client->getParticleManager()->step(dtime); + + /* + Fog + */ + + if (m_cache_enable_fog && !flags.force_fog_off) { + driver->setFog( + sky->getBgColor(), + video::EFT_FOG_LINEAR, + runData->fog_range * 0.4, + runData->fog_range * 1.0, + 0.01, + false, // pixel fog + false // range fog + ); + } else { + driver->setFog( + sky->getBgColor(), + video::EFT_FOG_LINEAR, + 100000 * BS, + 110000 * BS, + 0.01, + false, // pixel fog + false // range fog + ); + } + + /* + Get chat messages from client + */ + + v2u32 screensize = driver->getScreenSize(); + + updateChat(*client, dtime, flags.show_debug, screensize, + flags.show_chat, runData->profiler_current_page, + *chat_backend, guitext_chat); + + /* + Inventory + */ + + if (client->getPlayerItem() != runData->new_playeritem) + client->selectPlayerItem(runData->new_playeritem); + + // Update local inventory if it has changed + if (client->getLocalInventoryUpdated()) { + //infostream<<"Updating local inventory"<getLocalInventory(*local_inventory); + runData->update_wielded_item_trigger = true; + } + + if (runData->update_wielded_item_trigger) { + // Update wielded tool + InventoryList *mlist = local_inventory->getList("main"); + + if (mlist && (client->getPlayerItem() < mlist->getSize())) { + ItemStack item = mlist->getItem(client->getPlayerItem()); + camera->wield(item); + } + runData->update_wielded_item_trigger = false; + } + + /* + Update block draw list every 200ms or when camera direction has + changed much + */ + runData->update_draw_list_timer += dtime; + + v3f camera_direction = camera->getDirection(); + if (runData->update_draw_list_timer >= 0.2 + || runData->update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2 + || flags.camera_offset_changed) { + runData->update_draw_list_timer = 0; + client->getEnv().getClientMap().updateDrawList(driver); + runData->update_draw_list_last_cam_dir = camera_direction; + } + + updateGui(&runData->statustext_time, *stats, *runData, dtime, flags, cam); + + /* + make sure menu is on top + 1. Delete formspec menu reference if menu was removed + 2. Else, make sure formspec menu is on top + */ + if (current_formspec) { + if (current_formspec->getReferenceCount() == 1) { + current_formspec->drop(); + current_formspec = NULL; + } else if (!noMenuActive()) { + guiroot->bringToFront(current_formspec); + } + } + + /* + Drawing begins + */ + + video::SColor skycolor = sky->getSkyColor(); + + TimeTaker tt_draw("mainloop: draw"); + { + TimeTaker timer("beginScene"); + driver->beginScene(true, true, skycolor); + stats->beginscenetime = timer.stop(true); + } + + draw_scene(driver, smgr, *camera, *client, player, *hud, guienv, + highlight_boxes, screensize, skycolor, flags.show_hud); + + /* + Profiler graph + */ + if (flags.show_profiler_graph) + graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont()); + + /* + Damage flash + */ + if (runData->damage_flash > 0.0) { + video::SColor color(std::min(runData->damage_flash, 180.0f), + 180, + 0, + 0); + driver->draw2DRectangle(color, + core::rect(0, 0, screensize.X, screensize.Y), + NULL); + + runData->damage_flash -= 100.0 * dtime; + } + + /* + Damage camera tilt + */ + if (player->hurt_tilt_timer > 0.0) { + player->hurt_tilt_timer -= dtime * 5; + + if (player->hurt_tilt_timer < 0) + player->hurt_tilt_strength = 0; + } + + /* + End scene + */ + { + TimeTaker timer("endScene"); + driver->endScene(); + stats->endscenetime = timer.stop(true); + } + + stats->drawtime = tt_draw.stop(true); + g_profiler->graphAdd("mainloop_draw", stats->drawtime / 1000.0f); +} + + +inline static const char *yawToDirectionString(int yaw) +{ + // NOTE: TODO: This can be done mathematically without the else/else-if + // cascade. + + const char *player_direction; + + yaw = wrapDegrees_0_360(yaw); + + if (yaw >= 45 && yaw < 135) + player_direction = "West [-X]"; + else if (yaw >= 135 && yaw < 225) + player_direction = "South [-Z]"; + else if (yaw >= 225 && yaw < 315) + player_direction = "East [+X]"; + else + player_direction = "North [+Z]"; + + return player_direction; +} + + +void Game::updateGui(float *statustext_time, const RunStats &stats, + const GameRunData& runData, f32 dtime, const VolatileRunFlags &flags, + const CameraOrientation &cam) +{ + v2u32 screensize = driver->getScreenSize(); + LocalPlayer *player = client->getEnv().getLocalPlayer(); + v3f player_position = player->getPosition(); + + if (flags.show_debug) { + static float drawtime_avg = 0; + drawtime_avg = drawtime_avg * 0.95 + stats.drawtime * 0.05; + + u16 fps = 1.0 / stats.dtime_jitter.avg; + //s32 fps = driver->getFPS(); + + std::ostringstream os(std::ios_base::binary); + os << std::fixed + << "Blokel " << minetest_version_hash + << " FPS = " << fps + << " (R: range_all=" << draw_control->range_all << ")" + << std::setprecision(0) + << " drawtime = " << drawtime_avg + << std::setprecision(1) + << ", dtime_jitter = " + << (stats.dtime_jitter.max_fraction * 100.0) << " %" + << std::setprecision(1) + << ", v_range = " << draw_control->wanted_range + << std::setprecision(3) + << ", RTT = " << client->getRTT(); + guitext->setText(narrow_to_wide(os.str()).c_str()); + guitext->setVisible(true); + } else if (flags.show_hud || flags.show_chat) { + std::ostringstream os(std::ios_base::binary); + os << "Blokel " << minetest_version_hash; + guitext->setText(narrow_to_wide(os.str()).c_str()); + guitext->setVisible(true); + } else { + guitext->setVisible(false); + } + + if (guitext->isVisible()) { + core::rect rect( + 5, 5, + screensize.X, 5 + g_fontengine->getTextHeight() + ); + guitext->setRelativePosition(rect); + } + + if (flags.show_debug) { + std::ostringstream os(std::ios_base::binary); + os << std::setprecision(1) << std::fixed + << "(" << (player_position.X / BS) + << ", " << (player_position.Y / BS) + << ", " << (player_position.Z / BS) + << ") (yaw=" << (wrapDegrees_0_360(cam.camera_yaw)) + << " " << yawToDirectionString(cam.camera_yaw) + << ") (seed = " << ((u64)client->getMapSeed()) + << ")"; + + if (runData.pointed_old.type == POINTEDTHING_NODE) { + ClientMap &map = client->getEnv().getClientMap(); + const INodeDefManager *nodedef = client->getNodeDefManager(); + MapNode n = map.getNodeNoEx(runData.pointed_old.node_undersurface); + if (n.getContent() != CONTENT_IGNORE && nodedef->get(n).name != "unknown") { + const ContentFeatures &features = nodedef->get(n); + os << " (pointing_at = " << nodedef->get(n).name + << " - " << features.tiledef[0].name.c_str() + << ")"; + } + } + + guitext2->setText(narrow_to_wide(os.str()).c_str()); + guitext2->setVisible(true); + + core::rect rect( + 5, 5 + g_fontengine->getTextHeight(), + screensize.X, 5 + g_fontengine->getTextHeight() * 2 + ); + guitext2->setRelativePosition(rect); + } else { + guitext2->setVisible(false); + } + + guitext_info->setText(infotext.c_str()); + guitext_info->setVisible(flags.show_hud && g_menumgr.menuCount() == 0); + + float statustext_time_max = 1.5; + + if (!statustext.empty()) { + *statustext_time += dtime; + + if (*statustext_time >= statustext_time_max) { + statustext = L""; + *statustext_time = 0; + } + } + + guitext_status->setText(statustext.c_str()); + guitext_status->setVisible(!statustext.empty()); + + if (!statustext.empty()) { + s32 status_width = guitext_status->getTextWidth(); + s32 status_height = guitext_status->getTextHeight(); + s32 status_y = screensize.Y - 150; + s32 status_x = (screensize.X - status_width) / 2; + core::rect rect( + status_x , status_y - status_height, + status_x + status_width, status_y + ); + guitext_status->setRelativePosition(rect); + + // Fade out + video::SColor initial_color(255, 0, 0, 0); + + if (guienv->getSkin()) + initial_color = guienv->getSkin()->getColor(gui::EGDC_BUTTON_TEXT); + + video::SColor final_color = initial_color; + final_color.setAlpha(0); + video::SColor fade_color = initial_color.getInterpolated_quadratic( + initial_color, final_color, + pow(*statustext_time / statustext_time_max, 2.0f)); + guitext_status->setOverrideColor(fade_color); + guitext_status->enableOverrideColor(true); + } +} + + +/* Log times and stuff for visualization */ +inline void Game::updateProfilerGraphs(ProfilerGraph *graph) +{ + Profiler::GraphValues values; + g_profiler->graphGet(values); + graph->put(values); +} + + + +/**************************************************************************** + Misc + ****************************************************************************/ + +/* On some computers framerate doesn't seem to be automatically limited + */ +inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime) +{ + // not using getRealTime is necessary for wine + device->getTimer()->tick(); // Maker sure device time is up-to-date + u32 time = device->getTimer()->getTime(); + + u32 last_time = fps_timings->last_time; + + if (time > last_time) // Make sure time hasn't overflowed + fps_timings->busy_time = time - last_time; + else + fps_timings->busy_time = 0; + + u32 frametime_min = 1000 / (g_menumgr.pausesGame() + ? g_settings->getFloat("pause_fps_max") + : g_settings->getFloat("fps_max")); + + if (fps_timings->busy_time < frametime_min) { + fps_timings->sleep_time = frametime_min - fps_timings->busy_time; + device->sleep(fps_timings->sleep_time); + } else { + fps_timings->sleep_time = 0; + } + + /* Get the new value of the device timer. Note that device->sleep() may + * not sleep for the entire requested time as sleep may be interrupted and + * therefore it is arguably more accurate to get the new time from the + * device rather than calculating it by adding sleep_time to time. + */ + + device->getTimer()->tick(); // Update device timer + time = device->getTimer()->getTime(); + + if (time > last_time) // Make sure last_time hasn't overflowed + *dtime = (time - last_time) / 1000.0; + else + *dtime = 0; + + fps_timings->last_time = time; +} + +// Note: This will free (using delete[])! \p msg. If you want to use it later, +// pass a copy of it to this function +// Note: \p msg must be allocated using new (not malloc()) +void Game::showOverlayMessage(const wchar_t *msg, float dtime, + int percent, bool draw_clouds) +{ + draw_load_screen(msg, device, guienv, dtime, percent, draw_clouds); + delete[] msg; +} + + +/**************************************************************************** + Shutdown / cleanup + ****************************************************************************/ + +void Game::extendedResourceCleanup() +{ + // Extended resource accounting + infostream << "Irrlicht resources after cleanup:" << std::endl; + infostream << "\tRemaining meshes : " + << device->getSceneManager()->getMeshCache()->getMeshCount() << std::endl; + infostream << "\tRemaining textures : " + << driver->getTextureCount() << std::endl; + + for (unsigned int i = 0; i < driver->getTextureCount(); i++) { + irr::video::ITexture *texture = driver->getTextureByIndex(i); + infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str() + << std::endl; + } + + clearTextureNameCache(); + infostream << "\tRemaining materials: " + << driver-> getMaterialRendererCount() + << " (note: irrlicht doesn't support removing renderers)" << std::endl; +} + + + +/**************************************************************************** + extern function for launching the game + ****************************************************************************/ + +void the_game(bool *kill, + bool random_input, + InputHandler *input, + IrrlichtDevice *device, + + const std::string &map_dir, + const std::string &playername, + const std::string &password, + const std::string &address, // If empty local server is created + u16 port, + + std::wstring &error_message, + ChatBackend &chat_backend, + const SubgameSpec &gamespec, // Used for local game + bool simple_singleplayer_mode) +{ + Game game; + + /* Make a copy of the server address because if a local singleplayer server + * is created then this is updated and we don't want to change the value + * passed to us by the calling function + */ + std::string server_address = address; + + try { + + if (game.startup(kill, random_input, input, device, map_dir, + playername, password, &server_address, port, + &error_message, &chat_backend, gamespec, + simple_singleplayer_mode)) { + + game.run(); + game.shutdown(); + } + + } catch (SerializationError &e) { + error_message = L"A serialization error occurred:\n" + + narrow_to_wide(e.what()) + L"\n\nThe server is probably " + L" running a different version of Blokel."; + errorstream << wide_to_narrow(error_message) << std::endl; + } catch (ServerError &e) { + error_message = narrow_to_wide(e.what()); + errorstream << "ServerError: " << e.what() << std::endl; + } catch (ModError &e) { + errorstream << "ModError: " << e.what() << std::endl; + error_message = narrow_to_wide(e.what()) + wstrgettext("\nCheck debug.txt for details."); + } +} diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..61f780b --- /dev/null +++ b/src/game.h @@ -0,0 +1,154 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef GAME_HEADER +#define GAME_HEADER + +#include "irrlichttypes_extrabloated.h" +#include +#include "keycode.h" +#include + +class KeyList : protected std::list +{ + typedef std::list super; + typedef super::iterator iterator; + typedef super::const_iterator const_iterator; + + virtual const_iterator find(const KeyPress &key) const + { + const_iterator f(begin()); + const_iterator e(end()); + + while (f != e) { + if (*f == key) + return f; + + ++f; + } + + return e; + } + + virtual iterator find(const KeyPress &key) + { + iterator f(begin()); + iterator e(end()); + + while (f != e) { + if (*f == key) + return f; + + ++f; + } + + return e; + } + +public: + void clear() + { + super::clear(); + } + + void set(const KeyPress &key) + { + if (find(key) == end()) + push_back(key); + } + + void unset(const KeyPress &key) + { + iterator p(find(key)); + + if (p != end()) + erase(p); + } + + void toggle(const KeyPress &key) + { + iterator p(this->find(key)); + + if (p != end()) + erase(p); + else + push_back(key); + } + + bool operator[](const KeyPress &key) const + { + return find(key) != end(); + } +}; + +class InputHandler +{ +public: + InputHandler() + { + } + virtual ~InputHandler() + { + } + + virtual bool isKeyDown(const KeyPress &keyCode) = 0; + virtual bool wasKeyDown(const KeyPress &keyCode) = 0; + + virtual v2s32 getMousePos() = 0; + virtual void setMousePos(s32 x, s32 y) = 0; + + virtual bool getLeftState() = 0; + virtual bool getRightState() = 0; + + virtual bool getLeftClicked() = 0; + virtual bool getRightClicked() = 0; + virtual void resetLeftClicked() = 0; + virtual void resetRightClicked() = 0; + + virtual bool getLeftReleased() = 0; + virtual bool getRightReleased() = 0; + virtual void resetLeftReleased() = 0; + virtual void resetRightReleased() = 0; + + virtual s32 getMouseWheel() = 0; + + virtual void step(float dtime) {} + + virtual void clear() {} +}; + +class ChatBackend; /* to avoid having to include chat.h */ +struct SubgameSpec; + +void the_game(bool *kill, + bool random_input, + InputHandler *input, + IrrlichtDevice *device, + const std::string &map_dir, + const std::string &playername, + const std::string &password, + const std::string &address, // If "", local server is used + u16 port, + std::wstring &error_message, + ChatBackend &chat_backend, + const SubgameSpec &gamespec, // Used for local game + bool simple_singleplayer_mode); + +#endif + diff --git a/src/gamedef.h b/src/gamedef.h new file mode 100644 index 0000000..793d85b --- /dev/null +++ b/src/gamedef.h @@ -0,0 +1,89 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef GAMEDEF_HEADER +#define GAMEDEF_HEADER + +#include +#include "irrlichttypes.h" + +class IItemDefManager; +class INodeDefManager; +class ICraftDefManager; +class ITextureSource; +class ISoundManager; +class IShaderSource; +class MtEventManager; +class IRollbackManager; +namespace irr { namespace scene { + class IAnimatedMesh; + class ISceneManager; +}} + +/* + An interface for fetching game-global definitions like tool and + mapnode properties +*/ + +class IGameDef +{ +public: + // These are thread-safe IF they are not edited while running threads. + // Thus, first they are set up and then they are only read. + virtual IItemDefManager* getItemDefManager()=0; + virtual INodeDefManager* getNodeDefManager()=0; + virtual ICraftDefManager* getCraftDefManager()=0; + + // This is always thread-safe, but referencing the irrlicht texture + // pointers in other threads than main thread will make things explode. + virtual ITextureSource* getTextureSource()=0; + + virtual IShaderSource* getShaderSource()=0; + + // Used for keeping track of names/ids of unknown nodes + virtual u16 allocateUnknownNodeId(const std::string &name)=0; + + // Only usable on the client + virtual ISoundManager* getSoundManager()=0; + virtual MtEventManager* getEventManager()=0; + virtual scene::IAnimatedMesh* getMesh(const std::string &filename) + { return NULL; } + virtual scene::ISceneManager* getSceneManager()=0; + + // Only usable on the server, and NOT thread-safe. It is usable from the + // environment thread. + virtual IRollbackManager* getRollbackManager(){return NULL;} + + // Used on the client + virtual bool checkLocalPrivilege(const std::string &priv) + { return false; } + + // Shorthands + IItemDefManager* idef(){return getItemDefManager();} + INodeDefManager* ndef(){return getNodeDefManager();} + ICraftDefManager* cdef(){return getCraftDefManager();} + ITextureSource* tsrc(){return getTextureSource();} + ISoundManager* sound(){return getSoundManager();} + IShaderSource* shsrc(){return getShaderSource();} + MtEventManager* event(){return getEventManager();} + IRollbackManager* rollback(){return getRollbackManager();} +}; + +#endif + diff --git a/src/gameparams.h b/src/gameparams.h new file mode 100644 index 0000000..b50e943 --- /dev/null +++ b/src/gameparams.h @@ -0,0 +1,33 @@ +/* +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. +*/ + +#ifndef __GAME_PARAMS_H__ +#define __GAME_PARAMS_H__ + +#include "irrlichttypes_extrabloated.h" + +struct GameParams { + u16 socket_port; + std::string world_path; + SubgameSpec game_spec; + bool is_dedicated_server; + int log_level; +}; + +#endif diff --git a/src/genericobject.cpp b/src/genericobject.cpp new file mode 100644 index 0000000..9a1b9d8 --- /dev/null +++ b/src/genericobject.cpp @@ -0,0 +1,172 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#include "genericobject.h" +#include +#include "util/serialize.h" + +std::string gob_cmd_set_properties(const ObjectProperties &prop) +{ + std::ostringstream os(std::ios::binary); + writeU8(os, GENERIC_CMD_SET_PROPERTIES); + prop.serialize(os); + return os.str(); +} + +ObjectProperties gob_read_set_properties(std::istream &is) +{ + ObjectProperties prop; + prop.deSerialize(is); + return prop; +} + +std::string gob_cmd_update_position( + v3f position, + v3f velocity, + v3f acceleration, + f32 yaw, + bool do_interpolate, + bool is_movement_end, + f32 update_interval +){ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, GENERIC_CMD_UPDATE_POSITION); + // pos + writeV3F1000(os, position); + // velocity + writeV3F1000(os, velocity); + // acceleration + writeV3F1000(os, acceleration); + // yaw + writeF1000(os, yaw); + // do_interpolate + writeU8(os, do_interpolate); + // is_end_position (for interpolation) + writeU8(os, is_movement_end); + // update_interval (for interpolation) + writeF1000(os, update_interval); + return os.str(); +} + +std::string gob_cmd_set_texture_mod(const std::string &mod) +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, GENERIC_CMD_SET_TEXTURE_MOD); + // parameters + os<first); + writeS16(os, i->second); + } + return os.str(); +} + +std::string gob_cmd_update_physics_override(float physics_override_speed, float physics_override_jump, + float physics_override_gravity, bool sneak, bool sneak_glitch) +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, GENERIC_CMD_SET_PHYSICS_OVERRIDE); + // parameters + writeF1000(os, physics_override_speed); + writeF1000(os, physics_override_jump); + writeF1000(os, physics_override_gravity); + // these are sent inverted so we get true when the server sends nothing + writeU8(os, !sneak); + writeU8(os, !sneak_glitch); + return os.str(); +} + +std::string gob_cmd_update_animation(v2f frames, float frame_speed, float frame_blend) +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, GENERIC_CMD_SET_ANIMATION); + // parameters + writeV2F1000(os, frames); + writeF1000(os, frame_speed); + writeF1000(os, frame_blend); + return os.str(); +} + +std::string gob_cmd_update_bone_position(std::string bone, v3f position, v3f rotation) +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, GENERIC_CMD_SET_BONE_POSITION); + // parameters + os< + +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. +*/ + +#ifndef GENERICOBJECT_HEADER +#define GENERICOBJECT_HEADER + +#include +#include "irrlichttypes_bloated.h" +#include + +#define GENERIC_CMD_SET_PROPERTIES 0 +#define GENERIC_CMD_UPDATE_POSITION 1 +#define GENERIC_CMD_SET_TEXTURE_MOD 2 +#define GENERIC_CMD_SET_SPRITE 3 +#define GENERIC_CMD_PUNCHED 4 +#define GENERIC_CMD_UPDATE_ARMOR_GROUPS 5 +#define GENERIC_CMD_SET_ANIMATION 6 +#define GENERIC_CMD_SET_BONE_POSITION 7 +#define GENERIC_CMD_SET_ATTACHMENT 8 +#define GENERIC_CMD_SET_PHYSICS_OVERRIDE 9 + +#include "object_properties.h" +std::string gob_cmd_set_properties(const ObjectProperties &prop); +ObjectProperties gob_read_set_properties(std::istream &is); + +std::string gob_cmd_update_position( + v3f position, + v3f velocity, + v3f acceleration, + f32 yaw, + bool do_interpolate, + bool is_movement_end, + f32 update_interval +); + +std::string gob_cmd_set_texture_mod(const std::string &mod); + +std::string gob_cmd_set_sprite( + v2s16 p, + u16 num_frames, + f32 framelength, + bool select_horiz_by_yawpitch +); + +std::string gob_cmd_punched(s16 damage, s16 result_hp); + +#include "itemgroup.h" +std::string gob_cmd_update_armor_groups(const ItemGroupList &armor_groups); + +std::string gob_cmd_update_physics_override(float physics_override_speed, + float physics_override_jump, float physics_override_gravity, bool sneak, bool sneak_glitch); + +std::string gob_cmd_update_animation(v2f frames, float frame_speed, float frame_blend); + +std::string gob_cmd_update_bone_position(std::string bone, v3f position, v3f rotation); + +std::string gob_cmd_update_attachment(int parent_id, std::string bone, v3f position, v3f rotation); + +#endif + diff --git a/src/gettext.cpp b/src/gettext.cpp new file mode 100644 index 0000000..fc75694 --- /dev/null +++ b/src/gettext.cpp @@ -0,0 +1,267 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#include +#include +#include +#include +#include "gettext.h" +#include "util/string.h" +#include "log.h" + +#if USE_GETTEXT && defined(_MSC_VER) +#include +#include +#include +#include "filesys.h" + +#define setlocale(category, localename) \ + setlocale(category, MSVC_LocaleLookup(localename)) + +static std::map glb_supported_locales; + +/******************************************************************************/ +BOOL CALLBACK UpdateLocaleCallback(LPTSTR pStr) +{ + char* endptr = 0; + int LOCALEID = strtol(pStr, &endptr,16); + + wchar_t buffer[LOCALE_NAME_MAX_LENGTH]; + memset(buffer, 0, sizeof(buffer)); + if (GetLocaleInfoW( + LOCALEID, + LOCALE_SISO639LANGNAME, + buffer, + LOCALE_NAME_MAX_LENGTH)) { + + std::wstring name = buffer; + + memset(buffer, 0, sizeof(buffer)); + GetLocaleInfoW( + LOCALEID, + LOCALE_SISO3166CTRYNAME, + buffer, + LOCALE_NAME_MAX_LENGTH); + + std::wstring country = buffer; + + memset(buffer, 0, sizeof(buffer)); + GetLocaleInfoW( + LOCALEID, + LOCALE_SENGLISHLANGUAGENAME, + buffer, + LOCALE_NAME_MAX_LENGTH); + + std::wstring languagename = buffer; + + /* set both short and long variant */ + glb_supported_locales[name] = languagename; + glb_supported_locales[name + L"_" + country] = languagename; + } + return true; +} + +/******************************************************************************/ +const char* MSVC_LocaleLookup(const char* raw_shortname) { + + /* NULL is used to read locale only so we need to return it too */ + if (raw_shortname == NULL) return NULL; + + std::string shortname(raw_shortname); + if (shortname == "C") return "C"; + if (shortname == "") return ""; + + static std::string last_raw_value = ""; + static std::string last_full_name = ""; + static bool first_use = true; + + if (last_raw_value == shortname) { + return last_full_name.c_str(); + } + + if (first_use) { + EnumSystemLocalesA(UpdateLocaleCallback, LCID_SUPPORTED | LCID_ALTERNATE_SORTS); + first_use = false; + } + + last_raw_value = shortname; + + if (glb_supported_locales.find(narrow_to_wide(shortname)) != glb_supported_locales.end()) { + last_full_name = wide_to_narrow(glb_supported_locales[narrow_to_wide(shortname)]); + return last_full_name.c_str(); + } + + /* empty string is system default */ + errorstream << "MSVC_LocaleLookup: unsupported locale: \"" << shortname + << "\" switching to system default!" << std::endl; + return ""; +} + +#endif + +/******************************************************************************/ +#ifdef _MSC_VER +void init_gettext(const char *path, const std::string &configured_language, int argc, char** argv) { +#else +void init_gettext(const char *path, const std::string &configured_language) { +#endif +#if USE_GETTEXT + /** first try to set user override environment **/ + if (configured_language.length() != 0) { +#ifndef _WIN32 + /* add user specified locale to environment */ + setenv("LANGUAGE", configured_language.c_str(), 1); + + /* reload locale with changed environment */ + setlocale(LC_ALL, ""); +#elif defined(_MSC_VER) + std::string current_language_var(""); + if (getenv("LANGUAGE") != 0) { + current_language_var = std::string(getenv("LANGUAGE")); + } + + char *lang_str = (char*)calloc(10 + configured_language.length(), sizeof(char)); + strcat(lang_str, "LANGUAGE="); + strcat(lang_str, configured_language.c_str()); + putenv(lang_str); + + SetEnvironmentVariableA("LANGUAGE",configured_language.c_str()); + +#ifndef SERVER + //very very dirty workaround to force gettext to see the right environment + if (current_language_var != configured_language) { + STARTUPINFO startupinfo; + PROCESS_INFORMATION processinfo; + memset(&startupinfo, 0, sizeof(startupinfo)); + memset(&processinfo, 0, sizeof(processinfo)); + errorstream << "MSVC localization workaround active restating minetest in new environment!" << std::endl; + + std::string parameters = ""; + + for (unsigned int i=1;i < argc; i++) { + if (parameters != "") { + parameters += " "; + } + parameters += argv[i]; + } + + const char* ptr_parameters = 0; + + if (parameters != "") { + ptr_parameters = parameters.c_str(); + } + + /** users may start by short name in commandline without extention **/ + std::string appname = argv[0]; + if (appname.substr(appname.length() - 4) != ".exe") { + appname += ".exe"; + } + + if (!CreateProcess(appname.c_str(), + (char*) ptr_parameters, + NULL, + NULL, + false, + DETACHED_PROCESS | CREATE_UNICODE_ENVIRONMENT, + NULL, + NULL, + &startupinfo, + &processinfo)) { + char buffer[1024]; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), + buffer, + sizeof(buffer)-1, + NULL); + errorstream << "*******************************************************" << std::endl; + errorstream << "CMD: " << appname << std::endl; + errorstream << "Failed to restart with current locale: " << std::endl; + errorstream << buffer; + errorstream << "Expect language to be broken!" << std::endl; + errorstream << "*******************************************************" << std::endl; + } + else { + exit(0); + } +#else + errorstream << "*******************************************************" << std::endl; + errorstream << "Can't apply locale workaround for server!" << std::endl; + errorstream << "Expect language to be broken!" << std::endl; + errorstream << "*******************************************************" << std::endl; + +#endif + } + + setlocale(LC_ALL,configured_language.c_str()); +#else // Mingw + char *lang_str = (char*)calloc(10 + configured_language.length(), sizeof(char)); + strcat(lang_str, "LANGUAGE="); + strcat(lang_str, configured_language.c_str()); + putenv(lang_str); + + setlocale(LC_ALL, ""); +#endif // ifndef _WIN32 + } + else { + /* set current system default locale */ + setlocale(LC_ALL, ""); + } + +#if defined(_WIN32) + if (getenv("LANGUAGE") != 0) { + setlocale(LC_ALL, getenv("LANGUAGE")); + } +#ifdef _MSC_VER + else if (getenv("LANG") != 0) { + setlocale(LC_ALL, getenv("LANG")); + } +#endif +#endif + + bindtextdomain(PROJECT_NAME, path); + textdomain(PROJECT_NAME); + +#if defined(_WIN32) + // Set character encoding for Win32 + char *tdomain = textdomain( (char *) NULL ); + if( tdomain == NULL ) + { + errorstream << "Warning: domainname parameter is the null pointer" << + ", default domain is not set" << std::endl; + tdomain = (char *) "messages"; + } + /* char *codeset = */bind_textdomain_codeset( tdomain, "UTF-8" ); + //errorstream << "Gettext debug: domainname = " << tdomain << "; codeset = "<< codeset << std::endl; +#endif // defined(_WIN32) + +#else + /* set current system default locale */ + setlocale(LC_ALL, ""); +#endif // if USE_GETTEXT + + /* no matter what locale is used we need number format to be "C" */ + /* to ensure formspec parameters are evaluated correct! */ + + setlocale(LC_NUMERIC, "C"); + infostream << "Message locale is now set to: " + << setlocale(LC_ALL, 0) << std::endl; +} + diff --git a/src/gettext.h b/src/gettext.h new file mode 100644 index 0000000..8235efa --- /dev/null +++ b/src/gettext.h @@ -0,0 +1,64 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef GETTEXT_HEADER +#define GETTEXT_HEADER + +#include "config.h" // for USE_GETTEXT + +#if USE_GETTEXT + #include +#else + #define gettext(String) String +#endif + +#define _(String) gettext(String) +#define gettext_noop(String) (String) +#define N_(String) gettext_noop((String)) + +#ifdef _MSC_VER +void init_gettext(const char *path, const std::string &configured_language, + int argc, char** argv); +#else +void init_gettext(const char *path, const std::string &configured_language); +#endif + +extern wchar_t *narrow_to_wide_c(const char *str); + +// You must free the returned string! +// The returned string is allocated using new +inline const wchar_t *wgettext(const char *str) +{ + return narrow_to_wide_c(gettext(str)); +} + +inline std::wstring wstrgettext(const std::string &text) +{ + const wchar_t *tmp = wgettext(text.c_str()); + std::wstring retval = (std::wstring)tmp; + delete[] tmp; + return retval; +} + +inline std::string strgettext(const std::string &text) +{ + return gettext(text.c_str()); +} + +#endif diff --git a/src/gettime.h b/src/gettime.h new file mode 100644 index 0000000..2a6a211 --- /dev/null +++ b/src/gettime.h @@ -0,0 +1,63 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef GETTIME_HEADER +#define GETTIME_HEADER + +#include "irrlichttypes.h" + +/* + Get a millisecond counter value. + Precision depends on implementation. + Overflows at any value above 10000000. + + Implementation of this is done in: + Normal build: main.cpp + Server build: servermain.cpp +*/ +enum TimePrecision { + PRECISION_SECONDS = 0, + PRECISION_MILLI, + PRECISION_MICRO, + PRECISION_NANO +}; + +extern u32 getTimeMs(); +extern u32 getTime(TimePrecision prec); + +/* + Timestamp stuff +*/ + +#include +#include + +inline std::string getTimestamp() +{ + time_t t = time(NULL); + // This is not really thread-safe but it won't break anything + // except its own output, so just go with it. + struct tm *tm = localtime(&t); + char cs[20]; + strftime(cs, 20, "%H:%M:%S", tm); + return cs; +} + + +#endif diff --git a/src/guiChatConsole.cpp b/src/guiChatConsole.cpp new file mode 100644 index 0000000..b525890 --- /dev/null +++ b/src/guiChatConsole.cpp @@ -0,0 +1,575 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#include "guiChatConsole.h" +#include "chat.h" +#include "client.h" +#include "debug.h" +#include "gettime.h" +#include "keycode.h" +#include "settings.h" +#include "main.h" // for g_settings +#include "porting.h" +#include "client/tile.h" +#include "fontengine.h" +#include "log.h" +#include "gettext.h" +#include + +#if USE_FREETYPE +#include "xCGUITTFont.h" +#endif + +inline u32 clamp_u8(s32 value) +{ + return (u32) MYMIN(MYMAX(value, 0), 255); +} + + +GUIChatConsole::GUIChatConsole( + gui::IGUIEnvironment* env, + gui::IGUIElement* parent, + s32 id, + ChatBackend* backend, + Client* client +): + IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, + core::rect(0,0,100,100)), + m_chat_backend(backend), + m_client(client), + m_screensize(v2u32(0,0)), + m_animate_time_old(0), + m_open(false), + m_height(0), + m_desired_height(0), + m_desired_height_fraction(0.0), + m_height_speed(5.0), + m_open_inhibited(0), + m_cursor_blink(0.0), + m_cursor_blink_speed(0.0), + m_cursor_height(0.0), + m_background(NULL), + m_background_color(255, 0, 0, 0), + m_font(NULL), + m_fontsize(0, 0) +{ + m_animate_time_old = getTimeMs(); + + // load background settings + s32 console_alpha = g_settings->getS32("console_alpha"); + m_background_color.setAlpha(clamp_u8(console_alpha)); + + // load the background texture depending on settings + ITextureSource *tsrc = client->getTextureSource(); + if (tsrc->isKnownSourceImage("background_chat.jpg")) { + m_background = tsrc->getTexture("background_chat.jpg"); + m_background_color.setRed(255); + m_background_color.setGreen(255); + m_background_color.setBlue(255); + } else { + v3f console_color = g_settings->getV3F("console_color"); + m_background_color.setRed(clamp_u8(myround(console_color.X))); + m_background_color.setGreen(clamp_u8(myround(console_color.Y))); + m_background_color.setBlue(clamp_u8(myround(console_color.Z))); + } + + m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, FM_Mono); + + if (m_font == NULL) + { + errorstream << "GUIChatConsole: Unable to load mono font "; + } + else + { + core::dimension2d dim = m_font->getDimension(L"M"); + m_fontsize = v2u32(dim.Width, dim.Height); + m_font->grab(); + } + m_fontsize.X = MYMAX(m_fontsize.X, 1); + m_fontsize.Y = MYMAX(m_fontsize.Y, 1); + + // set default cursor options + setCursor(true, true, 2.0, 0.1); +} + +GUIChatConsole::~GUIChatConsole() +{ + if (m_font) + m_font->drop(); +} + +void GUIChatConsole::openConsole(f32 height) +{ + m_open = true; + m_desired_height_fraction = height; + m_desired_height = height * m_screensize.Y; + reformatConsole(); +} + +bool GUIChatConsole::isOpen() const +{ + return m_open; +} + +bool GUIChatConsole::isOpenInhibited() const +{ + return m_open_inhibited > 0; +} + +void GUIChatConsole::closeConsole() +{ + m_open = false; +} + +void GUIChatConsole::closeConsoleAtOnce() +{ + m_open = false; + m_height = 0; + recalculateConsolePosition(); +} + +f32 GUIChatConsole::getDesiredHeight() const +{ + return m_desired_height_fraction; +} + +void GUIChatConsole::setCursor( + bool visible, bool blinking, f32 blink_speed, f32 relative_height) +{ + if (visible) + { + if (blinking) + { + // leave m_cursor_blink unchanged + m_cursor_blink_speed = blink_speed; + } + else + { + m_cursor_blink = 0x8000; // on + m_cursor_blink_speed = 0.0; + } + } + else + { + m_cursor_blink = 0; // off + m_cursor_blink_speed = 0.0; + } + m_cursor_height = relative_height; +} + +void GUIChatConsole::draw() +{ + if(!IsVisible) + return; + + video::IVideoDriver* driver = Environment->getVideoDriver(); + + // Check screen size + v2u32 screensize = driver->getScreenSize(); + if (screensize != m_screensize) + { + // screen size has changed + // scale current console height to new window size + if (m_screensize.Y != 0) + m_height = m_height * screensize.Y / m_screensize.Y; + m_desired_height = m_desired_height_fraction * m_screensize.Y; + m_screensize = screensize; + reformatConsole(); + } + + // Animation + u32 now = getTimeMs(); + animate(now - m_animate_time_old); + m_animate_time_old = now; + + // Draw console elements if visible + if (m_height > 0) + { + drawBackground(); + drawText(); + drawPrompt(); + } + + gui::IGUIElement::draw(); +} + +void GUIChatConsole::reformatConsole() +{ + s32 cols = m_screensize.X / m_fontsize.X - 2; // make room for a margin (looks better) + s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt + if (cols <= 0 || rows <= 0) + cols = rows = 0; + m_chat_backend->reformat(cols, rows); +} + +void GUIChatConsole::recalculateConsolePosition() +{ + core::rect rect(0, 0, m_screensize.X, m_height); + DesiredRect = rect; + recalculateAbsolutePosition(false); +} + +void GUIChatConsole::animate(u32 msec) +{ + // animate the console height + s32 goal = m_open ? m_desired_height : 0; + if (m_height != goal) + { + s32 max_change = msec * m_screensize.Y * (m_height_speed / 1000.0); + if (max_change == 0) + max_change = 1; + + if (m_height < goal) + { + // increase height + if (m_height + max_change < goal) + m_height += max_change; + else + m_height = goal; + } + else + { + // decrease height + if (m_height > goal + max_change) + m_height -= max_change; + else + m_height = goal; + } + + recalculateConsolePosition(); + } + + // blink the cursor + if (m_cursor_blink_speed != 0.0) + { + u32 blink_increase = 0x10000 * msec * (m_cursor_blink_speed / 1000.0); + if (blink_increase == 0) + blink_increase = 1; + m_cursor_blink = ((m_cursor_blink + blink_increase) & 0xffff); + } + + // decrease open inhibit counter + if (m_open_inhibited > msec) + m_open_inhibited -= msec; + else + m_open_inhibited = 0; +} + +void GUIChatConsole::drawBackground() +{ + video::IVideoDriver* driver = Environment->getVideoDriver(); + if (m_background != NULL) + { + core::rect sourcerect(0, -m_height, m_screensize.X, 0); + driver->draw2DImage( + m_background, + v2s32(0, 0), + sourcerect, + &AbsoluteClippingRect, + m_background_color, + false); + } + else + { + driver->draw2DRectangle( + m_background_color, + core::rect(0, 0, m_screensize.X, m_height), + &AbsoluteClippingRect); + } +} + +void GUIChatConsole::drawText() +{ + if (m_font == NULL) + return; + + ChatBuffer& buf = m_chat_backend->getConsoleBuffer(); + for (u32 row = 0; row < buf.getRows(); ++row) + { + const ChatFormattedLine& line = buf.getFormattedLine(row); + if (line.fragments.empty()) + continue; + + s32 line_height = m_fontsize.Y; + s32 y = row * line_height + m_height - m_desired_height; + if (y + line_height < 0) + continue; + + for (u32 i = 0; i < line.fragments.size(); ++i) + { + const ChatFormattedFragment& fragment = line.fragments[i]; + s32 x = (fragment.column + 1) * m_fontsize.X; + core::rect destrect( + x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y); + m_font->draw( + fragment.text.c_str(), + destrect, + video::SColor(255, 255, 255, 255), + false, + false, + &AbsoluteClippingRect); + } + } +} + +void GUIChatConsole::drawPrompt() +{ + if (m_font == NULL) + return; + + u32 row = m_chat_backend->getConsoleBuffer().getRows(); + s32 line_height = m_fontsize.Y; + s32 y = row * line_height + m_height - m_desired_height; + + ChatPrompt& prompt = m_chat_backend->getPrompt(); + std::wstring prompt_text = prompt.getVisiblePortion(); + + // FIXME Draw string at once, not character by character + // That will only work with the cursor once we have a monospace font + for (u32 i = 0; i < prompt_text.size(); ++i) + { + wchar_t ws[2] = {prompt_text[i], 0}; + s32 x = (1 + i) * m_fontsize.X; + core::rect destrect( + x, y, x + m_fontsize.X, y + m_fontsize.Y); + m_font->draw( + ws, + destrect, + video::SColor(255, 255, 255, 255), + false, + false, + &AbsoluteClippingRect); + } + + // Draw the cursor during on periods + if ((m_cursor_blink & 0x8000) != 0) + { + s32 cursor_pos = prompt.getVisibleCursorPosition(); + if (cursor_pos >= 0) + { + video::IVideoDriver* driver = Environment->getVideoDriver(); + s32 x = (1 + cursor_pos) * m_fontsize.X; + core::rect destrect( + x, + y + (1.0-m_cursor_height) * m_fontsize.Y, + x + m_fontsize.X, + y + m_fontsize.Y); + video::SColor cursor_color(255,255,255,255); + driver->draw2DRectangle( + cursor_color, + destrect, + &AbsoluteClippingRect); + } + } + +} + +bool GUIChatConsole::OnEvent(const SEvent& event) +{ + if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown) + { + // Key input + if(KeyPress(event.KeyInput) == getKeySetting("keymap_console")) + { + closeConsole(); + Environment->removeFocus(this); + + // inhibit open so the_game doesn't reopen immediately + m_open_inhibited = 50; + return true; + } + else if(event.KeyInput.Key == KEY_ESCAPE) + { + closeConsoleAtOnce(); + Environment->removeFocus(this); + // the_game will open the pause menu + return true; + } + else if(event.KeyInput.Key == KEY_PRIOR) + { + m_chat_backend->scrollPageUp(); + return true; + } + else if(event.KeyInput.Key == KEY_NEXT) + { + m_chat_backend->scrollPageDown(); + return true; + } + else if(event.KeyInput.Key == KEY_RETURN) + { + std::wstring text = m_chat_backend->getPrompt().submit(); + m_client->typeChatMessage(text); + return true; + } + else if(event.KeyInput.Key == KEY_UP) + { + // Up pressed + // Move back in history + m_chat_backend->getPrompt().historyPrev(); + return true; + } + else if(event.KeyInput.Key == KEY_DOWN) + { + // Down pressed + // Move forward in history + m_chat_backend->getPrompt().historyNext(); + return true; + } + else if(event.KeyInput.Key == KEY_LEFT) + { + // Left or Ctrl-Left pressed + // move character / word to the left + ChatPrompt::CursorOpScope scope = + event.KeyInput.Control ? + ChatPrompt::CURSOROP_SCOPE_WORD : + ChatPrompt::CURSOROP_SCOPE_CHARACTER; + m_chat_backend->getPrompt().cursorOperation( + ChatPrompt::CURSOROP_MOVE, + ChatPrompt::CURSOROP_DIR_LEFT, + scope); + return true; + } + else if(event.KeyInput.Key == KEY_RIGHT) + { + // Right or Ctrl-Right pressed + // move character / word to the right + ChatPrompt::CursorOpScope scope = + event.KeyInput.Control ? + ChatPrompt::CURSOROP_SCOPE_WORD : + ChatPrompt::CURSOROP_SCOPE_CHARACTER; + m_chat_backend->getPrompt().cursorOperation( + ChatPrompt::CURSOROP_MOVE, + ChatPrompt::CURSOROP_DIR_RIGHT, + scope); + return true; + } + else if(event.KeyInput.Key == KEY_HOME) + { + // Home pressed + // move to beginning of line + m_chat_backend->getPrompt().cursorOperation( + ChatPrompt::CURSOROP_MOVE, + ChatPrompt::CURSOROP_DIR_LEFT, + ChatPrompt::CURSOROP_SCOPE_LINE); + return true; + } + else if(event.KeyInput.Key == KEY_END) + { + // End pressed + // move to end of line + m_chat_backend->getPrompt().cursorOperation( + ChatPrompt::CURSOROP_MOVE, + ChatPrompt::CURSOROP_DIR_RIGHT, + ChatPrompt::CURSOROP_SCOPE_LINE); + return true; + } + else if(event.KeyInput.Key == KEY_BACK) + { + // Backspace or Ctrl-Backspace pressed + // delete character / word to the left + ChatPrompt::CursorOpScope scope = + event.KeyInput.Control ? + ChatPrompt::CURSOROP_SCOPE_WORD : + ChatPrompt::CURSOROP_SCOPE_CHARACTER; + m_chat_backend->getPrompt().cursorOperation( + ChatPrompt::CURSOROP_DELETE, + ChatPrompt::CURSOROP_DIR_LEFT, + scope); + return true; + } + else if(event.KeyInput.Key == KEY_DELETE) + { + // Delete or Ctrl-Delete pressed + // delete character / word to the right + ChatPrompt::CursorOpScope scope = + event.KeyInput.Control ? + ChatPrompt::CURSOROP_SCOPE_WORD : + ChatPrompt::CURSOROP_SCOPE_CHARACTER; + m_chat_backend->getPrompt().cursorOperation( + ChatPrompt::CURSOROP_DELETE, + ChatPrompt::CURSOROP_DIR_RIGHT, + scope); + return true; + } + else if(event.KeyInput.Key == KEY_KEY_V && event.KeyInput.Control) + { + // Ctrl-V pressed + // paste text from clipboard + IOSOperator *os_operator = Environment->getOSOperator(); + const c8 *text = os_operator->getTextFromClipboard(); + if (text) + { + std::wstring wtext = narrow_to_wide(text); + m_chat_backend->getPrompt().input(wtext); + } + return true; + } + else if(event.KeyInput.Key == KEY_KEY_U && event.KeyInput.Control) + { + // Ctrl-U pressed + // kill line to left end + m_chat_backend->getPrompt().cursorOperation( + ChatPrompt::CURSOROP_DELETE, + ChatPrompt::CURSOROP_DIR_LEFT, + ChatPrompt::CURSOROP_SCOPE_LINE); + return true; + } + else if(event.KeyInput.Key == KEY_KEY_K && event.KeyInput.Control) + { + // Ctrl-K pressed + // kill line to right end + m_chat_backend->getPrompt().cursorOperation( + ChatPrompt::CURSOROP_DELETE, + ChatPrompt::CURSOROP_DIR_RIGHT, + ChatPrompt::CURSOROP_SCOPE_LINE); + return true; + } + else if(event.KeyInput.Key == KEY_TAB) + { + // Tab or Shift-Tab pressed + // Nick completion + std::list names = m_client->getConnectedPlayerNames(); + bool backwards = event.KeyInput.Shift; + m_chat_backend->getPrompt().nickCompletion(names, backwards); + return true; + } + else if(event.KeyInput.Char != 0 && !event.KeyInput.Control) + { + #if (defined(linux) || defined(__linux)) + wchar_t wc = L'_'; + mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) ); + m_chat_backend->getPrompt().input(wc); + #else + m_chat_backend->getPrompt().input(event.KeyInput.Char); + #endif + return true; + } + } + else if(event.EventType == EET_MOUSE_INPUT_EVENT) + { + if(event.MouseInput.Event == EMIE_MOUSE_WHEEL) + { + s32 rows = myround(-3.0 * event.MouseInput.Wheel); + m_chat_backend->scroll(rows); + } + } + + return Parent ? Parent->OnEvent(event) : false; +} + diff --git a/src/guiChatConsole.h b/src/guiChatConsole.h new file mode 100644 index 0000000..652b265 --- /dev/null +++ b/src/guiChatConsole.h @@ -0,0 +1,129 @@ +/* +Minetest +Copyright (C) 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. +*/ + +#ifndef GUICHATCONSOLE_HEADER +#define GUICHATCONSOLE_HEADER + +#include "irrlichttypes_extrabloated.h" +#include "chat.h" +#include "config.h" + +class Client; + +class GUIChatConsole : public gui::IGUIElement +{ +public: + GUIChatConsole(gui::IGUIEnvironment* env, + gui::IGUIElement* parent, + s32 id, + ChatBackend* backend, + Client* client); + virtual ~GUIChatConsole(); + + // Open the console (height = desired fraction of screen size) + // This doesn't open immediately but initiates an animation. + // You should call isOpenInhibited() before this. + void openConsole(f32 height); + + bool isOpen() const; + + // Check if the console should not be opened at the moment + // This is to avoid reopening the console immediately after closing + bool isOpenInhibited() const; + // Close the console, equivalent to openConsole(0). + // This doesn't close immediately but initiates an animation. + void closeConsole(); + // Close the console immediately, without animation. + void closeConsoleAtOnce(); + + // Return the desired height (fraction of screen size) + // Zero if the console is closed or getting closed + f32 getDesiredHeight() const; + + // Change how the cursor looks + void setCursor( + bool visible, + bool blinking = false, + f32 blink_speed = 1.0, + f32 relative_height = 1.0); + + // Irrlicht draw method + virtual void draw(); + + bool canTakeFocus(gui::IGUIElement* element) { return false; } + + virtual bool OnEvent(const SEvent& event); + +private: + void reformatConsole(); + void recalculateConsolePosition(); + + // These methods are called by draw + void animate(u32 msec); + void drawBackground(); + void drawText(); + void drawPrompt(); + +private: + // pointer to the chat backend + ChatBackend* m_chat_backend; + + // pointer to the client + Client* m_client; + + // current screen size + v2u32 m_screensize; + + // used to compute how much time passed since last animate() + u32 m_animate_time_old; + + // should the console be opened or closed? + bool m_open; + // current console height [pixels] + s32 m_height; + // desired height [pixels] + f32 m_desired_height; + // desired height [screen height fraction] + f32 m_desired_height_fraction; + // console open/close animation speed [screen height fraction / second] + f32 m_height_speed; + // if nonzero, opening the console is inhibited [milliseconds] + u32 m_open_inhibited; + + // cursor blink frame (16-bit value) + // cursor is off during [0,32767] and on during [32768,65535] + u32 m_cursor_blink; + // cursor blink speed [on/off toggles / second] + f32 m_cursor_blink_speed; + // cursor height [line height] + f32 m_cursor_height; + + // background texture + video::ITexture* m_background; + // background color (including alpha) + video::SColor m_background_color; + + // font + gui::IGUIFont* m_font; + v2u32 m_fontsize; +}; + + +#endif + diff --git a/src/guiEngine.cpp b/src/guiEngine.cpp new file mode 100644 index 0000000..489c4e2 --- /dev/null +++ b/src/guiEngine.cpp @@ -0,0 +1,621 @@ +/* +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. +*/ + +#include "guiEngine.h" + +#include +#include +#include "scripting_mainmenu.h" +#include "util/numeric.h" +#include "config.h" +#include "version.h" +#include "porting.h" +#include "filesys.h" +#include "main.h" +#include "settings.h" +#include "guiMainMenu.h" +#include "sound.h" +#include "sound_openal.h" +#include "clouds.h" +#include "httpfetch.h" +#include "log.h" +#include "fontengine.h" + +#ifdef __ANDROID__ +#include "client/tile.h" +#include +#endif + + +/******************************************************************************/ +/** TextDestGuiEngine */ +/******************************************************************************/ +TextDestGuiEngine::TextDestGuiEngine(GUIEngine* engine) +{ + m_engine = engine; +} + +/******************************************************************************/ +void TextDestGuiEngine::gotText(std::map fields) +{ + m_engine->getScriptIface()->handleMainMenuButtons(fields); +} + +/******************************************************************************/ +void TextDestGuiEngine::gotText(std::wstring text) +{ + m_engine->getScriptIface()->handleMainMenuEvent(wide_to_narrow(text)); +} + +/******************************************************************************/ +/** MenuTextureSource */ +/******************************************************************************/ +MenuTextureSource::MenuTextureSource(video::IVideoDriver *driver) +{ + m_driver = driver; +} + +/******************************************************************************/ +MenuTextureSource::~MenuTextureSource() +{ + for (std::set::iterator it = m_to_delete.begin(); + it != m_to_delete.end(); ++it) { + const char *tname = (*it).c_str(); + video::ITexture *texture = m_driver->getTexture(tname); + m_driver->removeTexture(texture); + } +} + +/******************************************************************************/ +video::ITexture* MenuTextureSource::getTexture(const std::string &name, u32 *id) +{ + if(id) + *id = 0; + if(name.empty()) + return NULL; + m_to_delete.insert(name); + +#ifdef __ANDROID__ + video::IImage *image = m_driver->createImageFromFile(name.c_str()); + if (image) { + image = Align2Npot2(image, m_driver); + video::ITexture* retval = m_driver->addTexture(name.c_str(), image); + image->drop(); + return retval; + } +#endif + return m_driver->getTexture(name.c_str()); +} + +/******************************************************************************/ +/** MenuMusicFetcher */ +/******************************************************************************/ +void MenuMusicFetcher::fetchSounds(const std::string &name, + std::set &dst_paths, + std::set &dst_datas) +{ + if(m_fetched.count(name)) + return; + m_fetched.insert(name); + std::string base; + base = porting::path_share + DIR_DELIM + "sounds"; + dst_paths.insert(base + DIR_DELIM + name + ".ogg"); + int i; + for(i=0; i<10; i++) + dst_paths.insert(base + DIR_DELIM + name + "."+itos(i)+".ogg"); + base = porting::path_user + DIR_DELIM + "sounds"; + dst_paths.insert(base + DIR_DELIM + name + ".ogg"); + for(i=0; i<10; i++) + dst_paths.insert(base + DIR_DELIM + name + "."+itos(i)+".ogg"); +} + +/******************************************************************************/ +/** GUIEngine */ +/******************************************************************************/ +GUIEngine::GUIEngine( irr::IrrlichtDevice* dev, + gui::IGUIElement* parent, + IMenuManager *menumgr, + scene::ISceneManager* smgr, + MainMenuData* data, + bool& kill) : + m_device(dev), + m_parent(parent), + m_menumanager(menumgr), + m_smgr(smgr), + m_data(data), + m_texture_source(NULL), + m_sound_manager(NULL), + m_formspecgui(0), + m_buttonhandler(0), + m_menu(0), + m_kill(kill), + m_startgame(false), + m_script(0), + m_scriptdir(""), + m_irr_toplefttext(0), + m_clouds_enabled(true), + m_cloud() +{ + //initialize texture pointers + for (unsigned int i = 0; i < TEX_LAYER_MAX; i++) { + m_textures[i].texture = NULL; + } + // is deleted by guiformspec! + m_buttonhandler = new TextDestGuiEngine(this); + + //create texture source + m_texture_source = new MenuTextureSource(m_device->getVideoDriver()); + + //create soundmanager + MenuMusicFetcher soundfetcher; +#if USE_SOUND + m_sound_manager = createOpenALSoundManager(&soundfetcher); +#endif + if(!m_sound_manager) + m_sound_manager = &dummySoundManager; + + //create topleft header + std::wstring t = narrow_to_wide(std::string("Blokel ") + + minetest_version_hash); + + core::rect rect(0, 0, g_fontengine->getTextWidth(t), g_fontengine->getTextHeight()); + rect += v2s32(4, 0); + + m_irr_toplefttext = + m_device->getGUIEnvironment()->addStaticText(t.c_str(), + rect,false,true,0,-1); + + //create formspecsource + m_formspecgui = new FormspecFormSource(""); + + /* Create menu */ + m_menu = new GUIFormSpecMenu(m_device, + m_parent, + -1, + m_menumanager, + NULL /* &client */, + NULL /* gamedef */, + m_texture_source, + m_formspecgui, + m_buttonhandler, + NULL, + false); + + m_menu->allowClose(false); + m_menu->lockSize(true,v2u32(800,600)); + + // Initialize scripting + + infostream << "GUIEngine: Initializing Lua" << std::endl; + + m_script = new MainMenuScripting(this); + + try { + if (m_data->errormessage != "") { + m_script->setMainMenuErrorMessage(m_data->errormessage); + m_data->errormessage = ""; + } + + if (!loadMainMenuScript()) { + errorstream << "No future without mainmenu" << std::endl; + abort(); + } + + run(); + } + catch(LuaError &e) { + errorstream << "MAINMENU ERROR: " << e.what() << std::endl; + m_data->errormessage = e.what(); + } + + m_menu->quitMenu(); + m_menu->drop(); + m_menu = NULL; +} + +/******************************************************************************/ +bool GUIEngine::loadMainMenuScript() +{ + // Try custom menu script (main_menu_path) + + m_scriptdir = g_settings->get("main_menu_path"); + if (m_scriptdir.empty()) { + m_scriptdir = porting::path_share + DIR_DELIM "builtin" + DIR_DELIM "mainmenu"; + } + + std::string script = porting::path_share + DIR_DELIM "builtin" + DIR_DELIM "init.lua"; + if (m_script->loadScript(script)) { + // Menu script loaded + return true; + } else { + infostream + << "GUIEngine: execution of menu script in: \"" + << m_scriptdir << "\" failed!" << std::endl; + } + + return false; +} + +/******************************************************************************/ +void GUIEngine::run() +{ + // Always create clouds because they may or may not be + // needed based on the game selected + video::IVideoDriver* driver = m_device->getVideoDriver(); + + cloudInit(); + + unsigned int text_height = g_fontengine->getTextHeight(); + + while(m_device->run() && (!m_startgame) && (!m_kill)) + { + //check if we need to update the "upper left corner"-text + if (text_height != g_fontengine->getTextHeight()) { + updateTopLeftTextSize(); + text_height = g_fontengine->getTextHeight(); + } + + driver->beginScene(true, true, video::SColor(255,140,186,250)); + + if (m_clouds_enabled) + { + cloudPreProcess(); + drawOverlay(driver); + } + else + drawBackground(driver); + + drawHeader(driver); + drawFooter(driver); + + m_device->getGUIEnvironment()->drawAll(); + + driver->endScene(); + + if (m_clouds_enabled) + cloudPostProcess(); + else + sleep_ms(25); + + m_script->step(); + +#ifdef __ANDROID__ + m_menu->getAndroidUIInput(); +#endif + } +} + +/******************************************************************************/ +GUIEngine::~GUIEngine() +{ + video::IVideoDriver* driver = m_device->getVideoDriver(); + FATAL_ERROR_IF(driver == 0, "Could not get video driver"); + + if(m_sound_manager != &dummySoundManager){ + delete m_sound_manager; + m_sound_manager = NULL; + } + + infostream<<"GUIEngine: Deinitializing scripting"<setText(L""); + + //clean up texture pointers + for (unsigned int i = 0; i < TEX_LAYER_MAX; i++) { + if (m_textures[i].texture != NULL) + driver->removeTexture(m_textures[i].texture); + } + + delete m_texture_source; + + if (m_cloud.clouds) + m_cloud.clouds->drop(); +} + +/******************************************************************************/ +void GUIEngine::cloudInit() +{ + m_cloud.clouds = new Clouds(m_smgr->getRootSceneNode(), + m_smgr, -1, rand(), 100); + m_cloud.clouds->update(v2f(0, 0), video::SColor(255,200,200,255)); + + m_cloud.camera = m_smgr->addCameraSceneNode(0, + v3f(0,0,0), v3f(0, 60, 100)); + m_cloud.camera->setFarValue(10000); + + m_cloud.lasttime = m_device->getTimer()->getTime(); +} + +/******************************************************************************/ +void GUIEngine::cloudPreProcess() +{ + u32 time = m_device->getTimer()->getTime(); + + if(time > m_cloud.lasttime) + m_cloud.dtime = (time - m_cloud.lasttime) / 1000.0; + else + m_cloud.dtime = 0; + + m_cloud.lasttime = time; + + m_cloud.clouds->step(m_cloud.dtime*3); + m_cloud.clouds->render(); + m_smgr->drawAll(); +} + +/******************************************************************************/ +void GUIEngine::cloudPostProcess() +{ + float fps_max = g_settings->getFloat("fps_max"); + // Time of frame without fps limit + u32 busytime_u32; + + // not using getRealTime is necessary for wine + u32 time = m_device->getTimer()->getTime(); + if(time > m_cloud.lasttime) + busytime_u32 = time - m_cloud.lasttime; + else + busytime_u32 = 0; + + // FPS limiter + u32 frametime_min = 1000./fps_max; + + if(busytime_u32 < frametime_min) { + u32 sleeptime = frametime_min - busytime_u32; + m_device->sleep(sleeptime); + } +} + +/******************************************************************************/ +void GUIEngine::drawBackground(video::IVideoDriver* driver) +{ + v2u32 screensize = driver->getScreenSize(); + + video::ITexture* texture = m_textures[TEX_LAYER_BACKGROUND].texture; + + /* If no texture, draw background of solid color */ + if(!texture){ + video::SColor color(255,80,58,37); + core::rect rect(0, 0, screensize.X, screensize.Y); + driver->draw2DRectangle(color, rect, NULL); + return; + } + + v2u32 sourcesize = texture->getOriginalSize(); + + if (m_textures[TEX_LAYER_BACKGROUND].tile) + { + v2u32 tilesize( + MYMAX(sourcesize.X,m_textures[TEX_LAYER_BACKGROUND].minsize), + MYMAX(sourcesize.Y,m_textures[TEX_LAYER_BACKGROUND].minsize)); + for (unsigned int x = 0; x < screensize.X; x += tilesize.X ) + { + for (unsigned int y = 0; y < screensize.Y; y += tilesize.Y ) + { + driver->draw2DImage(texture, + core::rect(x, y, x+tilesize.X, y+tilesize.Y), + core::rect(0, 0, sourcesize.X, sourcesize.Y), + NULL, NULL, true); + } + } + return; + } + + /* Draw background texture */ + driver->draw2DImage(texture, + core::rect(0, 0, screensize.X, screensize.Y), + core::rect(0, 0, sourcesize.X, sourcesize.Y), + NULL, NULL, true); +} + +/******************************************************************************/ +void GUIEngine::drawOverlay(video::IVideoDriver* driver) +{ + v2u32 screensize = driver->getScreenSize(); + + video::ITexture* texture = m_textures[TEX_LAYER_OVERLAY].texture; + + /* If no texture, draw background of solid color */ + if(!texture) + return; + + /* Draw background texture */ + v2u32 sourcesize = texture->getOriginalSize(); + driver->draw2DImage(texture, + core::rect(0, 0, screensize.X, screensize.Y), + core::rect(0, 0, sourcesize.X, sourcesize.Y), + NULL, NULL, true); +} + +/******************************************************************************/ +void GUIEngine::drawHeader(video::IVideoDriver* driver) +{ + core::dimension2d screensize = driver->getScreenSize(); + + video::ITexture* texture = m_textures[TEX_LAYER_HEADER].texture; + + /* If no texture, draw nothing */ + if(!texture) + return; + + f32 mult = (((f32)screensize.Width / 2.0)) / + ((f32)texture->getOriginalSize().Width); + + v2s32 splashsize(((f32)texture->getOriginalSize().Width) * mult, + ((f32)texture->getOriginalSize().Height) * mult); + + // Don't draw the header is there isn't enough room + s32 free_space = (((s32)screensize.Height)-320)/2; + + if (free_space > splashsize.Y) { + core::rect splashrect(0, 0, splashsize.X, splashsize.Y); + splashrect += v2s32((screensize.Width/2)-(splashsize.X/2), + ((free_space/2)-splashsize.Y/2)+10); + + video::SColor bgcolor(255,50,50,50); + + driver->draw2DImage(texture, splashrect, + core::rect(core::position2d(0,0), + core::dimension2di(texture->getOriginalSize())), + NULL, NULL, true); + } +} + +/******************************************************************************/ +void GUIEngine::drawFooter(video::IVideoDriver* driver) +{ + core::dimension2d screensize = driver->getScreenSize(); + + video::ITexture* texture = m_textures[TEX_LAYER_FOOTER].texture; + + /* If no texture, draw nothing */ + if(!texture) + return; + + f32 mult = (((f32)screensize.Width)) / + ((f32)texture->getOriginalSize().Width); + + v2s32 footersize(((f32)texture->getOriginalSize().Width) * mult, + ((f32)texture->getOriginalSize().Height) * mult); + + // Don't draw the footer if there isn't enough room + s32 free_space = (((s32)screensize.Height)-320)/2; + + if (free_space > footersize.Y) { + core::rect rect(0,0,footersize.X,footersize.Y); + rect += v2s32(screensize.Width/2,screensize.Height-footersize.Y); + rect -= v2s32(footersize.X/2, 0); + + driver->draw2DImage(texture, rect, + core::rect(core::position2d(0,0), + core::dimension2di(texture->getOriginalSize())), + NULL, NULL, true); + } +} + +/******************************************************************************/ +bool GUIEngine::setTexture(texture_layer layer, std::string texturepath, + bool tile_image, unsigned int minsize) +{ + video::IVideoDriver* driver = m_device->getVideoDriver(); + FATAL_ERROR_IF(driver == 0, "Could not get video driver"); + + if (m_textures[layer].texture != NULL) + { + driver->removeTexture(m_textures[layer].texture); + m_textures[layer].texture = NULL; + } + + if ((texturepath == "") || !fs::PathExists(texturepath)) + { + return false; + } + + m_textures[layer].texture = driver->getTexture(texturepath.c_str()); + m_textures[layer].tile = tile_image; + m_textures[layer].minsize = minsize; + + if (m_textures[layer].texture == NULL) + { + return false; + } + + return true; +} + +/******************************************************************************/ +bool GUIEngine::downloadFile(std::string url, std::string target) +{ +#if USE_CURL + std::ofstream target_file(target.c_str(), std::ios::out | std::ios::binary); + + if (!target_file.good()) { + return false; + } + + HTTPFetchRequest fetch_request; + HTTPFetchResult fetch_result; + fetch_request.url = url; + fetch_request.caller = HTTPFETCH_SYNC; + fetch_request.timeout = g_settings->getS32("curl_file_download_timeout"); + httpfetch_sync(fetch_request, fetch_result); + + if (!fetch_result.succeeded) { + return false; + } + target_file << fetch_result.data; + + return true; +#else + return false; +#endif +} + +/******************************************************************************/ +void GUIEngine::setTopleftText(std::string append) +{ + std::wstring toset = narrow_to_wide( std::string("Blokel ") + + minetest_version_hash); + + if (append != "") + { + toset += L" / "; + toset += narrow_to_wide(append); + } + + m_irr_toplefttext->setText(toset.c_str()); + + updateTopLeftTextSize(); +} + +/******************************************************************************/ +void GUIEngine::updateTopLeftTextSize() +{ + std::wstring text = m_irr_toplefttext->getText(); + + core::rect rect(0, 0, g_fontengine->getTextWidth(text), g_fontengine->getTextHeight()); + rect += v2s32(4, 0); + + m_irr_toplefttext->remove(); + m_irr_toplefttext = + m_device->getGUIEnvironment()->addStaticText(text.c_str(), + rect,false,true,0,-1); +} + +/******************************************************************************/ +s32 GUIEngine::playSound(SimpleSoundSpec spec, bool looped) +{ + s32 handle = m_sound_manager->playSound(spec, looped); + return handle; +} + +/******************************************************************************/ +void GUIEngine::stopSound(s32 handle) +{ + m_sound_manager->stopSound(handle); +} + +/******************************************************************************/ +unsigned int GUIEngine::queueAsync(std::string serialized_func, + std::string serialized_params) +{ + return m_script->queueAsync(serialized_func, serialized_params); +} + diff --git a/src/guiEngine.h b/src/guiEngine.h new file mode 100644 index 0000000..e575732 --- /dev/null +++ b/src/guiEngine.h @@ -0,0 +1,313 @@ +/* +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. +*/ + +#ifndef GUI_ENGINE_H_ +#define GUI_ENGINE_H_ + +/******************************************************************************/ +/* Includes */ +/******************************************************************************/ +#include "irrlichttypes.h" +#include "modalMenu.h" +#include "guiFormSpecMenu.h" +#include "sound.h" +#include "client/tile.h" + +/******************************************************************************/ +/* Typedefs and macros */ +/******************************************************************************/ +/** texture layer ids */ +typedef enum { + TEX_LAYER_BACKGROUND = 0, + TEX_LAYER_OVERLAY, + TEX_LAYER_HEADER, + TEX_LAYER_FOOTER, + TEX_LAYER_MAX +} texture_layer; + +typedef struct { + video::ITexture* texture; + bool tile; + unsigned int minsize; +} image_definition; + +/******************************************************************************/ +/* forward declarations */ +/******************************************************************************/ +class GUIEngine; +class MainMenuScripting; +class Clouds; +struct MainMenuData; + +/******************************************************************************/ +/* declarations */ +/******************************************************************************/ + +/** GUIEngine specific implementation of TextDest used within guiFormSpecMenu */ +class TextDestGuiEngine : public TextDest +{ +public: + /** + * default constructor + * @param engine the engine data is transmitted for further processing + */ + TextDestGuiEngine(GUIEngine* engine); + + /** + * receive fields transmitted by guiFormSpecMenu + * @param fields map containing formspec field elements currently active + */ + void gotText(std::map fields); + + /** + * receive text/events transmitted by guiFormSpecMenu + * @param text textual representation of event + */ + void gotText(std::wstring text); + +private: + /** target to transmit data to */ + GUIEngine* m_engine; +}; + +/** GUIEngine specific implementation of ISimpleTextureSource */ +class MenuTextureSource : public ISimpleTextureSource +{ +public: + /** + * default constructor + * @param driver the video driver to load textures from + */ + MenuTextureSource(video::IVideoDriver *driver); + + /** + * destructor, removes all loaded textures + */ + virtual ~MenuTextureSource(); + + /** + * get a texture, loading it if required + * @param name path to the texture + * @param id receives the texture ID, always 0 in this implementation + */ + video::ITexture* getTexture(const std::string &name, u32 *id = NULL); + +private: + /** driver to get textures from */ + video::IVideoDriver *m_driver; + /** set of texture names to delete */ + std::set m_to_delete; +}; + +/** GUIEngine specific implementation of OnDemandSoundFetcher */ +class MenuMusicFetcher: public OnDemandSoundFetcher +{ +public: + /** + * get sound file paths according to sound name + * @param name sound name + * @param dst_paths receives possible paths to sound files + * @param dst_datas receives binary sound data (not used here) + */ + void fetchSounds(const std::string &name, + std::set &dst_paths, + std::set &dst_datas); + +private: + /** set of fetched sound names */ + std::set m_fetched; +}; + +/** implementation of main menu based uppon formspecs */ +class GUIEngine { + /** grant ModApiMainMenu access to private members */ + friend class ModApiMainMenu; + +public: + /** + * default constructor + * @param dev device to draw at + * @param parent parent gui element + * @param menumgr manager to add menus to + * @param smgr scene manager to add scene elements to + * @param data struct to transfer data to main game handling + */ + GUIEngine( irr::IrrlichtDevice* dev, + gui::IGUIElement* parent, + IMenuManager *menumgr, + scene::ISceneManager* smgr, + MainMenuData* data, + bool& kill); + + /** default destructor */ + virtual ~GUIEngine(); + + /** + * return MainMenuScripting interface + */ + MainMenuScripting* getScriptIface() + { + return m_script; + } + + /** + * return dir of current menuscript + */ + std::string getScriptDir() + { + return m_scriptdir; + } + + /** pass async callback to scriptengine **/ + unsigned int queueAsync(std::string serialized_fct,std::string serialized_params); + +private: + + /** find and run the main menu script */ + bool loadMainMenuScript(); + + /** run main menu loop */ + void run(); + + /** handler to limit frame rate within main menu */ + void limitFrameRate(); + + /** update size of topleftext element */ + void updateTopLeftTextSize(); + + /** device to draw at */ + irr::IrrlichtDevice* m_device; + /** parent gui element */ + gui::IGUIElement* m_parent; + /** manager to add menus to */ + IMenuManager* m_menumanager; + /** scene manager to add scene elements to */ + scene::ISceneManager* m_smgr; + /** pointer to data beeing transfered back to main game handling */ + MainMenuData* m_data; + /** pointer to texture source */ + ISimpleTextureSource* m_texture_source; + /** pointer to soundmanager*/ + ISoundManager* m_sound_manager; + + /** representation of form source to be used in mainmenu formspec */ + FormspecFormSource* m_formspecgui; + /** formspec input receiver */ + TextDestGuiEngine* m_buttonhandler; + /** the formspec menu */ + GUIFormSpecMenu* m_menu; + + /** reference to kill variable managed by SIGINT handler */ + bool& m_kill; + + /** variable used to abort menu and return back to main game handling */ + bool m_startgame; + + /** scripting interface */ + MainMenuScripting* m_script; + + /** script basefolder */ + std::string m_scriptdir; + + /** + * draw background layer + * @param driver to use for drawing + */ + void drawBackground(video::IVideoDriver* driver); + /** + * draw overlay layer + * @param driver to use for drawing + */ + void drawOverlay(video::IVideoDriver* driver); + /** + * draw header layer + * @param driver to use for drawing + */ + void drawHeader(video::IVideoDriver* driver); + /** + * draw footer layer + * @param driver to use for drawing + */ + void drawFooter(video::IVideoDriver* driver); + + /** + * load a texture for a specified layer + * @param layer draw layer to specify texture + * @param texturepath full path of texture to load + */ + bool setTexture(texture_layer layer, std::string texturepath, + bool tile_image, unsigned int minsize); + + /** + * download a file using curl + * @param url url to download + * @param target file to store to + */ + static bool downloadFile(std::string url,std::string target); + + /** array containing pointers to current specified texture layers */ + image_definition m_textures[TEX_LAYER_MAX]; + + /** draw version string in topleft corner */ + void drawVersion(); + + /** + * specify text to be appended to version string + * @param text to set + */ + void setTopleftText(std::string append); + + /** pointer to gui element shown at topleft corner */ + irr::gui::IGUIStaticText* m_irr_toplefttext; + + /** initialize cloud subsystem */ + void cloudInit(); + /** do preprocessing for cloud subsystem */ + void cloudPreProcess(); + /** do postprocessing for cloud subsystem */ + void cloudPostProcess(); + + /** internam data required for drawing clouds */ + struct clouddata { + /** delta time since last cloud processing */ + f32 dtime; + /** absolute time of last cloud processing */ + u32 lasttime; + /** pointer to cloud class */ + Clouds* clouds; + /** camera required for drawing clouds */ + scene::ICameraSceneNode* camera; + }; + + /** is drawing of clouds enabled atm */ + bool m_clouds_enabled; + /** data used to draw clouds */ + clouddata m_cloud; + + /** start playing a sound and return handle */ + s32 playSound(SimpleSoundSpec spec, bool looped); + /** stop playing a sound started with playSound() */ + void stopSound(s32 handle); + + +}; + + + +#endif /* GUI_ENGINE_H_ */ diff --git a/src/guiFileSelectMenu.cpp b/src/guiFileSelectMenu.cpp new file mode 100644 index 0000000..e98b025 --- /dev/null +++ b/src/guiFileSelectMenu.cpp @@ -0,0 +1,125 @@ +/* + 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. + */ + +#include "guiFileSelectMenu.h" +#include "util/string.h" +#include + +GUIFileSelectMenu::GUIFileSelectMenu(gui::IGUIEnvironment* env, + gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, + std::string title, std::string formname) : +GUIModalMenu(env, parent, id, menumgr) +{ + m_title = narrow_to_wide(title); + m_parent = parent; + m_formname = formname; + m_text_dst = 0; + m_accepted = false; +} + +GUIFileSelectMenu::~GUIFileSelectMenu() +{ + removeChildren(); +} + +void GUIFileSelectMenu::removeChildren() +{ + const core::list &children = getChildren(); + core::list children_copy; + for (core::list::ConstIterator i = children.begin(); i + != children.end(); i++) + { + children_copy.push_back(*i); + } + for (core::list::Iterator i = children_copy.begin(); i + != children_copy.end(); i++) + { + (*i)->remove(); + } +} + +void GUIFileSelectMenu::regenerateGui(v2u32 screensize) +{ + removeChildren(); + m_fileOpenDialog = 0; + + core::dimension2du size(600,400); + core::rect < s32 > rect(0,0,screensize.X,screensize.Y); + + DesiredRect = rect; + recalculateAbsolutePosition(false); + + m_fileOpenDialog = + Environment->addFileOpenDialog(m_title.c_str(),false,this,-1); + + core::position2di pos = core::position2di(screensize.X/2 - size.Width/2,screensize.Y/2 -size.Height/2); + m_fileOpenDialog->setRelativePosition(pos); + m_fileOpenDialog->setMinSize(size); +} + +void GUIFileSelectMenu::drawMenu() +{ + gui::IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + + gui::IGUIElement::draw(); +} + +void GUIFileSelectMenu::acceptInput() { + if ((m_text_dst != 0) && (this->m_formname != "")){ + std::map fields; + + if (m_accepted) + fields[m_formname + "_accepted"] = wide_to_narrow(m_fileOpenDialog->getFileName()); + else + fields[m_formname + "_canceled"] = m_formname; + + this->m_text_dst->gotText(fields); + } +} + +bool GUIFileSelectMenu::OnEvent(const SEvent& event) +{ + + if (event.EventType == irr::EET_GUI_EVENT) { + switch (event.GUIEvent.EventType) { + case gui::EGET_ELEMENT_CLOSED: + case gui::EGET_FILE_CHOOSE_DIALOG_CANCELLED: + m_accepted=false; + acceptInput(); + quitMenu(); + return true; + break; + + case gui::EGET_DIRECTORY_SELECTED: + case gui::EGET_FILE_SELECTED: + m_accepted=true; + acceptInput(); + quitMenu(); + return true; + break; + + default: + //ignore this event + break; + } + } + return Parent ? Parent->OnEvent(event) : false; +} diff --git a/src/guiFileSelectMenu.h b/src/guiFileSelectMenu.h new file mode 100644 index 0000000..e37d3d8 --- /dev/null +++ b/src/guiFileSelectMenu.h @@ -0,0 +1,78 @@ +/* + 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. + */ + +#ifndef GUIFILESELECTMENU_H_ +#define GUIFILESELECTMENU_H_ + +#include + +#include "modalMenu.h" +#include "IGUIFileOpenDialog.h" +#include "guiFormSpecMenu.h" //required because of TextDest only !!! + + +class GUIFileSelectMenu: public GUIModalMenu +{ +public: + GUIFileSelectMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, + s32 id, IMenuManager *menumgr, + std::string title, + std::string formid); + ~GUIFileSelectMenu(); + + void removeChildren(); + + /* + Remove and re-add (or reposition) stuff + */ + void regenerateGui(v2u32 screensize); + + void drawMenu(); + + bool OnEvent(const SEvent& event); + + bool isRunning() { + return m_running; + } + + void setTextDest(TextDest * dest) { + m_text_dst = dest; + } + +private: + void acceptInput(); + + std::wstring m_title; + bool m_accepted; + gui::IGUIElement* m_parent; + + std::string m_selectedPath; + + gui::IGUIFileOpenDialog* m_fileOpenDialog; + + bool m_running; + + TextDest *m_text_dst; + + std::string m_formname; +}; + + + +#endif /* GUIFILESELECTMENU_H_ */ diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp new file mode 100644 index 0000000..11360a0 --- /dev/null +++ b/src/guiFormSpecMenu.cpp @@ -0,0 +1,3509 @@ +/* +Minetest +Copyright (C) 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. +*/ + + +#include +#include +#include +#include +#include +#include "guiFormSpecMenu.h" +#include "guiTable.h" +#include "constants.h" +#include "gamedef.h" +#include "keycode.h" +#include "strfnd.h" +#include +#include +#include +#include +#include +#include +#include +#include "log.h" +#include "client/tile.h" // ITextureSource +#include "hud.h" // drawItemStack +#include "filesys.h" +#include "gettime.h" +#include "gettext.h" +#include "scripting_game.h" +#include "porting.h" +#include "main.h" +#include "settings.h" +#include "client.h" +#include "fontengine.h" +#include "util/hex.h" +#include "util/numeric.h" +#include "util/string.h" // for parseColorString() + +#define MY_CHECKPOS(a,b) \ + if (v_pos.size() != 2) { \ + errorstream<< "Invalid pos for element " << a << "specified: \"" \ + << parts[b] << "\"" << std::endl; \ + return; \ + } + +#define MY_CHECKGEOM(a,b) \ + if (v_geom.size() != 2) { \ + errorstream<< "Invalid pos for element " << a << "specified: \"" \ + << parts[b] << "\"" << std::endl; \ + return; \ + } +/* + GUIFormSpecMenu +*/ +static unsigned int font_line_height(gui::IGUIFont *font) +{ + return font->getDimension(L"Ay").Height + font->getKerningHeight(); +} + +GUIFormSpecMenu::GUIFormSpecMenu(irr::IrrlichtDevice* dev, + gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, + InventoryManager *invmgr, IGameDef *gamedef, + ISimpleTextureSource *tsrc, IFormSource* fsrc, TextDest* tdst, + Client* client, bool remap_dbl_click) : + GUIModalMenu(dev->getGUIEnvironment(), parent, id, menumgr), + m_device(dev), + m_invmgr(invmgr), + m_gamedef(gamedef), + m_tsrc(tsrc), + m_client(client), + m_selected_item(NULL), + m_selected_amount(0), + m_selected_dragging(false), + m_tooltip_element(NULL), + m_hovered_time(0), + m_old_tooltip_id(-1), + m_rmouse_auto_place(false), + m_allowclose(true), + m_lock(false), + m_form_src(fsrc), + m_text_dst(tdst), + m_formspec_version(0), + m_focused_element(L""), + m_font(NULL), + m_remap_dbl_click(remap_dbl_click) +#ifdef __ANDROID__ + ,m_JavaDialogFieldName(L"") +#endif +{ + current_keys_pending.key_down = false; + current_keys_pending.key_up = false; + current_keys_pending.key_enter = false; + current_keys_pending.key_escape = false; + + m_doubleclickdetect[0].time = 0; + m_doubleclickdetect[1].time = 0; + + m_doubleclickdetect[0].pos = v2s32(0, 0); + m_doubleclickdetect[1].pos = v2s32(0, 0); + + m_tooltip_show_delay = (u32)g_settings->getS32("tooltip_show_delay"); +} + +GUIFormSpecMenu::~GUIFormSpecMenu() +{ + removeChildren(); + + for (u32 i = 0; i < m_tables.size(); ++i) { + GUITable *table = m_tables[i].second; + table->drop(); + } + + delete m_selected_item; + + if (m_form_src != NULL) { + delete m_form_src; + } + if (m_text_dst != NULL) { + delete m_text_dst; + } +} + +void GUIFormSpecMenu::removeChildren() +{ + const core::list &children = getChildren(); + + while(!children.empty()) { + (*children.getLast())->remove(); + } + + if(m_tooltip_element) { + m_tooltip_element->remove(); + m_tooltip_element->drop(); + m_tooltip_element = NULL; + } + +} + +void GUIFormSpecMenu::setInitialFocus() +{ + // Set initial focus according to following order of precedence: + // 1. first empty editbox + // 2. first editbox + // 3. first table + // 4. last button + // 5. first focusable (not statictext, not tabheader) + // 6. first child element + + core::list children = getChildren(); + + // in case "children" contains any NULL elements, remove them + for (core::list::Iterator it = children.begin(); + it != children.end();) { + if (*it) + ++it; + else + it = children.erase(it); + } + + // 1. first empty editbox + for (core::list::Iterator it = children.begin(); + it != children.end(); ++it) { + if ((*it)->getType() == gui::EGUIET_EDIT_BOX + && (*it)->getText()[0] == 0) { + Environment->setFocus(*it); + return; + } + } + + // 2. first editbox + for (core::list::Iterator it = children.begin(); + it != children.end(); ++it) { + if ((*it)->getType() == gui::EGUIET_EDIT_BOX) { + Environment->setFocus(*it); + return; + } + } + + // 3. first table + for (core::list::Iterator it = children.begin(); + it != children.end(); ++it) { + if ((*it)->getTypeName() == std::string("GUITable")) { + Environment->setFocus(*it); + return; + } + } + + // 4. last button + for (core::list::Iterator it = children.getLast(); + it != children.end(); --it) { + if ((*it)->getType() == gui::EGUIET_BUTTON) { + Environment->setFocus(*it); + return; + } + } + + // 5. first focusable (not statictext, not tabheader) + for (core::list::Iterator it = children.begin(); + it != children.end(); ++it) { + if ((*it)->getType() != gui::EGUIET_STATIC_TEXT && + (*it)->getType() != gui::EGUIET_TAB_CONTROL) { + Environment->setFocus(*it); + return; + } + } + + // 6. first child element + if (children.empty()) + Environment->setFocus(this); + else + Environment->setFocus(*(children.begin())); +} + +GUITable* GUIFormSpecMenu::getTable(std::wstring tablename) +{ + for (u32 i = 0; i < m_tables.size(); ++i) { + if (tablename == m_tables[i].first.fname) + return m_tables[i].second; + } + return 0; +} + +std::vector split(const std::string &s, char delim) { + std::vector tokens; + + std::string current = ""; + bool last_was_escape = false; + for(unsigned int i=0; i < s.size(); i++) { + if (last_was_escape) { + current += '\\'; + current += s.c_str()[i]; + last_was_escape = false; + } + else { + if (s.c_str()[i] == delim) { + tokens.push_back(current); + current = ""; + last_was_escape = false; + } + else if (s.c_str()[i] == '\\'){ + last_was_escape = true; + } + else { + current += s.c_str()[i]; + last_was_escape = false; + } + } + } + //push last element + tokens.push_back(current); + + return tokens; +} + +void GUIFormSpecMenu::parseSize(parserData* data,std::string element) +{ + std::vector parts = split(element,','); + + if (((parts.size() == 2) || parts.size() == 3) || + ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + if (parts[1].find(';') != std::string::npos) + parts[1] = parts[1].substr(0,parts[1].find(';')); + + data->invsize.X = MYMAX(0, stof(parts[0])); + data->invsize.Y = MYMAX(0, stof(parts[1])); + + lockSize(false); + if (parts.size() == 3) { + if (parts[2] == "true") { + lockSize(true,v2u32(800,600)); + } + } + + data->explicit_size = true; + return; + } + errorstream<< "Invalid size element (" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseList(parserData* data,std::string element) +{ + if (m_gamedef == 0) { + errorstream<<"WARNING: invalid use of 'list' with m_gamedef==0"< parts = split(element,';'); + + if (((parts.size() == 4) || (parts.size() == 5)) || + ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + std::string location = parts[0]; + std::string listname = parts[1]; + std::vector v_pos = split(parts[2],','); + std::vector v_geom = split(parts[3],','); + std::string startindex = ""; + if (parts.size() == 5) + startindex = parts[4]; + + MY_CHECKPOS("list",2); + MY_CHECKGEOM("list",3); + + InventoryLocation loc; + + if(location == "context" || location == "current_name") + loc = m_current_inventory_location; + else + loc.deSerialize(location); + + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + + v2s32 geom; + geom.X = stoi(v_geom[0]); + geom.Y = stoi(v_geom[1]); + + s32 start_i = 0; + if(startindex != "") + start_i = stoi(startindex); + + if (geom.X < 0 || geom.Y < 0 || start_i < 0) { + errorstream<< "Invalid list element: '" << element << "'" << std::endl; + return; + } + + if(!data->explicit_size) + errorstream<<"WARNING: invalid use of list without a size[] element"< parts = split(element,';'); + + if (((parts.size() >= 3) && (parts.size() <= 4)) || + ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + std::vector v_pos = split(parts[0],','); + std::string name = parts[1]; + std::string label = parts[2]; + std::string selected = ""; + + if (parts.size() >= 4) + selected = parts[3]; + + MY_CHECKPOS("checkbox",0); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float) spacing.X; + pos.Y += stof(v_pos[1]) * (float) spacing.Y; + + bool fselected = false; + + if (selected == "true") + fselected = true; + + std::wstring wlabel = narrow_to_wide(label); + + core::rect rect = core::rect( + pos.X, pos.Y + ((imgsize.Y/2) - m_btn_height), + pos.X + m_font->getDimension(wlabel.c_str()).Width + 25, // text size + size of checkbox + pos.Y + ((imgsize.Y/2) + m_btn_height)); + + FieldSpec spec( + narrow_to_wide(name), + wlabel, //Needed for displaying text on MSVC + wlabel, + 258+m_fields.size() + ); + + spec.ftype = f_CheckBox; + + gui::IGUICheckBox* e = Environment->addCheckBox(fselected, rect, this, + spec.fid, spec.flabel.c_str()); + + if (spec.fname == data->focused_fieldname) { + Environment->setFocus(e); + } + + m_checkboxes.push_back(std::pair(spec,e)); + m_fields.push_back(spec); + return; + } + errorstream<< "Invalid checkbox element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseScrollBar(parserData* data, std::string element) +{ + std::vector parts = split(element,';'); + + if (parts.size() >= 5) { + std::vector v_pos = split(parts[0],','); + std::vector v_dim = split(parts[1],','); + std::string name = parts[2]; + std::string value = parts[4]; + + MY_CHECKPOS("scrollbar",0); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float) spacing.X; + pos.Y += stof(v_pos[1]) * (float) spacing.Y; + + if (v_dim.size() != 2) { + errorstream<< "Invalid size for element " << "scrollbar" + << "specified: \"" << parts[1] << "\"" << std::endl; + return; + } + + v2s32 dim; + dim.X = stof(v_dim[0]) * (float) spacing.X; + dim.Y = stof(v_dim[1]) * (float) spacing.Y; + + core::rect rect = + core::rect(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y); + + FieldSpec spec( + narrow_to_wide(name), + L"", + L"", + 258+m_fields.size() + ); + + bool is_horizontal = true; + + if (parts[2] == "vertical") + is_horizontal = false; + + spec.ftype = f_ScrollBar; + spec.send = true; + gui::IGUIScrollBar* e = + Environment->addScrollBar(is_horizontal,rect,this,spec.fid); + + e->setMax(1000); + e->setMin(0); + e->setPos(stoi(parts[4])); + e->setSmallStep(10); + e->setLargeStep(100); + + m_scrollbars.push_back(std::pair(spec,e)); + m_fields.push_back(spec); + return; + } + errorstream<< "Invalid scrollbar element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseImage(parserData* data,std::string element) +{ + std::vector parts = split(element,';'); + + if ((parts.size() == 3) || + ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string name = unescape_string(parts[2]); + + MY_CHECKPOS("image",0); + MY_CHECKGEOM("image",1); + + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + pos.X += stof(v_pos[0]) * (float) spacing.X; + pos.Y += stof(v_pos[1]) * (float) spacing.Y; + + v2s32 geom; + geom.X = stof(v_geom[0]) * (float)imgsize.X; + geom.Y = stof(v_geom[1]) * (float)imgsize.Y; + + if(!data->explicit_size) + errorstream<<"WARNING: invalid use of image without a size[] element"< v_pos = split(parts[0],','); + std::string name = unescape_string(parts[1]); + + MY_CHECKPOS("image",0); + + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + pos.X += stof(v_pos[0]) * (float) spacing.X; + pos.Y += stof(v_pos[1]) * (float) spacing.Y; + + if(!data->explicit_size) + errorstream<<"WARNING: invalid use of image without a size[] element"< parts = split(element,';'); + + if ((parts.size() == 3) || + ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string name = parts[2]; + + MY_CHECKPOS("itemimage",0); + MY_CHECKGEOM("itemimage",1); + + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + pos.X += stof(v_pos[0]) * (float) spacing.X; + pos.Y += stof(v_pos[1]) * (float) spacing.Y; + + v2s32 geom; + geom.X = stof(v_geom[0]) * (float)imgsize.X; + geom.Y = stof(v_geom[1]) * (float)imgsize.Y; + + if(!data->explicit_size) + errorstream<<"WARNING: invalid use of item_image without a size[] element"< parts = split(element,';'); + + if ((parts.size() == 4) || + ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string name = parts[2]; + std::string label = parts[3]; + + MY_CHECKPOS("button",0); + MY_CHECKGEOM("button",1); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + + v2s32 geom; + geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X); + pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; + + core::rect rect = + core::rect(pos.X, pos.Y - m_btn_height, + pos.X + geom.X, pos.Y + m_btn_height); + + if(!data->explicit_size) + errorstream<<"WARNING: invalid use of button without a size[] element"<addButton(rect, this, spec.fid, + spec.flabel.c_str()); + + if (spec.fname == data->focused_fieldname) { + Environment->setFocus(e); + } + + m_fields.push_back(spec); + return; + } + errorstream<< "Invalid button element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseBackground(parserData* data,std::string element) +{ + std::vector parts = split(element,';'); + + if (((parts.size() == 3) || (parts.size() == 4)) || + ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string name = unescape_string(parts[2]); + + MY_CHECKPOS("background",0); + MY_CHECKGEOM("background",1); + + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + pos.X += stof(v_pos[0]) * (float)spacing.X - ((float)spacing.X-(float)imgsize.X)/2; + pos.Y += stof(v_pos[1]) * (float)spacing.Y - ((float)spacing.Y-(float)imgsize.Y)/2; + + v2s32 geom; + geom.X = stof(v_geom[0]) * (float)spacing.X; + geom.Y = stof(v_geom[1]) * (float)spacing.Y; + + if (parts.size() == 4) { + m_clipbackground = is_yes(parts[3]); + if (m_clipbackground) { + pos.X = stoi(v_pos[0]); //acts as offset + pos.Y = stoi(v_pos[1]); //acts as offset + } + } + + if(!data->explicit_size) + errorstream<<"WARNING: invalid use of background without a size[] element"< parts = split(element,';'); + + data->table_options.clear(); + for (size_t i = 0; i < parts.size(); ++i) { + // Parse table option + std::string opt = unescape_string(parts[i]); + data->table_options.push_back(GUITable::splitOption(opt)); + } +} + +void GUIFormSpecMenu::parseTableColumns(parserData* data,std::string element) +{ + std::vector parts = split(element,';'); + + data->table_columns.clear(); + for (size_t i = 0; i < parts.size(); ++i) { + std::vector col_parts = split(parts[i],','); + GUITable::TableColumn column; + // Parse column type + if (!col_parts.empty()) + column.type = col_parts[0]; + // Parse column options + for (size_t j = 1; j < col_parts.size(); ++j) { + std::string opt = unescape_string(col_parts[j]); + column.options.push_back(GUITable::splitOption(opt)); + } + data->table_columns.push_back(column); + } +} + +void GUIFormSpecMenu::parseTable(parserData* data,std::string element) +{ + std::vector parts = split(element,';'); + + if (((parts.size() == 4) || (parts.size() == 5)) || + ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string name = parts[2]; + std::vector items = split(parts[3],','); + std::string str_initial_selection = ""; + std::string str_transparent = "false"; + + if (parts.size() >= 5) + str_initial_selection = parts[4]; + + MY_CHECKPOS("table",0); + MY_CHECKGEOM("table",1); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + + v2s32 geom; + geom.X = stof(v_geom[0]) * (float)spacing.X; + geom.Y = stof(v_geom[1]) * (float)spacing.Y; + + + core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + + std::wstring fname_w = narrow_to_wide(name); + + FieldSpec spec( + fname_w, + L"", + L"", + 258+m_fields.size() + ); + + spec.ftype = f_Table; + + for (unsigned int i = 0; i < items.size(); ++i) { + items[i] = unescape_string(items[i]); + } + + //now really show table + GUITable *e = new GUITable(Environment, this, spec.fid, rect, + m_tsrc); + + if (spec.fname == data->focused_fieldname) { + Environment->setFocus(e); + } + + e->setTable(data->table_options, data->table_columns, items); + + if (data->table_dyndata.find(fname_w) != data->table_dyndata.end()) { + e->setDynamicData(data->table_dyndata[fname_w]); + } + + if ((str_initial_selection != "") && + (str_initial_selection != "0")) + e->setSelected(stoi(str_initial_selection.c_str())); + + m_tables.push_back(std::pair(spec, e)); + m_fields.push_back(spec); + return; + } + errorstream<< "Invalid table element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseTextList(parserData* data,std::string element) +{ + std::vector parts = split(element,';'); + + if (((parts.size() == 4) || (parts.size() == 5) || (parts.size() == 6)) || + ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string name = parts[2]; + std::vector items = split(parts[3],','); + std::string str_initial_selection = ""; + std::string str_transparent = "false"; + + if (parts.size() >= 5) + str_initial_selection = parts[4]; + + if (parts.size() >= 6) + str_transparent = parts[5]; + + MY_CHECKPOS("textlist",0); + MY_CHECKGEOM("textlist",1); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + + v2s32 geom; + geom.X = stof(v_geom[0]) * (float)spacing.X; + geom.Y = stof(v_geom[1]) * (float)spacing.Y; + + + core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + + std::wstring fname_w = narrow_to_wide(name); + + FieldSpec spec( + fname_w, + L"", + L"", + 258+m_fields.size() + ); + + spec.ftype = f_Table; + + for (unsigned int i = 0; i < items.size(); ++i) { + items[i] = unescape_string(items[i]); + } + + //now really show list + GUITable *e = new GUITable(Environment, this, spec.fid, rect, + m_tsrc); + + if (spec.fname == data->focused_fieldname) { + Environment->setFocus(e); + } + + e->setTextList(items, is_yes(str_transparent)); + + if (data->table_dyndata.find(fname_w) != data->table_dyndata.end()) { + e->setDynamicData(data->table_dyndata[fname_w]); + } + + if ((str_initial_selection != "") && + (str_initial_selection != "0")) + e->setSelected(stoi(str_initial_selection.c_str())); + + m_tables.push_back(std::pair(spec, e)); + m_fields.push_back(spec); + return; + } + errorstream<< "Invalid textlist element(" << parts.size() << "): '" << element << "'" << std::endl; +} + + +void GUIFormSpecMenu::parseDropDown(parserData* data,std::string element) +{ + std::vector parts = split(element,';'); + + if ((parts.size() == 5) || + ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + std::vector v_pos = split(parts[0],','); + std::string name = parts[2]; + std::vector items = split(parts[3],','); + std::string str_initial_selection = ""; + str_initial_selection = parts[4]; + + MY_CHECKPOS("dropdown",0); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + + s32 width = stof(parts[1]) * (float)spacing.Y; + + core::rect rect = core::rect(pos.X, pos.Y, + pos.X + width, pos.Y + (m_btn_height * 2)); + + std::wstring fname_w = narrow_to_wide(name); + + FieldSpec spec( + fname_w, + L"", + L"", + 258+m_fields.size() + ); + + spec.ftype = f_DropDown; + spec.send = true; + + //now really show list + gui::IGUIComboBox *e = Environment->addComboBox(rect, this,spec.fid); + + if (spec.fname == data->focused_fieldname) { + Environment->setFocus(e); + } + + for (unsigned int i=0; i < items.size(); i++) { + e->addItem(narrow_to_wide(items[i]).c_str()); + } + + if (str_initial_selection != "") + e->setSelected(stoi(str_initial_selection.c_str())-1); + + m_fields.push_back(spec); + return; + } + errorstream << "Invalid dropdown element(" << parts.size() << "): '" + << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parsePwdField(parserData* data,std::string element) +{ + std::vector parts = split(element,';'); + + if ((parts.size() == 4) || + ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string name = parts[2]; + std::string label = parts[3]; + + MY_CHECKPOS("pwdfield",0); + MY_CHECKGEOM("pwdfield",1); + + v2s32 pos; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + + v2s32 geom; + geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X); + + pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; + pos.Y -= m_btn_height; + geom.Y = m_btn_height*2; + + core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + + label = unescape_string(label); + + std::wstring wlabel = narrow_to_wide(label); + + FieldSpec spec( + narrow_to_wide(name), + wlabel, + L"", + 258+m_fields.size() + ); + + spec.send = true; + gui::IGUIEditBox * e = Environment->addEditBox(0, rect, true, this, spec.fid); + + if (spec.fname == data->focused_fieldname) { + Environment->setFocus(e); + } + + if (label.length() >= 1) + { + int font_height = g_fontengine->getTextHeight(); + rect.UpperLeftCorner.Y -= font_height; + rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; + Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0); + } + + e->setPasswordBox(true,L'*'); + + irr::SEvent evt; + evt.EventType = EET_KEY_INPUT_EVENT; + evt.KeyInput.Key = KEY_END; + evt.KeyInput.Char = 0; + evt.KeyInput.Control = 0; + evt.KeyInput.Shift = 0; + evt.KeyInput.PressedDown = true; + e->OnEvent(evt); + m_fields.push_back(spec); + return; + } + errorstream<< "Invalid pwdfield element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseSimpleField(parserData* data, + std::vector &parts) +{ + std::string name = parts[0]; + std::string label = parts[1]; + std::string default_val = parts[2]; + + core::rect rect; + + if(data->explicit_size) + errorstream<<"WARNING: invalid use of unpositioned \"field\" in inventory"<(size.X / 2 - 150, pos.Y, + (size.X / 2 - 150) + 300, pos.Y + (m_btn_height*2)); + + + if(m_form_src) + default_val = m_form_src->resolveText(default_val); + + default_val = unescape_string(default_val); + label = unescape_string(label); + + std::wstring wlabel = narrow_to_wide(label); + + FieldSpec spec( + narrow_to_wide(name), + wlabel, + narrow_to_wide(default_val), + 258+m_fields.size() + ); + + if (name == "") + { + // spec field id to 0, this stops submit searching for a value that isn't there + Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid); + } + else + { + spec.send = true; + gui::IGUIEditBox *e = + Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid); + + if (spec.fname == data->focused_fieldname) { + Environment->setFocus(e); + } + + irr::SEvent evt; + evt.EventType = EET_KEY_INPUT_EVENT; + evt.KeyInput.Key = KEY_END; + evt.KeyInput.Char = 0; + evt.KeyInput.Control = 0; + evt.KeyInput.Shift = 0; + evt.KeyInput.PressedDown = true; + e->OnEvent(evt); + + if (label.length() >= 1) + { + int font_height = g_fontengine->getTextHeight(); + rect.UpperLeftCorner.Y -= font_height; + rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; + Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0); + } + } + + m_fields.push_back(spec); +} + +void GUIFormSpecMenu::parseTextArea(parserData* data, + std::vector& parts,std::string type) +{ + + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string name = parts[2]; + std::string label = parts[3]; + std::string default_val = parts[4]; + + MY_CHECKPOS(type,0); + MY_CHECKGEOM(type,1); + + v2s32 pos; + pos.X = stof(v_pos[0]) * (float) spacing.X; + pos.Y = stof(v_pos[1]) * (float) spacing.Y; + + v2s32 geom; + + geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X); + + if (type == "textarea") + { + geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y-imgsize.Y); + pos.Y += m_btn_height; + } + else + { + pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2; + pos.Y -= m_btn_height; + geom.Y = m_btn_height*2; + } + + core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + + if(!data->explicit_size) + errorstream<<"WARNING: invalid use of positioned "<resolveText(default_val); + + + default_val = unescape_string(default_val); + label = unescape_string(label); + + std::wstring wlabel = narrow_to_wide(label); + + FieldSpec spec( + narrow_to_wide(name), + wlabel, + narrow_to_wide(default_val), + 258+m_fields.size() + ); + + if (name == "") + { + // spec field id to 0, this stops submit searching for a value that isn't there + Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid); + } + else + { + spec.send = true; + gui::IGUIEditBox *e = + Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid); + + if (spec.fname == data->focused_fieldname) { + Environment->setFocus(e); + } + + if (type == "textarea") + { + e->setMultiLine(true); + e->setWordWrap(true); + e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT); + } else { + irr::SEvent evt; + evt.EventType = EET_KEY_INPUT_EVENT; + evt.KeyInput.Key = KEY_END; + evt.KeyInput.Char = 0; + evt.KeyInput.Control = 0; + evt.KeyInput.Shift = 0; + evt.KeyInput.PressedDown = true; + e->OnEvent(evt); + } + + if (label.length() >= 1) + { + int font_height = g_fontengine->getTextHeight(); + rect.UpperLeftCorner.Y -= font_height; + rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height; + Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0); + } + } + m_fields.push_back(spec); +} + +void GUIFormSpecMenu::parseField(parserData* data,std::string element, + std::string type) +{ + std::vector parts = split(element,';'); + + if (parts.size() == 3 || parts.size() == 4) { + parseSimpleField(data,parts); + return; + } + + if ((parts.size() == 5) || + ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + parseTextArea(data,parts,type); + return; + } + errorstream<< "Invalid field element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseLabel(parserData* data,std::string element) +{ + std::vector parts = split(element,';'); + + if ((parts.size() == 2) || + ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + std::vector v_pos = split(parts[0],','); + std::string text = parts[1]; + + MY_CHECKPOS("label",0); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += (stof(v_pos[1]) + 7.0/30.0) * (float)spacing.Y; + + if(!data->explicit_size) + errorstream<<"WARNING: invalid use of label without a size[] element"< lines = split(text, '\n'); + + for (unsigned int i = 0; i != lines.size(); i++) { + // Lines are spaced at the nominal distance of + // 2/5 inventory slot, even if the font doesn't + // quite match that. This provides consistent + // form layout, at the expense of sometimes + // having sub-optimal spacing for the font. + // We multiply by 2 and then divide by 5, rather + // than multiply by 0.4, to get exact results + // in the integer cases: 0.4 is not exactly + // representable in binary floating point. + s32 posy = pos.Y + ((float)i) * spacing.Y * 2.0 / 5.0; + std::wstring wlabel = narrow_to_wide(lines[i]); + core::rect rect = core::rect( + pos.X, posy - m_btn_height, + pos.X + m_font->getDimension(wlabel.c_str()).Width, + posy + m_btn_height); + FieldSpec spec( + L"", + wlabel, + L"", + 258+m_fields.size() + ); + gui::IGUIStaticText *e = + Environment->addStaticText(spec.flabel.c_str(), + rect, false, false, this, spec.fid); + e->setTextAlignment(gui::EGUIA_UPPERLEFT, + gui::EGUIA_CENTER); + m_fields.push_back(spec); + } + + return; + } + errorstream<< "Invalid label element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseVertLabel(parserData* data,std::string element) +{ + std::vector parts = split(element,';'); + + if ((parts.size() == 2) || + ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + std::vector v_pos = split(parts[0],','); + std::wstring text = narrow_to_wide(unescape_string(parts[1])); + + MY_CHECKPOS("vertlabel",1); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + + core::rect rect = core::rect( + pos.X, pos.Y+((imgsize.Y/2)- m_btn_height), + pos.X+15, pos.Y + + font_line_height(m_font) + * (text.length()+1) + +((imgsize.Y/2)- m_btn_height)); + //actually text.length() would be correct but adding +1 avoids to break all mods + + if(!data->explicit_size) + errorstream<<"WARNING: invalid use of label without a size[] element"<addStaticText(spec.flabel.c_str(), rect, false, false, this, spec.fid); + t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); + m_fields.push_back(spec); + return; + } + errorstream<< "Invalid vertlabel element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseImageButton(parserData* data,std::string element, + std::string type) +{ + std::vector parts = split(element,';'); + + if ((((parts.size() >= 5) && (parts.size() <= 8)) && (parts.size() != 6)) || + ((parts.size() > 8) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string image_name = parts[2]; + std::string name = parts[3]; + std::string label = parts[4]; + + MY_CHECKPOS("imagebutton",0); + MY_CHECKGEOM("imagebutton",1); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + v2s32 geom; + geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X); + geom.Y = (stof(v_geom[1]) * (float)spacing.Y)-(spacing.Y-imgsize.Y); + + bool noclip = false; + bool drawborder = true; + std::string pressed_image_name = ""; + + if (parts.size() >= 7) { + if (parts[5] == "true") + noclip = true; + if (parts[6] == "false") + drawborder = false; + } + + if (parts.size() >= 8) { + pressed_image_name = parts[7]; + } + + core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + + if(!data->explicit_size) + errorstream<<"WARNING: invalid use of image_button without a size[] element"<getTexture(image_name); + if (pressed_image_name != "") + pressed_texture = m_tsrc->getTexture(pressed_image_name); + else + pressed_texture = texture; + + gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str()); + + if (spec.fname == data->focused_fieldname) { + Environment->setFocus(e); + } + + e->setUseAlphaChannel(true); + e->setImage(texture); + e->setPressedImage(pressed_texture); + e->setScaleImage(true); + e->setNotClipped(noclip); + e->setDrawBorder(drawborder); + + m_fields.push_back(spec); + return; + } + + errorstream<< "Invalid imagebutton element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseTabHeader(parserData* data,std::string element) +{ + std::vector parts = split(element,';'); + + if (((parts.size() == 4) || (parts.size() == 6)) || + ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + std::vector v_pos = split(parts[0],','); + std::string name = parts[1]; + std::vector buttons = split(parts[2],','); + std::string str_index = parts[3]; + bool show_background = true; + bool show_border = true; + int tab_index = stoi(str_index) -1; + + MY_CHECKPOS("tabheader",0); + + if (parts.size() == 6) { + if (parts[4] == "true") + show_background = false; + if (parts[5] == "false") + show_border = false; + } + + FieldSpec spec( + narrow_to_wide(name), + L"", + L"", + 258+m_fields.size() + ); + + spec.ftype = f_TabHeader; + + v2s32 pos(0,0); + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y - m_btn_height * 2; + v2s32 geom; + geom.X = DesiredRect.getWidth(); + geom.Y = m_btn_height*2; + + core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, + pos.Y+geom.Y); + + gui::IGUITabControl *e = Environment->addTabControl(rect, this, + show_background, show_border, spec.fid); + e->setAlignment(irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT, + irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT); + e->setTabHeight(m_btn_height*2); + + if (spec.fname == data->focused_fieldname) { + Environment->setFocus(e); + } + + e->setNotClipped(true); + + for (unsigned int i=0; i< buttons.size(); i++) { + e->addTab(narrow_to_wide(buttons[i]).c_str(), -1); + } + + if ((tab_index >= 0) && + (buttons.size() < INT_MAX) && + (tab_index < (int) buttons.size())) + e->setActiveTab(tab_index); + + m_fields.push_back(spec); + return; + } + errorstream << "Invalid TabHeader element(" << parts.size() << "): '" + << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseItemImageButton(parserData* data,std::string element) +{ + + if (m_gamedef == 0) { + errorstream << + "WARNING: invalid use of item_image_button with m_gamedef==0" + << std::endl; + return; + } + + std::vector parts = split(element,';'); + + if ((parts.size() == 5) || + ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + std::string item_name = parts[2]; + std::string name = parts[3]; + std::string label = parts[4]; + + MY_CHECKPOS("itemimagebutton",0); + MY_CHECKGEOM("itemimagebutton",1); + + v2s32 pos = padding; + pos.X += stof(v_pos[0]) * (float)spacing.X; + pos.Y += stof(v_pos[1]) * (float)spacing.Y; + v2s32 geom; + geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X); + geom.Y = (stof(v_geom[1]) * (float)spacing.Y)-(spacing.Y-imgsize.Y); + + core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + + if(!data->explicit_size) + errorstream<<"WARNING: invalid use of item_image_button without a size[] element"<idef(); + ItemStack item; + item.deSerialize(item_name, idef); + video::ITexture *texture = idef->getInventoryTexture(item.getDefinition(idef).name, m_gamedef); + + m_tooltips[narrow_to_wide(name)] = + TooltipSpec(item.getDefinition(idef).description, + m_default_tooltip_bgcolor, + m_default_tooltip_color); + + label = unescape_string(label); + FieldSpec spec( + narrow_to_wide(name), + narrow_to_wide(label), + narrow_to_wide(item_name), + 258+m_fields.size() + ); + + gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str()); + + if (spec.fname == data->focused_fieldname) { + Environment->setFocus(e); + } + + e->setUseAlphaChannel(true); + e->setImage(texture); + e->setPressedImage(texture); + e->setScaleImage(true); + spec.ftype = f_Button; + rect+=data->basepos-padding; + spec.rect=rect; + m_fields.push_back(spec); + return; + } + errorstream<< "Invalid ItemImagebutton element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseBox(parserData* data,std::string element) +{ + std::vector parts = split(element,';'); + + if ((parts.size() == 3) || + ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + std::vector v_pos = split(parts[0],','); + std::vector v_geom = split(parts[1],','); + + MY_CHECKPOS("box",0); + MY_CHECKGEOM("box",1); + + v2s32 pos = padding + AbsoluteRect.UpperLeftCorner; + pos.X += stof(v_pos[0]) * (float) spacing.X; + pos.Y += stof(v_pos[1]) * (float) spacing.Y; + + v2s32 geom; + geom.X = stof(v_geom[0]) * (float)spacing.X; + geom.Y = stof(v_geom[1]) * (float)spacing.Y; + + video::SColor tmp_color; + + if (parseColorString(parts[2], tmp_color, false)) { + BoxDrawSpec spec(pos, geom, tmp_color); + + m_boxes.push_back(spec); + } + else { + errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "' INVALID COLOR" << std::endl; + } + return; + } + errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseBackgroundColor(parserData* data,std::string element) +{ + std::vector parts = split(element,';'); + + if (((parts.size() == 1) || (parts.size() == 2)) || + ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + parseColorString(parts[0],m_bgcolor,false); + + if (parts.size() == 2) { + std::string fullscreen = parts[1]; + m_bgfullscreen = is_yes(fullscreen); + } + return; + } + errorstream<< "Invalid bgcolor element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseListColors(parserData* data,std::string element) +{ + std::vector parts = split(element,';'); + + if (((parts.size() == 2) || (parts.size() == 3) || (parts.size() == 5)) || + ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION))) + { + parseColorString(parts[0], m_slotbg_n, false); + parseColorString(parts[1], m_slotbg_h, false); + + if (parts.size() >= 3) { + if (parseColorString(parts[2], m_slotbordercolor, false)) { + m_slotborder = true; + } + } + if (parts.size() == 5) { + video::SColor tmp_color; + + if (parseColorString(parts[3], tmp_color, false)) + m_default_tooltip_bgcolor = tmp_color; + if (parseColorString(parts[4], tmp_color, false)) + m_default_tooltip_color = tmp_color; + } + return; + } + errorstream<< "Invalid listcolors element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +void GUIFormSpecMenu::parseTooltip(parserData* data, std::string element) +{ + std::vector parts = split(element,';'); + if (parts.size() == 2) { + std::string name = parts[0]; + m_tooltips[narrow_to_wide(name)] = TooltipSpec(unescape_string(parts[1]), + m_default_tooltip_bgcolor, m_default_tooltip_color); + return; + } else if (parts.size() == 4) { + std::string name = parts[0]; + video::SColor tmp_color1, tmp_color2; + if ( parseColorString(parts[2], tmp_color1, false) && parseColorString(parts[3], tmp_color2, false) ) { + m_tooltips[narrow_to_wide(name)] = TooltipSpec(unescape_string(parts[1]), + tmp_color1, tmp_color2); + return; + } + } + errorstream<< "Invalid tooltip element(" << parts.size() << "): '" << element << "'" << std::endl; +} + +bool GUIFormSpecMenu::parseVersionDirect(std::string data) +{ + //some prechecks + if (data == "") + return false; + + std::vector parts = split(data,'['); + + if (parts.size() < 2) { + return false; + } + + if (parts[0] != "formspec_version") { + return false; + } + + if (is_number(parts[1])) { + m_formspec_version = mystoi(parts[1]); + return true; + } + + return false; +} + +bool GUIFormSpecMenu::parseSizeDirect(parserData* data, std::string element) +{ + if (element == "") + return false; + + std::vector parts = split(element,'['); + + if (parts.size() < 2) + return false; + + std::string type = trim(parts[0]); + std::string description = trim(parts[1]); + + if (type != "size" && type != "invsize") + return false; + + if (type == "invsize") + log_deprecated("Deprecated formspec element \"invsize\" is used"); + + parseSize(data, description); + + return true; +} + +void GUIFormSpecMenu::parseElement(parserData* data, std::string element) +{ + //some prechecks + if (element == "") + return; + + std::vector parts = split(element,'['); + + // ugly workaround to keep compatibility + if (parts.size() > 2) { + if (trim(parts[0]) == "image") { + for (unsigned int i=2;i< parts.size(); i++) { + parts[1] += "[" + parts[i]; + } + } + else { return; } + } + + if (parts.size() < 2) { + return; + } + + std::string type = trim(parts[0]); + std::string description = trim(parts[1]); + + if (type == "list") { + parseList(data,description); + return; + } + + if (type == "checkbox") { + parseCheckbox(data,description); + return; + } + + if (type == "image") { + parseImage(data,description); + return; + } + + if (type == "item_image") { + parseItemImage(data,description); + return; + } + + if ((type == "button") || (type == "button_exit")) { + parseButton(data,description,type); + return; + } + + if (type == "background") { + parseBackground(data,description); + return; + } + + if (type == "tableoptions"){ + parseTableOptions(data,description); + return; + } + + if (type == "tablecolumns"){ + parseTableColumns(data,description); + return; + } + + if (type == "table"){ + parseTable(data,description); + return; + } + + if (type == "textlist"){ + parseTextList(data,description); + return; + } + + if (type == "dropdown"){ + parseDropDown(data,description); + return; + } + + if (type == "pwdfield") { + parsePwdField(data,description); + return; + } + + if ((type == "field") || (type == "textarea")){ + parseField(data,description,type); + return; + } + + if (type == "label") { + parseLabel(data,description); + return; + } + + if (type == "vertlabel") { + parseVertLabel(data,description); + return; + } + + if (type == "item_image_button") { + parseItemImageButton(data,description); + return; + } + + if ((type == "image_button") || (type == "image_button_exit")) { + parseImageButton(data,description,type); + return; + } + + if (type == "tabheader") { + parseTabHeader(data,description); + return; + } + + if (type == "box") { + parseBox(data,description); + return; + } + + if (type == "bgcolor") { + parseBackgroundColor(data,description); + return; + } + + if (type == "listcolors") { + parseListColors(data,description); + return; + } + + if (type == "tooltip") { + parseTooltip(data,description); + return; + } + + if (type == "scrollbar") { + parseScrollBar(data, description); + return; + } + + // Ignore others + infostream + << "Unknown DrawSpec: type="<getDynamicData(); + } + + //set focus + if (!m_focused_element.empty()) + mydata.focused_fieldname = m_focused_element; + + //preserve focus + gui::IGUIElement *focused_element = Environment->getFocus(); + if (focused_element && focused_element->getParent() == this) { + s32 focused_id = focused_element->getID(); + if (focused_id > 257) { + for (u32 i=0; idrop(); + } + + mydata.size= v2s32(100,100); + mydata.screensize = screensize; + + // Base position of contents of form + mydata.basepos = getBasePos(); + + /* Convert m_init_draw_spec to m_inventorylists */ + + m_inventorylists.clear(); + m_images.clear(); + m_backgrounds.clear(); + m_itemimages.clear(); + m_tables.clear(); + m_checkboxes.clear(); + m_scrollbars.clear(); + m_fields.clear(); + m_boxes.clear(); + m_tooltips.clear(); + + // Set default values (fits old formspec values) + m_bgcolor = video::SColor(140,0,0,0); + m_bgfullscreen = false; + + m_slotbg_n = video::SColor(255,128,128,128); + m_slotbg_h = video::SColor(255,192,192,192); + + m_default_tooltip_bgcolor = video::SColor(255,110,130,60); + m_default_tooltip_color = video::SColor(255,255,255,255); + + m_slotbordercolor = video::SColor(200,0,0,0); + m_slotborder = false; + + m_clipbackground = false; + // Add tooltip + { + assert(m_tooltip_element == NULL); + // Note: parent != this so that the tooltip isn't clipped by the menu rectangle + m_tooltip_element = Environment->addStaticText(L"",core::rect(0,0,110,18)); + m_tooltip_element->enableOverrideColor(true); + m_tooltip_element->setBackgroundColor(m_default_tooltip_bgcolor); + m_tooltip_element->setDrawBackground(true); + m_tooltip_element->setDrawBorder(true); + m_tooltip_element->setOverrideColor(m_default_tooltip_color); + m_tooltip_element->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); + m_tooltip_element->setWordWrap(false); + //we're not parent so no autograb for this one! + m_tooltip_element->grab(); + } + + std::vector elements = split(m_formspec_string,']'); + unsigned int i = 0; + + /* try to read version from first element only */ + if (elements.size() >= 1) { + if ( parseVersionDirect(elements[0]) ) { + i++; + } + } + + /* we need size first in order to calculate image scale */ + mydata.explicit_size = false; + for (; i< elements.size(); i++) { + if (!parseSizeDirect(&mydata, elements[i])) { + break; + } + } + + if (mydata.explicit_size) { + // compute scaling for specified form size + if (m_lock) { + v2u32 current_screensize = m_device->getVideoDriver()->getScreenSize(); + v2u32 delta = current_screensize - m_lockscreensize; + + if (current_screensize.Y > m_lockscreensize.Y) + delta.Y /= 2; + else + delta.Y = 0; + + if (current_screensize.X > m_lockscreensize.X) + delta.X /= 2; + else + delta.X = 0; + + offset = v2s32(delta.X,delta.Y); + + mydata.screensize = m_lockscreensize; + } else { + offset = v2s32(0,0); + } + + double gui_scaling = g_settings->getFloat("gui_scaling"); + double screen_dpi = porting::getDisplayDensity() * 96; + + double use_imgsize; + if (m_lock) { + // In fixed-size mode, inventory image size + // is 0.53 inch multiplied by the gui_scaling + // config parameter. This magic size is chosen + // to make the main menu (15.5 inventory images + // wide, including border) just fit into the + // default window (800 pixels wide) at 96 DPI + // and default scaling (1.00). + use_imgsize = 0.5555 * screen_dpi * gui_scaling; + } else { + // In variable-size mode, we prefer to make the + // inventory image size 1/15 of screen height, + // multiplied by the gui_scaling config parameter. + // If the preferred size won't fit the whole + // form on the screen, either horizontally or + // vertically, then we scale it down to fit. + // (The magic numbers in the computation of what + // fits arise from the scaling factors in the + // following stanza, including the form border, + // help text space, and 0.1 inventory slot spare.) + // However, a minimum size is also set, that + // the image size can't be less than 0.3 inch + // multiplied by gui_scaling, even if this means + // the form doesn't fit the screen. + double prefer_imgsize = mydata.screensize.Y / 15 * + gui_scaling; + double fitx_imgsize = mydata.screensize.X / + ((5.0/4.0) * (0.5 + mydata.invsize.X)); + double fity_imgsize = mydata.screensize.Y / + ((15.0/13.0) * (0.85 * mydata.invsize.Y)); + double screen_dpi = porting::getDisplayDensity() * 96; + double min_imgsize = 0.3 * screen_dpi * gui_scaling; + use_imgsize = MYMAX(min_imgsize, MYMIN(prefer_imgsize, + MYMIN(fitx_imgsize, fity_imgsize))); + } + + // Everything else is scaled in proportion to the + // inventory image size. The inventory slot spacing + // is 5/4 image size horizontally and 15/13 image size + // vertically. The padding around the form (incorporating + // the border of the outer inventory slots) is 3/8 + // image size. Font height (baseline to baseline) + // is 2/5 vertical inventory slot spacing, and button + // half-height is 7/8 of font height. + imgsize = v2s32(use_imgsize, use_imgsize); + spacing = v2s32(use_imgsize*5.0/4, use_imgsize*15.0/13); + padding = v2s32(use_imgsize*3.0/8, use_imgsize*3.0/8); + m_btn_height = use_imgsize*15.0/13 * 0.35; + + m_font = g_fontengine->getFont(); + + mydata.size = v2s32( + padding.X*2+spacing.X*(mydata.invsize.X-1.0)+imgsize.X, + padding.Y*2+spacing.Y*(mydata.invsize.Y-1.0)+imgsize.Y + m_btn_height*2.0/3.0 + ); + DesiredRect = mydata.rect = core::rect( + mydata.screensize.X/2 - mydata.size.X/2 + offset.X, + mydata.screensize.Y/2 - mydata.size.Y/2 + offset.Y, + mydata.screensize.X/2 + mydata.size.X/2 + offset.X, + mydata.screensize.Y/2 + mydata.size.Y/2 + offset.Y + ); + } else { + // Non-size[] form must consist only of text fields and + // implicit "Proceed" button. Use default font, and + // temporary form size which will be recalculated below. + m_font = g_fontengine->getFont(); + m_btn_height = font_line_height(m_font) * 0.875; + DesiredRect = core::rect( + mydata.screensize.X/2 - 580/2, + mydata.screensize.Y/2 - 300/2, + mydata.screensize.X/2 + 580/2, + mydata.screensize.Y/2 + 300/2 + ); + } + recalculateAbsolutePosition(false); + mydata.basepos = getBasePos(); + m_tooltip_element->setOverrideFont(m_font); + + gui::IGUISkin* skin = Environment->getSkin(); + sanity_check(skin != NULL); + gui::IGUIFont *old_font = skin->getFont(); + skin->setFont(m_font); + + for (; i< elements.size(); i++) { + parseElement(&mydata, elements[i]); + } + + // If there are fields without explicit size[], add a "Proceed" + // button and adjust size to fit all the fields. + if (m_fields.size() && !mydata.explicit_size) { + mydata.rect = core::rect( + mydata.screensize.X/2 - 580/2, + mydata.screensize.Y/2 - 300/2, + mydata.screensize.X/2 + 580/2, + mydata.screensize.Y/2 + 240/2+(m_fields.size()*60) + ); + DesiredRect = mydata.rect; + recalculateAbsolutePosition(false); + mydata.basepos = getBasePos(); + + { + v2s32 pos = mydata.basepos; + pos.Y = ((m_fields.size()+2)*60); + + v2s32 size = DesiredRect.getSize(); + mydata.rect = + core::rect(size.X/2-70, pos.Y, + (size.X/2-70)+140, pos.Y + (m_btn_height*2)); + const wchar_t *text = wgettext("Proceed"); + Environment->addButton(mydata.rect, this, 257, text); + delete[] text; + } + + } + + //set initial focus if parser didn't set it + focused_element = Environment->getFocus(); + if (!focused_element + || !isMyChild(focused_element) + || focused_element->getType() == gui::EGUIET_TAB_CONTROL) + setInitialFocus(); + + skin->setFont(old_font); +} + +#ifdef __ANDROID__ +bool GUIFormSpecMenu::getAndroidUIInput() +{ + /* no dialog shown */ + if (m_JavaDialogFieldName == L"") { + return false; + } + + /* still waiting */ + if (porting::getInputDialogState() == -1) { + return true; + } + + std::wstring fieldname = m_JavaDialogFieldName; + m_JavaDialogFieldName = L""; + + /* no value abort dialog processing */ + if (porting::getInputDialogState() != 0) { + return false; + } + + for(std::vector::iterator iter = m_fields.begin(); + iter != m_fields.end(); iter++) { + + if (iter->fname != fieldname) { + continue; + } + IGUIElement* tochange = getElementFromId(iter->fid); + + if (tochange == 0) { + return false; + } + + if (tochange->getType() != irr::gui::EGUIET_EDIT_BOX) { + return false; + } + + std::string text = porting::getInputDialogValue(); + + ((gui::IGUIEditBox*) tochange)-> + setText(narrow_to_wide(text).c_str()); + } + return false; +} +#endif + +GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const +{ + core::rect imgrect(0,0,imgsize.X,imgsize.Y); + + for(u32 i=0; i rect = imgrect + s.pos + p0; + if(rect.isPointInside(p)) + { + return ItemSpec(s.inventoryloc, s.listname, item_i); + } + } + } + + return ItemSpec(InventoryLocation(), "", -1); +} + +void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase) +{ + video::IVideoDriver* driver = Environment->getVideoDriver(); + + Inventory *inv = m_invmgr->getInventory(s.inventoryloc); + if(!inv){ + infostream<<"GUIFormSpecMenu::drawList(): WARNING: " + <<"The inventory location " + <<"\""<getList(s.listname); + if(!ilist){ + infostream<<"GUIFormSpecMenu::drawList(): WARNING: " + <<"The inventory list \""< imgrect(0,0,imgsize.X,imgsize.Y); + + for(s32 i=0; i= (s32) ilist->getSize()) + break; + s32 x = (i%s.geom.X) * spacing.X; + s32 y = (i/s.geom.X) * spacing.Y; + v2s32 p(x,y); + core::rect rect = imgrect + s.pos + p; + ItemStack item; + if(ilist) + item = ilist->getItem(item_i); + + bool selected = m_selected_item + && m_invmgr->getInventory(m_selected_item->inventoryloc) == inv + && m_selected_item->listname == s.listname + && m_selected_item->i == item_i; + bool hovering = rect.isPointInside(m_pointer); + + if(phase == 0) + { + if(hovering) + driver->draw2DRectangle(m_slotbg_h, rect, &AbsoluteClippingRect); + else + driver->draw2DRectangle(m_slotbg_n, rect, &AbsoluteClippingRect); + } + + //Draw inv slot borders + if (m_slotborder) { + s32 x1 = rect.UpperLeftCorner.X; + s32 y1 = rect.UpperLeftCorner.Y; + s32 x2 = rect.LowerRightCorner.X; + s32 y2 = rect.LowerRightCorner.Y; + s32 border = 1; + driver->draw2DRectangle(m_slotbordercolor, + core::rect(v2s32(x1 - border, y1 - border), + v2s32(x2 + border, y1)), NULL); + driver->draw2DRectangle(m_slotbordercolor, + core::rect(v2s32(x1 - border, y2), + v2s32(x2 + border, y2 + border)), NULL); + driver->draw2DRectangle(m_slotbordercolor, + core::rect(v2s32(x1 - border, y1), + v2s32(x1, y2)), NULL); + driver->draw2DRectangle(m_slotbordercolor, + core::rect(v2s32(x2, y1), + v2s32(x2 + border, y2)), NULL); + } + + if(phase == 1) + { + // Draw item stack + if(selected) + { + item.takeItem(m_selected_amount); + } + if(!item.empty()) + { + drawItemStack(driver, m_font, item, + rect, &AbsoluteClippingRect, m_gamedef); + } + + // Draw tooltip + std::string tooltip_text = ""; + if (hovering && !m_selected_item) + tooltip_text = item.getDefinition(m_gamedef->idef()).description; + if (tooltip_text != "") { + std::vector tt_rows = str_split(tooltip_text, '\n'); + m_tooltip_element->setBackgroundColor(m_default_tooltip_bgcolor); + m_tooltip_element->setOverrideColor(m_default_tooltip_color); + m_tooltip_element->setVisible(true); + this->bringToFront(m_tooltip_element); + m_tooltip_element->setText(narrow_to_wide(tooltip_text).c_str()); + s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height; + s32 tooltip_height = m_tooltip_element->getTextHeight() * tt_rows.size() + 5; + v2u32 screenSize = driver->getScreenSize(); + int tooltip_offset_x = m_btn_height; + int tooltip_offset_y = m_btn_height; +#ifdef __ANDROID__ + tooltip_offset_x *= 3; + tooltip_offset_y = 0; + if (m_pointer.X > (s32)screenSize.X / 2) + tooltip_offset_x = (tooltip_offset_x + tooltip_width) * -1; +#endif + s32 tooltip_x = m_pointer.X + tooltip_offset_x; + s32 tooltip_y = m_pointer.Y + tooltip_offset_y; + if (tooltip_x + tooltip_width > (s32)screenSize.X) + tooltip_x = (s32)screenSize.X - tooltip_width - m_btn_height; + if (tooltip_y + tooltip_height > (s32)screenSize.Y) + tooltip_y = (s32)screenSize.Y - tooltip_height - m_btn_height; + m_tooltip_element->setRelativePosition(core::rect( + core::position2d(tooltip_x, tooltip_y), + core::dimension2d(tooltip_width, tooltip_height))); + } + } + } +} + +void GUIFormSpecMenu::drawSelectedItem() +{ + if(!m_selected_item) + return; + + video::IVideoDriver* driver = Environment->getVideoDriver(); + + Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc); + sanity_check(inv); + InventoryList *list = inv->getList(m_selected_item->listname); + sanity_check(list); + ItemStack stack = list->getItem(m_selected_item->i); + stack.count = m_selected_amount; + + core::rect imgrect(0,0,imgsize.X,imgsize.Y); + core::rect rect = imgrect + (m_pointer - imgrect.getCenter()); + drawItemStack(driver, m_font, stack, rect, NULL, m_gamedef); +} + +void GUIFormSpecMenu::drawMenu() +{ + if(m_form_src){ + std::string newform = m_form_src->getForm(); + if(newform != m_formspec_string){ + m_formspec_string = newform; + regenerateGui(m_screensize_old); + } + } + + gui::IGUISkin* skin = Environment->getSkin(); + sanity_check(skin != NULL); + gui::IGUIFont *old_font = skin->getFont(); + skin->setFont(m_font); + + updateSelectedItem(); + + video::IVideoDriver* driver = Environment->getVideoDriver(); + + v2u32 screenSize = driver->getScreenSize(); + core::rect allbg(0, 0, screenSize.X , screenSize.Y); + if (m_bgfullscreen) + driver->draw2DRectangle(m_bgcolor, allbg, &allbg); + else + driver->draw2DRectangle(m_bgcolor, AbsoluteRect, &AbsoluteClippingRect); + + m_tooltip_element->setVisible(false); + + /* + Draw backgrounds + */ + for(u32 i=0; igetTexture(spec.name); + + if (texture != 0) { + // Image size on screen + core::rect imgrect(0, 0, spec.geom.X, spec.geom.Y); + // Image rectangle on screen + core::rect rect = imgrect + spec.pos; + + if (m_clipbackground) { + core::dimension2d absrec_size = AbsoluteRect.getSize(); + rect = core::rect(AbsoluteRect.UpperLeftCorner.X - spec.pos.X, + AbsoluteRect.UpperLeftCorner.Y - spec.pos.Y, + AbsoluteRect.UpperLeftCorner.X + absrec_size.Width + spec.pos.X, + AbsoluteRect.UpperLeftCorner.Y + absrec_size.Height + spec.pos.Y); + } + + const video::SColor color(255,255,255,255); + const video::SColor colors[] = {color,color,color,color}; + driver->draw2DImage(texture, rect, + core::rect(core::position2d(0,0), + core::dimension2di(texture->getOriginalSize())), + NULL/*&AbsoluteClippingRect*/, colors, true); + } + else { + errorstream << "GUIFormSpecMenu::drawMenu() Draw backgrounds unable to load texture:" << std::endl; + errorstream << "\t" << spec.name << std::endl; + } + } + + /* + Draw Boxes + */ + for(u32 i=0; i rect(spec.pos.X,spec.pos.Y, + spec.pos.X + spec.geom.X,spec.pos.Y + spec.geom.Y); + + driver->draw2DRectangle(todraw, rect, 0); + } + /* + Draw images + */ + for(u32 i=0; igetTexture(spec.name); + + if (texture != 0) { + const core::dimension2d& img_origsize = texture->getOriginalSize(); + // Image size on screen + core::rect imgrect; + + if (spec.scale) + imgrect = core::rect(0,0,spec.geom.X, spec.geom.Y); + else { + + imgrect = core::rect(0,0,img_origsize.Width,img_origsize.Height); + } + // Image rectangle on screen + core::rect rect = imgrect + spec.pos; + const video::SColor color(255,255,255,255); + const video::SColor colors[] = {color,color,color,color}; + driver->draw2DImage(texture, rect, + core::rect(core::position2d(0,0),img_origsize), + NULL/*&AbsoluteClippingRect*/, colors, true); + } + else { + errorstream << "GUIFormSpecMenu::drawMenu() Draw images unable to load texture:" << std::endl; + errorstream << "\t" << spec.name << std::endl; + } + } + + /* + Draw item images + */ + for(u32 i=0; iidef(); + ItemStack item; + item.deSerialize(spec.name, idef); + video::ITexture *texture = idef->getInventoryTexture(item.getDefinition(idef).name, m_gamedef); + // Image size on screen + core::rect imgrect(0, 0, spec.geom.X, spec.geom.Y); + // Image rectangle on screen + core::rect rect = imgrect + spec.pos; + const video::SColor color(255,255,255,255); + const video::SColor colors[] = {color,color,color,color}; + driver->draw2DImage(texture, rect, + core::rect(core::position2d(0,0), + core::dimension2di(texture->getOriginalSize())), + NULL/*&AbsoluteClippingRect*/, colors, true); + } + + /* + Draw items + Phase 0: Item slot rectangles + Phase 1: Item images; prepare tooltip + */ + int start_phase=0; + for(int phase=start_phase; phase<=1; phase++) + for(u32 i=0; igetCursorControl()->getPosition(); +#endif + + /* + Draw fields/buttons tooltips + */ + gui::IGUIElement *hovered = + Environment->getRootGUIElement()->getElementFromPoint(m_pointer); + + if (hovered != NULL) { + s32 id = hovered->getID(); + + u32 delta = 0; + if (id == -1) { + m_old_tooltip_id = id; + m_old_tooltip = ""; + } else { + if (id == m_old_tooltip_id) { + delta = porting::getDeltaMs(m_hovered_time, getTimeMs()); + } else { + m_hovered_time = getTimeMs(); + m_old_tooltip_id = id; + } + } + + if (id != -1 && delta >= m_tooltip_show_delay) { + for(std::vector::iterator iter = m_fields.begin(); + iter != m_fields.end(); iter++) { + if ( (iter->fid == id) && (m_tooltips[iter->fname].tooltip != "") ){ + if (m_old_tooltip != m_tooltips[iter->fname].tooltip) { + m_old_tooltip = m_tooltips[iter->fname].tooltip; + m_tooltip_element->setText(narrow_to_wide(m_tooltips[iter->fname].tooltip).c_str()); + std::vector tt_rows = str_split(m_tooltips[iter->fname].tooltip, '\n'); + s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height; + s32 tooltip_height = m_tooltip_element->getTextHeight() * tt_rows.size() + 5; + int tooltip_offset_x = m_btn_height; + int tooltip_offset_y = m_btn_height; +#ifdef __ANDROID__ + tooltip_offset_x *= 3; + tooltip_offset_y = 0; + if (m_pointer.X > (s32)screenSize.X / 2) + tooltip_offset_x = (tooltip_offset_x + tooltip_width) * -1; +#endif + s32 tooltip_x = m_pointer.X + tooltip_offset_x; + s32 tooltip_y = m_pointer.Y + tooltip_offset_y; + if (tooltip_x + tooltip_width > (s32)screenSize.X) + tooltip_x = (s32)screenSize.X - tooltip_width - m_btn_height; + if (tooltip_y + tooltip_height > (s32)screenSize.Y) + tooltip_y = (s32)screenSize.Y - tooltip_height - m_btn_height; + m_tooltip_element->setRelativePosition(core::rect( + core::position2d(tooltip_x, tooltip_y), + core::dimension2d(tooltip_width, tooltip_height))); + } + m_tooltip_element->setBackgroundColor(m_tooltips[iter->fname].bgcolor); + m_tooltip_element->setOverrideColor(m_tooltips[iter->fname].color); + m_tooltip_element->setVisible(true); + this->bringToFront(m_tooltip_element); + break; + } + } + } + } + + /* + Draw dragged item stack + */ + drawSelectedItem(); + + skin->setFont(old_font); +} + +void GUIFormSpecMenu::updateSelectedItem() +{ + // If the selected stack has become empty for some reason, deselect it. + // If the selected stack has become inaccessible, deselect it. + // If the selected stack has become smaller, adjust m_selected_amount. + ItemStack selected = verifySelectedItem(); + + // WARNING: BLACK MAGIC + // See if there is a stack suited for our current guess. + // If such stack does not exist, clear the guess. + if(m_selected_content_guess.name != "" && + selected.name == m_selected_content_guess.name && + selected.count == m_selected_content_guess.count){ + // Selected item fits the guess. Skip the black magic. + } + else if(m_selected_content_guess.name != ""){ + bool found = false; + for(u32 i=0; igetInventory(s.inventoryloc); + if(!inv) + continue; + InventoryList *list = inv->getList(s.listname); + if(!list) + continue; + for(s32 i=0; i= list->getSize()) + continue; + ItemStack stack = list->getItem(item_i); + if(stack.name == m_selected_content_guess.name && + stack.count == m_selected_content_guess.count){ + found = true; + infostream<<"Client: Changing selected content guess to " + <getInventory(s.inventoryloc); + InventoryList *list = inv->getList("craftresult"); + if(list && list->getSize() >= 1 && !list->getItem(0).empty()) + { + m_selected_item = new ItemSpec; + m_selected_item->inventoryloc = s.inventoryloc; + m_selected_item->listname = "craftresult"; + m_selected_item->i = 0; + m_selected_amount = 0; + m_selected_dragging = false; + break; + } + } + } + } + + // If craftresult is selected, keep the whole stack selected + if(m_selected_item && m_selected_item->listname == "craftresult") + { + m_selected_amount = verifySelectedItem().count; + } +} + +ItemStack GUIFormSpecMenu::verifySelectedItem() +{ + // If the selected stack has become empty for some reason, deselect it. + // If the selected stack has become inaccessible, deselect it. + // If the selected stack has become smaller, adjust m_selected_amount. + // Return the selected stack. + + if(m_selected_item) + { + if(m_selected_item->isValid()) + { + Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc); + if(inv) + { + InventoryList *list = inv->getList(m_selected_item->listname); + if(list && (u32) m_selected_item->i < list->getSize()) + { + ItemStack stack = list->getItem(m_selected_item->i); + if(m_selected_amount > stack.count) + m_selected_amount = stack.count; + if(!stack.empty()) + return stack; + } + } + } + + // selection was not valid + delete m_selected_item; + m_selected_item = NULL; + m_selected_amount = 0; + m_selected_dragging = false; + } + return ItemStack(); +} + +void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) +{ + if(m_text_dst) + { + std::map fields; + + if (quitmode == quit_mode_accept) { + fields["quit"] = "true"; + } + + if (quitmode == quit_mode_cancel) { + fields["quit"] = "true"; + m_text_dst->gotText(fields); + return; + } + + if (current_keys_pending.key_down) { + fields["key_down"] = "true"; + current_keys_pending.key_down = false; + } + + if (current_keys_pending.key_up) { + fields["key_up"] = "true"; + current_keys_pending.key_up = false; + } + + if (current_keys_pending.key_enter) { + fields["key_enter"] = "true"; + current_keys_pending.key_enter = false; + } + + if (current_keys_pending.key_escape) { + fields["key_escape"] = "true"; + current_keys_pending.key_escape = false; + } + + for(unsigned int i=0; icheckEvent(); + } + } + else if(s.ftype == f_DropDown) { + // no dynamic cast possible due to some distributions shipped + // without rtti support in irrlicht + IGUIElement * element = getElementFromId(s.fid); + gui::IGUIComboBox *e = NULL; + if ((element) && (element->getType() == gui::EGUIET_COMBO_BOX)) { + e = static_cast(element); + } + s32 selected = e->getSelected(); + if (selected >= 0) { + fields[name] = + wide_to_narrow(e->getItem(selected)); + } + } + else if (s.ftype == f_TabHeader) { + // no dynamic cast possible due to some distributions shipped + // without rtti support in irrlicht + IGUIElement * element = getElementFromId(s.fid); + gui::IGUITabControl *e = NULL; + if ((element) && (element->getType() == gui::EGUIET_TAB_CONTROL)) { + e = static_cast(element); + } + + if (e != 0) { + std::stringstream ss; + ss << (e->getActiveTab() +1); + fields[name] = ss.str(); + } + } + else if (s.ftype == f_CheckBox) { + // no dynamic cast possible due to some distributions shipped + // without rtti support in irrlicht + IGUIElement * element = getElementFromId(s.fid); + gui::IGUICheckBox *e = NULL; + if ((element) && (element->getType() == gui::EGUIET_CHECK_BOX)) { + e = static_cast(element); + } + + if (e != 0) { + if (e->isChecked()) + fields[name] = "true"; + else + fields[name] = "false"; + } + } + else if (s.ftype == f_ScrollBar) { + // no dynamic cast possible due to some distributions shipped + // without rtti support in irrlicht + IGUIElement * element = getElementFromId(s.fid); + gui::IGUIScrollBar *e = NULL; + if ((element) && (element->getType() == gui::EGUIET_SCROLL_BAR)) { + e = static_cast(element); + } + + if (e != 0) { + std::stringstream os; + os << e->getPos(); + if (s.fdefault == L"Changed") + fields[name] = "CHG:" + os.str(); + else + fields[name] = "VAL:" + os.str(); + } + } + else + { + IGUIElement* e = getElementFromId(s.fid); + if(e != NULL) { + fields[name] = wide_to_narrow(e->getText()); + } + } + } + } + + m_text_dst->gotText(fields); + } +} + +static bool isChild(gui::IGUIElement * tocheck, gui::IGUIElement * parent) +{ + while(tocheck != NULL) { + if (tocheck == parent) { + return true; + } + tocheck = tocheck->getParent(); + } + return false; +} + +bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) +{ + // The IGUITabControl renders visually using the skin's selected + // font, which we override for the duration of form drawing, + // but computes tab hotspots based on how it would have rendered + // using the font that is selected at the time of button release. + // To make these two consistent, temporarily override the skin's + // font while the IGUITabControl is processing the event. + if (event.EventType == EET_MOUSE_INPUT_EVENT && + event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) { + s32 x = event.MouseInput.X; + s32 y = event.MouseInput.Y; + gui::IGUIElement *hovered = + Environment->getRootGUIElement()->getElementFromPoint( + core::position2d(x, y)); + if (hovered && isMyChild(hovered) && + hovered->getType() == gui::EGUIET_TAB_CONTROL) { + gui::IGUISkin* skin = Environment->getSkin(); + sanity_check(skin != NULL); + gui::IGUIFont *old_font = skin->getFont(); + skin->setFont(m_font); + bool retval = hovered->OnEvent(event); + skin->setFont(old_font); + return retval; + } + } + + // Fix Esc/Return key being eaten by checkboxen and tables + if(event.EventType==EET_KEY_INPUT_EVENT) { + KeyPress kp(event.KeyInput); + if (kp == EscapeKey || kp == CancelKey + || kp == getKeySetting("keymap_inventory") + || event.KeyInput.Key==KEY_RETURN) { + gui::IGUIElement *focused = Environment->getFocus(); + if (focused && isMyChild(focused) && + (focused->getType() == gui::EGUIET_LIST_BOX || + focused->getType() == gui::EGUIET_CHECK_BOX)) { + OnEvent(event); + return true; + } + } + } + // Mouse wheel events: send to hovered element instead of focused + if(event.EventType==EET_MOUSE_INPUT_EVENT + && event.MouseInput.Event == EMIE_MOUSE_WHEEL) { + s32 x = event.MouseInput.X; + s32 y = event.MouseInput.Y; + gui::IGUIElement *hovered = + Environment->getRootGUIElement()->getElementFromPoint( + core::position2d(x, y)); + if (hovered && isMyChild(hovered)) { + hovered->OnEvent(event); + return true; + } + } + + if (event.EventType == EET_MOUSE_INPUT_EVENT) { + s32 x = event.MouseInput.X; + s32 y = event.MouseInput.Y; + gui::IGUIElement *hovered = + Environment->getRootGUIElement()->getElementFromPoint( + core::position2d(x, y)); + if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { + m_old_tooltip_id = -1; + m_old_tooltip = ""; + } + if (!isChild(hovered,this)) { + if (DoubleClickDetection(event)) { + return true; + } + } + } + + #ifdef __ANDROID__ + // display software keyboard when clicking edit boxes + if (event.EventType == EET_MOUSE_INPUT_EVENT + && event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { + gui::IGUIElement *hovered = + Environment->getRootGUIElement()->getElementFromPoint( + core::position2d(event.MouseInput.X, event.MouseInput.Y)); + if ((hovered) && (hovered->getType() == irr::gui::EGUIET_EDIT_BOX)) { + bool retval = hovered->OnEvent(event); + if (retval) { + Environment->setFocus(hovered); + } + m_JavaDialogFieldName = getNameByID(hovered->getID()); + std::string message = gettext("Enter "); + std::string label = wide_to_narrow(getLabelByID(hovered->getID())); + if (label == "") { + label = "text"; + } + message += gettext(label) + ":"; + + /* single line text input */ + int type = 2; + + /* multi line text input */ + if (((gui::IGUIEditBox*) hovered)->isMultiLineEnabled()) { + type = 1; + } + + /* passwords are always single line */ + if (((gui::IGUIEditBox*) hovered)->isPasswordBox()) { + type = 3; + } + + porting::showInputDialog(gettext("ok"), "", + wide_to_narrow(((gui::IGUIEditBox*) hovered)->getText()), + type); + return retval; + } + } + + if (event.EventType == EET_TOUCH_INPUT_EVENT) + { + SEvent translated; + memset(&translated, 0, sizeof(SEvent)); + translated.EventType = EET_MOUSE_INPUT_EVENT; + gui::IGUIElement* root = Environment->getRootGUIElement(); + + if (!root) { + errorstream + << "GUIFormSpecMenu::preprocessEvent unable to get root element" + << std::endl; + return false; + } + gui::IGUIElement* hovered = root->getElementFromPoint( + core::position2d( + event.TouchInput.X, + event.TouchInput.Y)); + + translated.MouseInput.X = event.TouchInput.X; + translated.MouseInput.Y = event.TouchInput.Y; + translated.MouseInput.Control = false; + + bool dont_send_event = false; + + if (event.TouchInput.touchedCount == 1) { + switch (event.TouchInput.Event) { + case ETIE_PRESSED_DOWN: + m_pointer = v2s32(event.TouchInput.X,event.TouchInput.Y); + translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN; + translated.MouseInput.ButtonStates = EMBSM_LEFT; + m_down_pos = m_pointer; + break; + case ETIE_MOVED: + m_pointer = v2s32(event.TouchInput.X,event.TouchInput.Y); + translated.MouseInput.Event = EMIE_MOUSE_MOVED; + translated.MouseInput.ButtonStates = EMBSM_LEFT; + break; + case ETIE_LEFT_UP: + translated.MouseInput.Event = EMIE_LMOUSE_LEFT_UP; + translated.MouseInput.ButtonStates = 0; + hovered = root->getElementFromPoint(m_down_pos); + /* we don't have a valid pointer element use last + * known pointer pos */ + translated.MouseInput.X = m_pointer.X; + translated.MouseInput.Y = m_pointer.Y; + + /* reset down pos */ + m_down_pos = v2s32(0,0); + break; + default: + dont_send_event = true; + //this is not supposed to happen + errorstream + << "GUIFormSpecMenu::preprocessEvent unexpected usecase Event=" + << event.TouchInput.Event << std::endl; + } + } else if ( (event.TouchInput.touchedCount == 2) && + (event.TouchInput.Event == ETIE_PRESSED_DOWN) ) { + hovered = root->getElementFromPoint(m_down_pos); + + translated.MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN; + translated.MouseInput.ButtonStates = EMBSM_LEFT | EMBSM_RIGHT; + translated.MouseInput.X = m_pointer.X; + translated.MouseInput.Y = m_pointer.Y; + + if (hovered) { + hovered->OnEvent(translated); + } + + translated.MouseInput.Event = EMIE_RMOUSE_LEFT_UP; + translated.MouseInput.ButtonStates = EMBSM_LEFT; + + + if (hovered) { + hovered->OnEvent(translated); + } + dont_send_event = true; + } + /* ignore unhandled 2 touch events ... accidental moving for example */ + else if (event.TouchInput.touchedCount == 2) { + dont_send_event = true; + } + else if (event.TouchInput.touchedCount > 2) { + errorstream + << "GUIFormSpecMenu::preprocessEvent to many multitouch events " + << event.TouchInput.touchedCount << " ignoring them" << std::endl; + } + + if (dont_send_event) { + return true; + } + + /* check if translated event needs to be preprocessed again */ + if (preprocessEvent(translated)) { + return true; + } + if (hovered) { + grab(); + bool retval = hovered->OnEvent(translated); + + if (event.TouchInput.Event == ETIE_LEFT_UP) { + /* reset pointer */ + m_pointer = v2s32(0,0); + } + drop(); + return retval; + } + } + #endif + + return false; +} + +/******************************************************************************/ +bool GUIFormSpecMenu::DoubleClickDetection(const SEvent event) +{ + /* The following code is for capturing double-clicks of the mouse button + * and translating the double-click into an EET_KEY_INPUT_EVENT event + * -- which closes the form -- under some circumstances. + * + * There have been many github issues reporting this as a bug even though it + * was an intended feature. For this reason, remapping the double-click as + * an ESC must be explicitly set when creating this class via the + * /p remap_dbl_click parameter of the constructor. + */ + + if (!m_remap_dbl_click) + return false; + + if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { + m_doubleclickdetect[0].pos = m_doubleclickdetect[1].pos; + m_doubleclickdetect[0].time = m_doubleclickdetect[1].time; + + m_doubleclickdetect[1].pos = m_pointer; + m_doubleclickdetect[1].time = getTimeMs(); + } + else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) { + u32 delta = porting::getDeltaMs(m_doubleclickdetect[0].time, getTimeMs()); + if (delta > 400) { + return false; + } + + double squaredistance = + m_doubleclickdetect[0].pos + .getDistanceFromSQ(m_doubleclickdetect[1].pos); + + if (squaredistance > (30*30)) { + return false; + } + + SEvent* translated = new SEvent(); + assert(translated != 0); + //translate doubleclick to escape + memset(translated, 0, sizeof(SEvent)); + translated->EventType = irr::EET_KEY_INPUT_EVENT; + translated->KeyInput.Key = KEY_ESCAPE; + translated->KeyInput.Control = false; + translated->KeyInput.Shift = false; + translated->KeyInput.PressedDown = true; + translated->KeyInput.Char = 0; + OnEvent(*translated); + + // no need to send the key up event as we're already deleted + // and no one else did notice this event + delete translated; + return true; + } + + return false; +} + +bool GUIFormSpecMenu::OnEvent(const SEvent& event) +{ + if(event.EventType==EET_KEY_INPUT_EVENT) { + KeyPress kp(event.KeyInput); + if (event.KeyInput.PressedDown && ( (kp == EscapeKey) || + (kp == getKeySetting("keymap_inventory")) || (kp == CancelKey))) { + if (m_allowclose) { + doPause = false; + acceptInput(quit_mode_cancel); + quitMenu(); + } else { + m_text_dst->gotText(narrow_to_wide("MenuQuit")); + } + return true; + } else if (m_client != NULL && event.KeyInput.PressedDown && + (kp == getKeySetting("keymap_screenshot"))) { + m_client->makeScreenshot(m_device); + } + if (event.KeyInput.PressedDown && + (event.KeyInput.Key==KEY_RETURN || + event.KeyInput.Key==KEY_UP || + event.KeyInput.Key==KEY_DOWN) + ) { + switch (event.KeyInput.Key) { + case KEY_RETURN: + current_keys_pending.key_enter = true; + break; + case KEY_UP: + current_keys_pending.key_up = true; + break; + case KEY_DOWN: + current_keys_pending.key_down = true; + break; + break; + default: + //can't happen at all! + FATAL_ERROR("Reached a source line that can't ever been reached"); + break; + } + if (current_keys_pending.key_enter && m_allowclose) { + acceptInput(quit_mode_accept); + quitMenu(); + } else { + acceptInput(); + } + return true; + } + + } + + /* Mouse event other than movement, or crossing the border of inventory + field while holding right mouse button + */ + if (event.EventType == EET_MOUSE_INPUT_EVENT && + (event.MouseInput.Event != EMIE_MOUSE_MOVED || + (event.MouseInput.Event == EMIE_MOUSE_MOVED && + event.MouseInput.isRightPressed() && + getItemAtPos(m_pointer).i != getItemAtPos(m_old_pointer).i))) { + + // Get selected item and hovered/clicked item (s) + + m_old_tooltip_id = -1; + updateSelectedItem(); + ItemSpec s = getItemAtPos(m_pointer); + + Inventory *inv_selected = NULL; + Inventory *inv_s = NULL; + + if(m_selected_item) { + inv_selected = m_invmgr->getInventory(m_selected_item->inventoryloc); + sanity_check(inv_selected); + sanity_check(inv_selected->getList(m_selected_item->listname) != NULL); + } + + u32 s_count = 0; + + if(s.isValid()) + do { // breakable + inv_s = m_invmgr->getInventory(s.inventoryloc); + + if(!inv_s) { + errorstream<<"InventoryMenu: The selected inventory location " + <<"\""<getList(s.listname); + if(list == NULL) { + verbosestream<<"InventoryMenu: The selected inventory list \"" + <= list->getSize()) { + infostream<<"InventoryMenu: The selected inventory list \"" + <getItem(s.i).count; + } while(0); + + bool identical = (m_selected_item != NULL) && s.isValid() && + (inv_selected == inv_s) && + (m_selected_item->listname == s.listname) && + (m_selected_item->i == s.i); + + // buttons: 0 = left, 1 = right, 2 = middle + // up/down: 0 = down (press), 1 = up (release), 2 = unknown event, -1 movement + int button = 0; + int updown = 2; + if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) + { button = 0; updown = 0; } + else if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN) + { button = 1; updown = 0; } + else if(event.MouseInput.Event == EMIE_MMOUSE_PRESSED_DOWN) + { button = 2; updown = 0; } + else if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) + { button = 0; updown = 1; } + else if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP) + { button = 1; updown = 1; } + else if(event.MouseInput.Event == EMIE_MMOUSE_LEFT_UP) + { button = 2; updown = 1; } + else if(event.MouseInput.Event == EMIE_MOUSE_MOVED) + { updown = -1;} + + // Set this number to a positive value to generate a move action + // from m_selected_item to s. + u32 move_amount = 0; + + // Set this number to a positive value to generate a drop action + // from m_selected_item. + u32 drop_amount = 0; + + // Set this number to a positive value to generate a craft action at s. + u32 craft_amount = 0; + + if(updown == 0) { + // Some mouse button has been pressed + + //infostream<<"Mouse button "<= 1); + + if(s.isValid()) { + // Clicked a slot: move + if(button == 1) // right + move_amount = 1; + else if(button == 2) // middle + move_amount = MYMIN(m_selected_amount, 10); + else // left + move_amount = m_selected_amount; + + if(identical) { + if(move_amount >= m_selected_amount) + m_selected_amount = 0; + else + m_selected_amount -= move_amount; + move_amount = 0; + } + } + else if (!getAbsoluteClippingRect().isPointInside(m_pointer)) { + // Clicked outside of the window: drop + if(button == 1) // right + drop_amount = 1; + else if(button == 2) // middle + drop_amount = MYMIN(m_selected_amount, 10); + else // left + drop_amount = m_selected_amount; + } + } + } + else if(updown == 1) { + // Some mouse button has been released + + //infostream<<"Mouse button "<getList(m_selected_item->listname); + InventoryList *list_to = inv_s->getList(s.listname); + assert(list_from && list_to); + ItemStack stack_from = list_from->getItem(m_selected_item->i); + ItemStack stack_to = list_to->getItem(s.i); + if (stack_to.empty() || stack_to.name == stack_from.name) + move_amount = 1; + } + } + } + + // Possibly send inventory action to server + if(move_amount > 0) + { + // Send IACTION_MOVE + + assert(m_selected_item && m_selected_item->isValid()); + assert(s.isValid()); + + assert(inv_selected && inv_s); + InventoryList *list_from = inv_selected->getList(m_selected_item->listname); + InventoryList *list_to = inv_s->getList(s.listname); + assert(list_from && list_to); + ItemStack stack_from = list_from->getItem(m_selected_item->i); + ItemStack stack_to = list_to->getItem(s.i); + + // Check how many items can be moved + move_amount = stack_from.count = MYMIN(move_amount, stack_from.count); + ItemStack leftover = stack_to.addItem(stack_from, m_gamedef->idef()); + // If source stack cannot be added to destination stack at all, + // they are swapped + if ((leftover.count == stack_from.count) && + (leftover.name == stack_from.name)) { + m_selected_amount = stack_to.count; + // In case the server doesn't directly swap them but instead + // moves stack_to somewhere else, set this + m_selected_content_guess = stack_to; + m_selected_content_guess_inventory = s.inventoryloc; + } + // Source stack goes fully into destination stack + else if(leftover.empty()) { + m_selected_amount -= move_amount; + m_selected_content_guess = ItemStack(); // Clear + } + // Source stack goes partly into destination stack + else { + move_amount -= leftover.count; + m_selected_amount -= move_amount; + m_selected_content_guess = ItemStack(); // Clear + } + + infostream<<"Handing IACTION_MOVE to manager"<count = move_amount; + a->from_inv = m_selected_item->inventoryloc; + a->from_list = m_selected_item->listname; + a->from_i = m_selected_item->i; + a->to_inv = s.inventoryloc; + a->to_list = s.listname; + a->to_i = s.i; + m_invmgr->inventoryAction(a); + } + else if(drop_amount > 0) { + m_selected_content_guess = ItemStack(); // Clear + + // Send IACTION_DROP + + assert(m_selected_item && m_selected_item->isValid()); + assert(inv_selected); + InventoryList *list_from = inv_selected->getList(m_selected_item->listname); + assert(list_from); + ItemStack stack_from = list_from->getItem(m_selected_item->i); + + // Check how many items can be dropped + drop_amount = stack_from.count = MYMIN(drop_amount, stack_from.count); + assert(drop_amount > 0 && drop_amount <= m_selected_amount); + m_selected_amount -= drop_amount; + + infostream<<"Handing IACTION_DROP to manager"<count = drop_amount; + a->from_inv = m_selected_item->inventoryloc; + a->from_list = m_selected_item->listname; + a->from_i = m_selected_item->i; + m_invmgr->inventoryAction(a); + } + else if(craft_amount > 0) { + m_selected_content_guess = ItemStack(); // Clear + + // Send IACTION_CRAFT + + assert(s.isValid()); + assert(inv_s); + + infostream<<"Handing IACTION_CRAFT to manager"<count = craft_amount; + a->craft_inv = s.inventoryloc; + m_invmgr->inventoryAction(a); + } + + // If m_selected_amount has been decreased to zero, deselect + if(m_selected_amount == 0) { + delete m_selected_item; + m_selected_item = NULL; + m_selected_amount = 0; + m_selected_dragging = false; + m_selected_content_guess = ItemStack(); + } + m_old_pointer = m_pointer; + } + if(event.EventType==EET_GUI_EVENT) { + + if(event.GUIEvent.EventType==gui::EGET_TAB_CHANGED + && isVisible()) { + // find the element that was clicked + for(unsigned int i=0; igetID())) { + s.send = true; + acceptInput(); + s.send = false; + return true; + } + } + } + if(event.GUIEvent.EventType==gui::EGET_ELEMENT_FOCUS_LOST + && isVisible()) { + if(!canTakeFocus(event.GUIEvent.Element)) { + infostream<<"GUIFormSpecMenu: Not allowing focus change." + <getID(); + + if (btn_id == 257) { + if (m_allowclose) { + acceptInput(quit_mode_accept); + quitMenu(); + } else { + acceptInput(); + m_text_dst->gotText(narrow_to_wide("ExitButton")); + } + // quitMenu deallocates menu + return true; + } + + // find the element that was clicked + for(u32 i=0; igetID())) { + s.send = true; + if(s.is_exit) { + if (m_allowclose) { + acceptInput(quit_mode_accept); + quitMenu(); + } else { + m_text_dst->gotText(narrow_to_wide("ExitButton")); + } + return true; + } else { + acceptInput(quit_mode_no); + s.send = false; + return true; + } + } + else if ((s.ftype == f_DropDown) && + (s.fid == event.GUIEvent.Caller->getID())) { + // only send the changed dropdown + for(u32 i=0; igetID())) + { + s.fdefault = L"Changed"; + acceptInput(quit_mode_no); + s.fdefault = L""; + } + } + } + + if(event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) { + if(event.GUIEvent.Caller->getID() > 257) { + + if (m_allowclose) { + acceptInput(quit_mode_accept); + quitMenu(); + } else { + current_keys_pending.key_enter = true; + acceptInput(); + } + // quitMenu deallocates menu + return true; + } + } + + if(event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) { + int current_id = event.GUIEvent.Caller->getID(); + if(current_id > 257) { + // find the element that was clicked + for(u32 i=0; iOnEvent(event) : false; +} + +/** + * get name of element by element id + * @param id of element + * @return name string or empty string + */ +std::wstring GUIFormSpecMenu::getNameByID(s32 id) +{ + for(std::vector::iterator iter = m_fields.begin(); + iter != m_fields.end(); iter++) { + if (iter->fid == id) { + return iter->fname; + } + } + return L""; +} + +/** + * get label of element by id + * @param id of element + * @return label string or empty string + */ +std::wstring GUIFormSpecMenu::getLabelByID(s32 id) +{ + for(std::vector::iterator iter = m_fields.begin(); + iter != m_fields.end(); iter++) { + if (iter->fid == id) { + return iter->flabel; + } + } + return L""; +} diff --git a/src/guiFormSpecMenu.h b/src/guiFormSpecMenu.h new file mode 100644 index 0000000..73dc7af --- /dev/null +++ b/src/guiFormSpecMenu.h @@ -0,0 +1,473 @@ +/* +Minetest +Copyright (C) 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. +*/ + + +#ifndef GUIINVENTORYMENU_HEADER +#define GUIINVENTORYMENU_HEADER + +#include + +#include "irrlichttypes_extrabloated.h" +#include "inventory.h" +#include "inventorymanager.h" +#include "modalMenu.h" +#include "guiTable.h" +#include "network/networkprotocol.h" + +class IGameDef; +class InventoryManager; +class ISimpleTextureSource; +class Client; + +typedef enum { + f_Button, + f_Table, + f_TabHeader, + f_CheckBox, + f_DropDown, + f_ScrollBar, + f_Unknown +} FormspecFieldType; + +typedef enum { + quit_mode_no, + quit_mode_accept, + quit_mode_cancel +} FormspecQuitMode; + +struct TextDest +{ + virtual ~TextDest() {}; + // This is deprecated I guess? -celeron55 + virtual void gotText(std::wstring text){} + virtual void gotText(std::map fields) = 0; + virtual void setFormName(std::string formname) + { m_formname = formname;}; + + std::string m_formname; +}; + +class IFormSource +{ +public: + virtual ~IFormSource(){} + virtual std::string getForm() = 0; + // Fill in variables in field text + virtual std::string resolveText(std::string str){ return str; } +}; + +class GUIFormSpecMenu : public GUIModalMenu +{ + struct ItemSpec + { + ItemSpec() + { + i = -1; + } + ItemSpec(const InventoryLocation &a_inventoryloc, + const std::string &a_listname, + s32 a_i) + { + inventoryloc = a_inventoryloc; + listname = a_listname; + i = a_i; + } + bool isValid() const + { + return i != -1; + } + + InventoryLocation inventoryloc; + std::string listname; + s32 i; + }; + + struct ListDrawSpec + { + ListDrawSpec() + { + } + ListDrawSpec(const InventoryLocation &a_inventoryloc, + const std::string &a_listname, + v2s32 a_pos, v2s32 a_geom, s32 a_start_item_i): + inventoryloc(a_inventoryloc), + listname(a_listname), + pos(a_pos), + geom(a_geom), + start_item_i(a_start_item_i) + { + } + + InventoryLocation inventoryloc; + std::string listname; + v2s32 pos; + v2s32 geom; + s32 start_item_i; + }; + + struct ImageDrawSpec + { + ImageDrawSpec() + { + } + ImageDrawSpec(const std::string &a_name, + v2s32 a_pos, v2s32 a_geom): + name(a_name), + pos(a_pos), + geom(a_geom) + { + scale = true; + } + ImageDrawSpec(const std::string &a_name, + v2s32 a_pos): + name(a_name), + pos(a_pos) + { + scale = false; + } + std::string name; + v2s32 pos; + v2s32 geom; + bool scale; + }; + + struct FieldSpec + { + FieldSpec() + { + } + FieldSpec(const std::wstring &name, const std::wstring &label, + const std::wstring &fdeflt, int id) : + fname(name), + flabel(label), + fdefault(fdeflt), + fid(id) + { + send = false; + ftype = f_Unknown; + is_exit = false; + } + std::wstring fname; + std::wstring flabel; + std::wstring fdefault; + int fid; + bool send; + FormspecFieldType ftype; + bool is_exit; + core::rect rect; + }; + + struct BoxDrawSpec { + BoxDrawSpec(v2s32 a_pos, v2s32 a_geom,irr::video::SColor a_color): + pos(a_pos), + geom(a_geom), + color(a_color) + { + } + v2s32 pos; + v2s32 geom; + irr::video::SColor color; + }; + + struct TooltipSpec { + TooltipSpec() + { + } + TooltipSpec(std::string a_tooltip, irr::video::SColor a_bgcolor, + irr::video::SColor a_color): + tooltip(a_tooltip), + bgcolor(a_bgcolor), + color(a_color) + { + } + std::string tooltip; + irr::video::SColor bgcolor; + irr::video::SColor color; + }; + +public: + GUIFormSpecMenu(irr::IrrlichtDevice* dev, + gui::IGUIElement* parent, s32 id, + IMenuManager *menumgr, + InventoryManager *invmgr, + IGameDef *gamedef, + ISimpleTextureSource *tsrc, + IFormSource* fs_src, + TextDest* txt_dst, + Client* client, + bool remap_dbl_click = true); + + ~GUIFormSpecMenu(); + + void setFormSpec(const std::string &formspec_string, + InventoryLocation current_inventory_location) + { + m_formspec_string = formspec_string; + m_current_inventory_location = current_inventory_location; + regenerateGui(m_screensize_old); + } + + // form_src is deleted by this GUIFormSpecMenu + void setFormSource(IFormSource *form_src) + { + if (m_form_src != NULL) { + delete m_form_src; + } + m_form_src = form_src; + } + + // text_dst is deleted by this GUIFormSpecMenu + void setTextDest(TextDest *text_dst) + { + if (m_text_dst != NULL) { + delete m_text_dst; + } + m_text_dst = text_dst; + } + + void allowClose(bool value) + { + m_allowclose = value; + } + + void lockSize(bool lock,v2u32 basescreensize=v2u32(0,0)) + { + m_lock = lock; + m_lockscreensize = basescreensize; + } + + void removeChildren(); + void setInitialFocus(); + + void setFocus(std::wstring elementname) + { + m_focused_element = elementname; + } + + /* + Remove and re-add (or reposition) stuff + */ + void regenerateGui(v2u32 screensize); + + ItemSpec getItemAtPos(v2s32 p) const; + void drawList(const ListDrawSpec &s, int phase); + void drawSelectedItem(); + void drawMenu(); + void updateSelectedItem(); + ItemStack verifySelectedItem(); + + void acceptInput(FormspecQuitMode quitmode); + bool preprocessEvent(const SEvent& event); + bool OnEvent(const SEvent& event); + bool doPause; + bool pausesGame() { return doPause; } + + GUITable* getTable(std::wstring tablename); + +#ifdef __ANDROID__ + bool getAndroidUIInput(); +#endif + +protected: + v2s32 getBasePos() const + { + return padding + offset + AbsoluteRect.UpperLeftCorner; + } + + v2s32 padding; + v2s32 spacing; + v2s32 imgsize; + v2s32 offset; + + irr::IrrlichtDevice* m_device; + InventoryManager *m_invmgr; + IGameDef *m_gamedef; + ISimpleTextureSource *m_tsrc; + Client *m_client; + + std::string m_formspec_string; + InventoryLocation m_current_inventory_location; + + + std::vector m_inventorylists; + std::vector m_backgrounds; + std::vector m_images; + std::vector m_itemimages; + std::vector m_boxes; + std::vector m_fields; + std::vector > m_tables; + std::vector > m_checkboxes; + std::map m_tooltips; + std::vector > m_scrollbars; + + ItemSpec *m_selected_item; + u32 m_selected_amount; + bool m_selected_dragging; + + // WARNING: BLACK MAGIC + // Used to guess and keep up with some special things the server can do. + // If name is "", no guess exists. + ItemStack m_selected_content_guess; + InventoryLocation m_selected_content_guess_inventory; + + v2s32 m_pointer; + v2s32 m_old_pointer; // Mouse position after previous mouse event + gui::IGUIStaticText *m_tooltip_element; + + u32 m_tooltip_show_delay; + s32 m_hovered_time; + s32 m_old_tooltip_id; + std::string m_old_tooltip; + + bool m_rmouse_auto_place; + + bool m_allowclose; + bool m_lock; + v2u32 m_lockscreensize; + + bool m_bgfullscreen; + bool m_slotborder; + bool m_clipbackground; + video::SColor m_bgcolor; + video::SColor m_slotbg_n; + video::SColor m_slotbg_h; + video::SColor m_slotbordercolor; + video::SColor m_default_tooltip_bgcolor; + video::SColor m_default_tooltip_color; + +private: + IFormSource *m_form_src; + TextDest *m_text_dst; + unsigned int m_formspec_version; + std::wstring m_focused_element; + + typedef struct { + bool explicit_size; + v2f invsize; + v2s32 size; + core::rect rect; + v2s32 basepos; + v2u32 screensize; + std::wstring focused_fieldname; + GUITable::TableOptions table_options; + GUITable::TableColumns table_columns; + // used to restore table selection/scroll/treeview state + std::map table_dyndata; + } parserData; + + typedef struct { + bool key_up; + bool key_down; + bool key_enter; + bool key_escape; + } fs_key_pendig; + + fs_key_pendig current_keys_pending; + + void parseElement(parserData* data,std::string element); + + void parseSize(parserData* data,std::string element); + void parseList(parserData* data,std::string element); + void parseCheckbox(parserData* data,std::string element); + void parseImage(parserData* data,std::string element); + void parseItemImage(parserData* data,std::string element); + void parseButton(parserData* data,std::string element,std::string typ); + void parseBackground(parserData* data,std::string element); + void parseTableOptions(parserData* data,std::string element); + void parseTableColumns(parserData* data,std::string element); + void parseTable(parserData* data,std::string element); + void parseTextList(parserData* data,std::string element); + void parseDropDown(parserData* data,std::string element); + void parsePwdField(parserData* data,std::string element); + void parseField(parserData* data,std::string element,std::string type); + void parseSimpleField(parserData* data,std::vector &parts); + void parseTextArea(parserData* data,std::vector& parts, + std::string type); + void parseLabel(parserData* data,std::string element); + void parseVertLabel(parserData* data,std::string element); + void parseImageButton(parserData* data,std::string element,std::string type); + void parseItemImageButton(parserData* data,std::string element); + void parseTabHeader(parserData* data,std::string element); + void parseBox(parserData* data,std::string element); + void parseBackgroundColor(parserData* data,std::string element); + void parseListColors(parserData* data,std::string element); + void parseTooltip(parserData* data,std::string element); + bool parseVersionDirect(std::string data); + bool parseSizeDirect(parserData* data, std::string element); + void parseScrollBar(parserData* data, std::string element); + + /** + * check if event is part of a double click + * @param event event to evaluate + * @return true/false if a doubleclick was detected + */ + bool DoubleClickDetection(const SEvent event); + + struct clickpos + { + v2s32 pos; + s32 time; + }; + clickpos m_doubleclickdetect[2]; + + int m_btn_height; + gui::IGUIFont *m_font; + + std::wstring getLabelByID(s32 id); + std::wstring getNameByID(s32 id); +#ifdef __ANDROID__ + v2s32 m_down_pos; + std::wstring m_JavaDialogFieldName; +#endif + + /* If true, remap a double-click (or double-tap) action to ESC. This is so + * that, for example, Android users can double-tap to close a formspec. + * + * This value can (currently) only be set by the class constructor + * and the default value for the setting is true. + */ + bool m_remap_dbl_click; + +}; + +class FormspecFormSource: public IFormSource +{ +public: + FormspecFormSource(std::string formspec) + { + m_formspec = formspec; + } + + ~FormspecFormSource() + {} + + void setForm(std::string formspec) { + m_formspec = FORMSPEC_VERSION_STRING + formspec; + } + + std::string getForm() + { + return m_formspec; + } + + std::string m_formspec; +}; + +#endif + diff --git a/src/guiKeyChangeMenu.cpp b/src/guiKeyChangeMenu.cpp new file mode 100644 index 0000000..3ddb3f5 --- /dev/null +++ b/src/guiKeyChangeMenu.cpp @@ -0,0 +1,417 @@ +/* + Minetest + Copyright (C) 2010-2013 celeron55, Perttu Ahola + Copyright (C) 2013 Ciaran Gultnieks + Copyright (C) 2013 teddydestodes + + 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. + */ + +#include "guiKeyChangeMenu.h" +#include "debug.h" +#include "serialization.h" +#include "main.h" +#include +#include +#include +#include +#include +#include +#include "settings.h" +#include + +#include "mainmenumanager.h" // for g_gamecallback + +#define KMaxButtonPerColumns 12 + +extern MainGameCallback *g_gamecallback; + +enum +{ + GUI_ID_BACK_BUTTON = 101, GUI_ID_ABORT_BUTTON, GUI_ID_SCROLL_BAR, + // buttons + GUI_ID_KEY_FORWARD_BUTTON, + GUI_ID_KEY_BACKWARD_BUTTON, + GUI_ID_KEY_LEFT_BUTTON, + GUI_ID_KEY_RIGHT_BUTTON, + GUI_ID_KEY_USE_BUTTON, + GUI_ID_KEY_FLY_BUTTON, + GUI_ID_KEY_FAST_BUTTON, + GUI_ID_KEY_JUMP_BUTTON, + GUI_ID_KEY_NOCLIP_BUTTON, + GUI_ID_KEY_CINEMATIC_BUTTON, + GUI_ID_KEY_CHAT_BUTTON, + GUI_ID_KEY_CMD_BUTTON, + GUI_ID_KEY_CONSOLE_BUTTON, + GUI_ID_KEY_SNEAK_BUTTON, + GUI_ID_KEY_DROP_BUTTON, + GUI_ID_KEY_INVENTORY_BUTTON, + GUI_ID_KEY_DUMP_BUTTON, + GUI_ID_KEY_RANGE_BUTTON, + // other + GUI_ID_CB_AUX1_DESCENDS, + GUI_ID_CB_DOUBLETAP_JUMP, +}; + +GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env, + gui::IGUIElement* parent, s32 id, IMenuManager *menumgr) : +GUIModalMenu(env, parent, id, menumgr) +{ + shift_down = false; + activeKey = -1; + this->key_used_text = NULL; + init_keys(); + for(size_t i=0; ikey_used.push_back(key_settings.at(i)->key); +} + +GUIKeyChangeMenu::~GUIKeyChangeMenu() +{ + removeChildren(); + + for (std::vector::iterator iter = key_settings.begin(); + iter != key_settings.end(); iter ++) { + delete[] (*iter)->button_name; + delete (*iter); + } + key_settings.clear(); +} + +void GUIKeyChangeMenu::removeChildren() +{ + const core::list &children = getChildren(); + core::list children_copy; + for (core::list::ConstIterator i = children.begin(); i + != children.end(); i++) + { + children_copy.push_back(*i); + } + for (core::list::Iterator i = children_copy.begin(); i + != children_copy.end(); i++) + { + (*i)->remove(); + } +} + +void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) +{ + removeChildren(); + v2s32 size(620, 430); + + core::rect < s32 > rect(screensize.X / 2 - size.X / 2, + screensize.Y / 2 - size.Y / 2, screensize.X / 2 + size.X / 2, + screensize.Y / 2 + size.Y / 2); + + DesiredRect = rect; + recalculateAbsolutePosition(false); + + v2s32 topleft(0, 0); + + { + core::rect < s32 > rect(0, 0, 600, 40); + rect += topleft + v2s32(25, 3); + //gui::IGUIStaticText *t = + const wchar_t *text = wgettext("Keybindings. (If this menu screws up, remove stuff from minetest.conf)"); + Environment->addStaticText(text, + rect, false, true, this, -1); + delete[] text; + //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); + } + + // Build buttons + + v2s32 offset(25, 60); + + for(size_t i = 0; i < key_settings.size(); i++) + { + key_setting *k = key_settings.at(i); + { + core::rect < s32 > rect(0, 0, 110, 20); + rect += topleft + v2s32(offset.X, offset.Y); + Environment->addStaticText(k->button_name, rect, false, true, this, -1); + } + + { + core::rect < s32 > rect(0, 0, 100, 30); + rect += topleft + v2s32(offset.X + 115, offset.Y - 5); + const wchar_t *text = wgettext(k->key.name()); + k->button = Environment->addButton(rect, this, k->id, text); + delete[] text; + } + if(i + 1 == KMaxButtonPerColumns) + offset = v2s32(260, 60); + else + offset += v2s32(0, 25); + } + + { + s32 option_x = offset.X; + s32 option_y = offset.Y + 5; + u32 option_w = 180; + { + core::rect rect(0, 0, option_w, 30); + rect += topleft + v2s32(option_x, option_y); + const wchar_t *text = wgettext("\"Use\" = climb down"); + Environment->addCheckBox(g_settings->getBool("aux1_descends"), rect, this, + GUI_ID_CB_AUX1_DESCENDS, text); + delete[] text; + } + offset += v2s32(0, 25); + } + + { + s32 option_x = offset.X; + s32 option_y = offset.Y + 5; + u32 option_w = 280; + { + core::rect rect(0, 0, option_w, 30); + rect += topleft + v2s32(option_x, option_y); + const wchar_t *text = wgettext("Double tap \"jump\" to toggle fly"); + Environment->addCheckBox(g_settings->getBool("doubletap_jump"), rect, this, + GUI_ID_CB_DOUBLETAP_JUMP, text); + delete[] text; + } + offset += v2s32(0, 25); + } + + { + core::rect < s32 > rect(0, 0, 100, 30); + rect += topleft + v2s32(size.X - 100 - 20, size.Y - 40); + const wchar_t *text = wgettext("Save"); + Environment->addButton(rect, this, GUI_ID_BACK_BUTTON, + text); + delete[] text; + } + { + core::rect < s32 > rect(0, 0, 100, 30); + rect += topleft + v2s32(size.X - 100 - 20 - 100 - 20, size.Y - 40); + const wchar_t *text = wgettext("Cancel"); + Environment->addButton(rect, this, GUI_ID_ABORT_BUTTON, + text); + delete[] text; + } +} + +void GUIKeyChangeMenu::drawMenu() +{ + gui::IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + video::IVideoDriver* driver = Environment->getVideoDriver(); + + video::SColor bgcolor(140, 0, 0, 0); + + { + core::rect < s32 > rect(0, 0, 620, 620); + rect += AbsoluteRect.UpperLeftCorner; + driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect); + } + + gui::IGUIElement::draw(); +} + +bool GUIKeyChangeMenu::acceptInput() +{ + for(size_t i = 0; i < key_settings.size(); i++) + { + key_setting *k = key_settings.at(i); + g_settings->set(k->setting_name, k->key.sym()); + } + { + gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUX1_DESCENDS); + if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) + g_settings->setBool("aux1_descends", ((gui::IGUICheckBox*)e)->isChecked()); + } + { + gui::IGUIElement *e = getElementFromId(GUI_ID_CB_DOUBLETAP_JUMP); + if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) + g_settings->setBool("doubletap_jump", ((gui::IGUICheckBox*)e)->isChecked()); + } + + clearKeyCache(); + + g_gamecallback->signalKeyConfigChange(); + + return true; +} + +bool GUIKeyChangeMenu::resetMenu() +{ + if (activeKey >= 0) + { + for(size_t i = 0; i < key_settings.size(); i++) + { + key_setting *k = key_settings.at(i); + if(k->id == activeKey) + { + const wchar_t *text = wgettext(k->key.name()); + k->button->setText(text); + delete[] text; + break; + } + } + activeKey = -1; + return false; + } + return true; +} +bool GUIKeyChangeMenu::OnEvent(const SEvent& event) +{ + if (event.EventType == EET_KEY_INPUT_EVENT && activeKey >= 0 + && event.KeyInput.PressedDown) + { + + bool prefer_character = shift_down; + KeyPress kp(event.KeyInput, prefer_character); + + bool shift_went_down = false; + if(!shift_down && + (event.KeyInput.Key == irr::KEY_SHIFT || + event.KeyInput.Key == irr::KEY_LSHIFT || + event.KeyInput.Key == irr::KEY_RSHIFT)) + shift_went_down = true; + + // Remove Key already in use message + if(this->key_used_text) + { + this->key_used_text->remove(); + this->key_used_text = NULL; + } + // Display Key already in use message + if (std::find(this->key_used.begin(), this->key_used.end(), kp) != this->key_used.end()) + { + core::rect < s32 > rect(0, 0, 600, 40); + rect += v2s32(0, 0) + v2s32(25, 30); + const wchar_t *text = wgettext("Key already in use"); + this->key_used_text = Environment->addStaticText(text, + rect, false, true, this, -1); + delete[] text; + //infostream << "Key already in use" << std::endl; + } + + // But go on + { + key_setting *k = NULL; + for(size_t i = 0; i < key_settings.size(); i++) + { + if(key_settings.at(i)->id == activeKey) + { + k = key_settings.at(i); + break; + } + } + FATAL_ERROR_IF(k == NULL, "Key setting not found"); + k->key = kp; + const wchar_t *text = wgettext(k->key.name()); + k->button->setText(text); + delete[] text; + + this->key_used.push_back(kp); + + // Allow characters made with shift + if(shift_went_down){ + shift_down = true; + return false; + }else{ + activeKey = -1; + return true; + } + } + } + if (event.EventType == EET_GUI_EVENT) + { + if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST + && isVisible()) + { + if (!canTakeFocus(event.GUIEvent.Element)) + { + dstream << "GUIMainMenu: Not allowing focus change." + << std::endl; + // Returning true disables focus change + return true; + } + } + if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) + { + switch (event.GUIEvent.Caller->getID()) + { + case GUI_ID_BACK_BUTTON: //back + acceptInput(); + quitMenu(); + return true; + case GUI_ID_ABORT_BUTTON: //abort + quitMenu(); + return true; + default: + key_setting *k = NULL; + for(size_t i = 0; i < key_settings.size(); i++) + { + if(key_settings.at(i)->id == event.GUIEvent.Caller->getID()) + { + k = key_settings.at(i); + break; + } + } + FATAL_ERROR_IF(k == NULL, "Key setting not found"); + + resetMenu(); + shift_down = false; + activeKey = event.GUIEvent.Caller->getID(); + const wchar_t *text = wgettext("press key"); + k->button->setText(text); + delete[] text; + this->key_used.erase(std::remove(this->key_used.begin(), + this->key_used.end(), k->key), this->key_used.end()); + break; + } + Environment->setFocus(this); + } + } + return Parent ? Parent->OnEvent(event) : false; +} + +void GUIKeyChangeMenu::add_key(int id, const wchar_t *button_name, const std::string &setting_name) +{ + key_setting *k = new key_setting; + k->id = id; + + k->button_name = button_name; + k->setting_name = setting_name; + k->key = getKeySetting(k->setting_name.c_str()); + key_settings.push_back(k); +} + +void GUIKeyChangeMenu::init_keys() +{ + this->add_key(GUI_ID_KEY_FORWARD_BUTTON, wgettext("Forward"), "keymap_forward"); + this->add_key(GUI_ID_KEY_BACKWARD_BUTTON, wgettext("Backward"), "keymap_backward"); + this->add_key(GUI_ID_KEY_LEFT_BUTTON, wgettext("Left"), "keymap_left"); + this->add_key(GUI_ID_KEY_RIGHT_BUTTON, wgettext("Right"), "keymap_right"); + this->add_key(GUI_ID_KEY_USE_BUTTON, wgettext("Use"), "keymap_special1"); + this->add_key(GUI_ID_KEY_JUMP_BUTTON, wgettext("Jump"), "keymap_jump"); + this->add_key(GUI_ID_KEY_SNEAK_BUTTON, wgettext("Sneak"), "keymap_sneak"); + this->add_key(GUI_ID_KEY_DROP_BUTTON, wgettext("Drop"), "keymap_drop"); + this->add_key(GUI_ID_KEY_INVENTORY_BUTTON, wgettext("Inventory"), "keymap_inventory"); + this->add_key(GUI_ID_KEY_CHAT_BUTTON, wgettext("Chat"), "keymap_chat"); + this->add_key(GUI_ID_KEY_CMD_BUTTON, wgettext("Command"), "keymap_cmd"); + this->add_key(GUI_ID_KEY_CONSOLE_BUTTON, wgettext("Console"), "keymap_console"); + this->add_key(GUI_ID_KEY_FLY_BUTTON, wgettext("Toggle fly"), "keymap_freemove"); + this->add_key(GUI_ID_KEY_FAST_BUTTON, wgettext("Toggle fast"), "keymap_fastmove"); + this->add_key(GUI_ID_KEY_CINEMATIC_BUTTON, wgettext("Toggle Cinematic"), "keymap_cinematic"); + this->add_key(GUI_ID_KEY_NOCLIP_BUTTON, wgettext("Toggle noclip"), "keymap_noclip"); + this->add_key(GUI_ID_KEY_RANGE_BUTTON, wgettext("Range select"), "keymap_rangeselect"); + this->add_key(GUI_ID_KEY_DUMP_BUTTON, wgettext("Print stacks"), "keymap_print_debug_stacks"); +} + diff --git a/src/guiKeyChangeMenu.h b/src/guiKeyChangeMenu.h new file mode 100644 index 0000000..19a0762 --- /dev/null +++ b/src/guiKeyChangeMenu.h @@ -0,0 +1,79 @@ +/* + Minetest + Copyright (C) 2010-2013 celeron55, Perttu Ahola + Copyright (C) 2013 Ciaran Gultnieks + Copyright (C) 2013 teddydestodes + + 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. + */ + +#ifndef GUIKEYCHANGEMENU_HEADER +#define GUIKEYCHANGEMENU_HEADER + +#include "irrlichttypes_extrabloated.h" +#include "modalMenu.h" +#include "client.h" +#include "gettext.h" +#include "keycode.h" +#include +#include + +struct key_setting { + int id; + const wchar_t *button_name; + KeyPress key; + std::string setting_name; + gui::IGUIButton *button; +}; + + +class GUIKeyChangeMenu: public GUIModalMenu +{ +public: + GUIKeyChangeMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, + s32 id, IMenuManager *menumgr); + ~GUIKeyChangeMenu(); + + void removeChildren(); + /* + Remove and re-add (or reposition) stuff + */ + void regenerateGui(v2u32 screensize); + + void drawMenu(); + + bool acceptInput(); + + bool OnEvent(const SEvent& event); + +private: + + void init_keys(); + + bool resetMenu(); + + void add_key(int id, const wchar_t *button_name, const std::string &setting_name); + + bool shift_down; + + s32 activeKey; + + std::vector key_used; + gui::IGUIStaticText *key_used_text; + std::vector key_settings; +}; + +#endif + diff --git a/src/guiMainMenu.h b/src/guiMainMenu.h new file mode 100644 index 0000000..34362db --- /dev/null +++ b/src/guiMainMenu.h @@ -0,0 +1,63 @@ +/* +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. +*/ + +#ifndef GUIMAINMENU_HEADER +#define GUIMAINMENU_HEADER + +#include "irrlichttypes_extrabloated.h" +#include "modalMenu.h" +#include +#include + +enum +{ + TAB_SINGLEPLAYER=0, + TAB_MULTIPLAYER, + TAB_ADVANCED, + TAB_SETTINGS, + TAB_CREDITS +}; + +struct MainMenuData +{ + // Client options + std::string servername; + std::string serverdescription; + std::string address; + std::string port; + std::string name; + std::string password; + + // Server options + bool enable_public; + int selected_world; + bool simple_singleplayer_mode; + + //error handling + std::string errormessage; + MainMenuData(): + enable_public(false), + selected_world(0), + simple_singleplayer_mode(false), + errormessage("") + {} +}; + +#endif + diff --git a/src/guiPasswordChange.cpp b/src/guiPasswordChange.cpp new file mode 100644 index 0000000..0e19f57 --- /dev/null +++ b/src/guiPasswordChange.cpp @@ -0,0 +1,265 @@ +/* +Part of Minetest +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2013 Ciaran Gultnieks + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "guiPasswordChange.h" +#include "debug.h" +#include "serialization.h" +#include +#include +#include +#include +#include +#include + +#include "gettext.h" + +const int ID_oldPassword = 256; +const int ID_newPassword1 = 257; +const int ID_newPassword2 = 258; +const int ID_change = 259; +const int ID_message = 260; + +GUIPasswordChange::GUIPasswordChange(gui::IGUIEnvironment* env, + gui::IGUIElement* parent, s32 id, + IMenuManager *menumgr, + Client* client +): + GUIModalMenu(env, parent, id, menumgr), + m_client(client) +{ +} + +GUIPasswordChange::~GUIPasswordChange() +{ + removeChildren(); +} + +void GUIPasswordChange::removeChildren() +{ + { + gui::IGUIElement *e = getElementFromId(ID_oldPassword); + if(e != NULL) + e->remove(); + } + { + gui::IGUIElement *e = getElementFromId(ID_newPassword1); + if(e != NULL) + e->remove(); + } + { + gui::IGUIElement *e = getElementFromId(ID_newPassword2); + if(e != NULL) + e->remove(); + } + { + gui::IGUIElement *e = getElementFromId(ID_change); + if(e != NULL) + e->remove(); + } +} + +void GUIPasswordChange::regenerateGui(v2u32 screensize) +{ + /* + Remove stuff + */ + removeChildren(); + + /* + Calculate new sizes and positions + */ + core::rect rect( + screensize.X/2 - 580/2, + screensize.Y/2 - 300/2, + screensize.X/2 + 580/2, + screensize.Y/2 + 300/2 + ); + + DesiredRect = rect; + recalculateAbsolutePosition(false); + + v2s32 size = rect.getSize(); + v2s32 topleft_client(40, 0); + + const wchar_t *text; + + /* + Add stuff + */ + s32 ypos = 50; + { + core::rect rect(0, 0, 150, 20); + rect += topleft_client + v2s32(25, ypos+6); + text = wgettext("Old Password"); + Environment->addStaticText(text, rect, false, true, this, -1); + delete[] text; + } + { + core::rect rect(0, 0, 230, 30); + rect += topleft_client + v2s32(160, ypos); + gui::IGUIEditBox *e = + Environment->addEditBox(L"", rect, true, this, ID_oldPassword); + Environment->setFocus(e); + e->setPasswordBox(true); + } + ypos += 50; + { + core::rect rect(0, 0, 150, 20); + rect += topleft_client + v2s32(25, ypos+6); + text = wgettext("New Password"); + Environment->addStaticText(text, rect, false, true, this, -1); + delete[] text; + } + { + core::rect rect(0, 0, 230, 30); + rect += topleft_client + v2s32(160, ypos); + gui::IGUIEditBox *e = + Environment->addEditBox(L"", rect, true, this, ID_newPassword1); + e->setPasswordBox(true); + } + ypos += 50; + { + core::rect rect(0, 0, 150, 20); + rect += topleft_client + v2s32(25, ypos+6); + text = wgettext("Confirm Password"); + Environment->addStaticText(text, rect, false, true, this, -1); + delete[] text; + } + { + core::rect rect(0, 0, 230, 30); + rect += topleft_client + v2s32(160, ypos); + gui::IGUIEditBox *e = + Environment->addEditBox(L"", rect, true, this, ID_newPassword2); + e->setPasswordBox(true); + } + + ypos += 50; + { + core::rect rect(0, 0, 140, 30); + rect = rect + v2s32(size.X/2-140/2, ypos); + text = wgettext("Change"); + Environment->addButton(rect, this, ID_change, text); + delete[] text; + } + + ypos += 50; + { + core::rect rect(0, 0, 300, 20); + rect += topleft_client + v2s32(35, ypos); + text = wgettext("Passwords do not match!"); + IGUIElement *e = + Environment->addStaticText( + text, + rect, false, true, this, ID_message); + e->setVisible(false); + delete[] text; + } +} + +void GUIPasswordChange::drawMenu() +{ + gui::IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + video::IVideoDriver* driver = Environment->getVideoDriver(); + + video::SColor bgcolor(140,0,0,0); + driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); + + gui::IGUIElement::draw(); +} + +bool GUIPasswordChange::acceptInput() +{ + std::wstring oldpass; + std::wstring newpass; + gui::IGUIElement *e; + e = getElementFromId(ID_oldPassword); + if(e != NULL) + oldpass = e->getText(); + e = getElementFromId(ID_newPassword1); + if(e != NULL) + newpass = e->getText(); + e = getElementFromId(ID_newPassword2); + if(e != NULL && newpass != e->getText()) + { + e = getElementFromId(ID_message); + if(e != NULL) + e->setVisible(true); + return false; + } + m_client->sendChangePassword(oldpass, newpass); + return true; +} + +bool GUIPasswordChange::OnEvent(const SEvent& event) +{ + if(event.EventType==EET_KEY_INPUT_EVENT) + { + if(event.KeyInput.Key==KEY_ESCAPE && event.KeyInput.PressedDown) + { + quitMenu(); + return true; + } + if(event.KeyInput.Key==KEY_RETURN && event.KeyInput.PressedDown) + { + if(acceptInput()) + quitMenu(); + return true; + } + } + if(event.EventType==EET_GUI_EVENT) + { + if(event.GUIEvent.EventType==gui::EGET_ELEMENT_FOCUS_LOST + && isVisible()) + { + if(!canTakeFocus(event.GUIEvent.Element)) + { + dstream<<"GUIPasswordChange: Not allowing focus change." + <getID()) + { + case ID_change: + if(acceptInput()) + quitMenu(); + return true; + } + } + if(event.GUIEvent.EventType==gui::EGET_EDITBOX_ENTER) + { + switch(event.GUIEvent.Caller->getID()) + { + case ID_oldPassword: + case ID_newPassword1: + case ID_newPassword2: + if(acceptInput()) + quitMenu(); + return true; + } + } + } + + return Parent ? Parent->OnEvent(event) : false; +} + diff --git a/src/guiPasswordChange.h b/src/guiPasswordChange.h new file mode 100644 index 0000000..aecc707 --- /dev/null +++ b/src/guiPasswordChange.h @@ -0,0 +1,54 @@ +/* +Part of Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola +Copyright (C) 2013 Ciaran Gultnieks + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef GUIPASSWORDCHANGE_HEADER +#define GUIPASSWORDCHANGE_HEADER + +#include "irrlichttypes_extrabloated.h" +#include "modalMenu.h" +#include "client.h" +#include + +class GUIPasswordChange : public GUIModalMenu +{ +public: + GUIPasswordChange(gui::IGUIEnvironment* env, + gui::IGUIElement* parent, s32 id, + IMenuManager *menumgr, + Client* client); + ~GUIPasswordChange(); + + void removeChildren(); + /* + Remove and re-add (or reposition) stuff + */ + void regenerateGui(v2u32 screensize); + + void drawMenu(); + + bool acceptInput(); + + bool OnEvent(const SEvent& event); + +private: + Client* m_client; + +}; + +#endif + diff --git a/src/guiTable.cpp b/src/guiTable.cpp new file mode 100644 index 0000000..a7a53f5 --- /dev/null +++ b/src/guiTable.cpp @@ -0,0 +1,1244 @@ +/* +Minetest +Copyright (C) 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. +*/ + + +#include "guiTable.h" +#include +#include +#include +#include +#include +#include +#include +#include "debug.h" +#include "log.h" +#include "client/tile.h" +#include "gettime.h" +#include "util/string.h" +#include "util/numeric.h" +#include "util/string.h" // for parseColorString() +#include "main.h" +#include "settings.h" // for settings +#include "porting.h" // for dpi + +/* + GUITable +*/ + +GUITable::GUITable(gui::IGUIEnvironment *env, + gui::IGUIElement* parent, s32 id, + core::rect rectangle, + ISimpleTextureSource *tsrc +): + gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), + m_tsrc(tsrc), + m_is_textlist(false), + m_has_tree_column(false), + m_selected(-1), + m_sel_column(0), + m_sel_doubleclick(false), + m_keynav_time(0), + m_keynav_buffer(L""), + m_border(true), + m_color(255, 255, 255, 255), + m_background(255, 0, 0, 0), + m_highlight(255, 70, 100, 50), + m_highlight_text(255, 255, 255, 255), + m_rowheight(1), + m_font(NULL), + m_scrollbar(NULL) +{ + assert(tsrc != NULL); + + gui::IGUISkin* skin = Environment->getSkin(); + + m_font = skin->getFont(); + if (m_font) { + m_font->grab(); + m_rowheight = m_font->getDimension(L"A").Height + 4; + m_rowheight = MYMAX(m_rowheight, 1); + } + + const s32 s = skin->getSize(gui::EGDS_SCROLLBAR_SIZE); + m_scrollbar = Environment->addScrollBar(false, + core::rect(RelativeRect.getWidth() - s, + 0, + RelativeRect.getWidth(), + RelativeRect.getHeight()), + this, -1); + m_scrollbar->setSubElement(true); + m_scrollbar->setTabStop(false); + m_scrollbar->setAlignment(gui::EGUIA_LOWERRIGHT, gui::EGUIA_LOWERRIGHT, + gui::EGUIA_UPPERLEFT, gui::EGUIA_LOWERRIGHT); + m_scrollbar->setVisible(false); + m_scrollbar->setPos(0); + + setTabStop(true); + setTabOrder(-1); + updateAbsolutePosition(); + + core::rect relative_rect = m_scrollbar->getRelativePosition(); + s32 width = (relative_rect.getWidth()/(2.0/3.0)) * porting::getDisplayDensity() * + g_settings->getFloat("gui_scaling"); + m_scrollbar->setRelativePosition(core::rect( + relative_rect.LowerRightCorner.X-width,relative_rect.UpperLeftCorner.Y, + relative_rect.LowerRightCorner.X,relative_rect.LowerRightCorner.Y + )); +} + +GUITable::~GUITable() +{ + for (size_t i = 0; i < m_rows.size(); ++i) + delete[] m_rows[i].cells; + + if (m_font) + m_font->drop(); + + m_scrollbar->remove(); +} + +GUITable::Option GUITable::splitOption(const std::string &str) +{ + size_t equal_pos = str.find('='); + if (equal_pos == std::string::npos) + return GUITable::Option(str, ""); + else + return GUITable::Option(str.substr(0, equal_pos), + str.substr(equal_pos + 1)); +} + +void GUITable::setTextList(const std::vector &content, + bool transparent) +{ + clear(); + + if (transparent) { + m_background.setAlpha(0); + m_border = false; + } + + m_is_textlist = true; + + s32 empty_string_index = allocString(""); + + m_rows.resize(content.size()); + for (s32 i = 0; i < (s32) content.size(); ++i) { + Row *row = &m_rows[i]; + row->cells = new Cell[1]; + row->cellcount = 1; + row->indent = 0; + row->visible_index = i; + m_visible_rows.push_back(i); + + Cell *cell = row->cells; + cell->xmin = 0; + cell->xmax = 0x7fff; // something large enough + cell->xpos = 6; + cell->content_type = COLUMN_TYPE_TEXT; + cell->content_index = empty_string_index; + cell->tooltip_index = empty_string_index; + cell->color.set(255, 255, 255, 255); + cell->color_defined = false; + cell->reported_column = 1; + + // parse row content (color) + const std::string &s = content[i]; + if (s[0] == '#' && s[1] == '#') { + // double # to escape + cell->content_index = allocString(s.substr(2)); + } + else if (s[0] == '#' && s.size() >= 7 && + parseColorString( + s.substr(0,7), cell->color, false)) { + // single # for color + cell->color_defined = true; + cell->content_index = allocString(s.substr(7)); + } + else { + // no #, just text + cell->content_index = allocString(s); + } + + } + + allocationComplete(); + + // Clamp scroll bar position + updateScrollBar(); +} + +void GUITable::setTable(const TableOptions &options, + const TableColumns &columns, + std::vector &content) +{ + clear(); + + // Naming conventions: + // i is always a row index, 0-based + // j is always a column index, 0-based + // k is another index, for example an option index + + // Handle a stupid error case... (issue #1187) + if (columns.empty()) { + TableColumn text_column; + text_column.type = "text"; + TableColumns new_columns; + new_columns.push_back(text_column); + setTable(options, new_columns, content); + return; + } + + // Handle table options + video::SColor default_color(255, 255, 255, 255); + s32 opendepth = 0; + for (size_t k = 0; k < options.size(); ++k) { + const std::string &name = options[k].name; + const std::string &value = options[k].value; + if (name == "color") + parseColorString(value, m_color, false); + else if (name == "background") + parseColorString(value, m_background, false); + else if (name == "border") + m_border = is_yes(value); + else if (name == "highlight") + parseColorString(value, m_highlight, false); + else if (name == "highlight_text") + parseColorString(value, m_highlight_text, false); + else if (name == "opendepth") + opendepth = stoi(value); + else + errorstream<<"Invalid table option: \""<= 1); + // rowcount = ceil(cellcount / colcount) but use integer arithmetic + s32 rowcount = (content.size() + colcount - 1) / colcount; + assert(rowcount >= 0); + // Append empty strings to content if there is an incomplete row + s32 cellcount = rowcount * colcount; + while (content.size() < (u32) cellcount) + content.push_back(""); + + // Create temporary rows (for processing columns) + struct TempRow { + // Current horizontal position (may different between rows due + // to indent/tree columns, or text/image columns with width<0) + s32 x; + // Tree indentation level + s32 indent; + // Next cell: Index into m_strings or m_images + s32 content_index; + // Next cell: Width in pixels + s32 content_width; + // Vector of completed cells in this row + std::vector cells; + // Stores colors and how long they last (maximum column index) + std::vector > colors; + + TempRow(): x(0), indent(0), content_index(0), content_width(0) {} + }; + TempRow *rows = new TempRow[rowcount]; + + // Get em width. Pedantically speaking, the width of "M" is not + // necessarily the same as the em width, but whatever, close enough. + s32 em = 6; + if (m_font) + em = m_font->getDimension(L"M").Width; + + s32 default_tooltip_index = allocString(""); + + std::map active_image_indices; + + // Process content in column-major order + for (s32 j = 0; j < colcount; ++j) { + // Check column type + ColumnType columntype = COLUMN_TYPE_TEXT; + if (columns[j].type == "text") + columntype = COLUMN_TYPE_TEXT; + else if (columns[j].type == "image") + columntype = COLUMN_TYPE_IMAGE; + else if (columns[j].type == "color") + columntype = COLUMN_TYPE_COLOR; + else if (columns[j].type == "indent") + columntype = COLUMN_TYPE_INDENT; + else if (columns[j].type == "tree") + columntype = COLUMN_TYPE_TREE; + else + errorstream<<"Invalid table column type: \"" + <colors.empty() && row->colors.back().second < j) + row->colors.pop_back(); + } + } + + // Make template for new cells + Cell newcell; + memset(&newcell, 0, sizeof newcell); + newcell.content_type = columntype; + newcell.tooltip_index = tooltip_index; + newcell.reported_column = j+1; + + if (columntype == COLUMN_TYPE_TEXT) { + // Find right edge of column + s32 xmax = 0; + for (s32 i = 0; i < rowcount; ++i) { + TempRow *row = &rows[i]; + row->content_index = allocString(content[i * colcount + j]); + const core::stringw &text = m_strings[row->content_index]; + row->content_width = m_font ? + m_font->getDimension(text.c_str()).Width : 0; + row->content_width = MYMAX(row->content_width, width); + s32 row_xmax = row->x + padding + row->content_width; + xmax = MYMAX(xmax, row_xmax); + } + // Add a new cell (of text type) to each row + for (s32 i = 0; i < rowcount; ++i) { + newcell.xmin = rows[i].x + padding; + alignContent(&newcell, xmax, rows[i].content_width, align); + newcell.content_index = rows[i].content_index; + newcell.color_defined = !rows[i].colors.empty(); + if (newcell.color_defined) + newcell.color = rows[i].colors.back().first; + rows[i].cells.push_back(newcell); + rows[i].x = newcell.xmax; + } + } + else if (columntype == COLUMN_TYPE_IMAGE) { + // Find right edge of column + s32 xmax = 0; + for (s32 i = 0; i < rowcount; ++i) { + TempRow *row = &rows[i]; + row->content_index = -1; + + // Find content_index. Image indices are defined in + // column options so check active_image_indices. + s32 image_index = stoi(content[i * colcount + j]); + std::map::iterator image_iter = + active_image_indices.find(image_index); + if (image_iter != active_image_indices.end()) + row->content_index = image_iter->second; + + // Get texture object (might be NULL) + video::ITexture *image = NULL; + if (row->content_index >= 0) + image = m_images[row->content_index]; + + // Get content width and update xmax + row->content_width = image ? image->getOriginalSize().Width : 0; + row->content_width = MYMAX(row->content_width, width); + s32 row_xmax = row->x + padding + row->content_width; + xmax = MYMAX(xmax, row_xmax); + } + // Add a new cell (of image type) to each row + for (s32 i = 0; i < rowcount; ++i) { + newcell.xmin = rows[i].x + padding; + alignContent(&newcell, xmax, rows[i].content_width, align); + newcell.content_index = rows[i].content_index; + rows[i].cells.push_back(newcell); + rows[i].x = newcell.xmax; + } + active_image_indices.clear(); + } + else if (columntype == COLUMN_TYPE_COLOR) { + for (s32 i = 0; i < rowcount; ++i) { + video::SColor cellcolor(255, 255, 255, 255); + if (parseColorString(content[i * colcount + j], cellcolor, true)) + rows[i].colors.push_back(std::make_pair(cellcolor, j+span)); + } + } + else if (columntype == COLUMN_TYPE_INDENT || + columntype == COLUMN_TYPE_TREE) { + // For column type "tree", reserve additional space for +/- + // Also enable special processing for treeview-type tables + s32 content_width = 0; + if (columntype == COLUMN_TYPE_TREE) { + content_width = m_font ? m_font->getDimension(L"+").Width : 0; + m_has_tree_column = true; + } + // Add a new cell (of indent or tree type) to each row + for (s32 i = 0; i < rowcount; ++i) { + TempRow *row = &rows[i]; + + s32 indentlevel = stoi(content[i * colcount + j]); + indentlevel = MYMAX(indentlevel, 0); + if (columntype == COLUMN_TYPE_TREE) + row->indent = indentlevel; + + newcell.xmin = row->x + padding; + newcell.xpos = newcell.xmin + indentlevel * width; + newcell.xmax = newcell.xpos + content_width; + newcell.content_index = 0; + newcell.color_defined = !rows[i].colors.empty(); + if (newcell.color_defined) + newcell.color = rows[i].colors.back().first; + row->cells.push_back(newcell); + row->x = newcell.xmax; + } + } + } + + // Copy temporary rows to not so temporary rows + if (rowcount >= 1) { + m_rows.resize(rowcount); + for (s32 i = 0; i < rowcount; ++i) { + Row *row = &m_rows[i]; + row->cellcount = rows[i].cells.size(); + row->cells = new Cell[row->cellcount]; + memcpy((void*) row->cells, (void*) &rows[i].cells[0], + row->cellcount * sizeof(Cell)); + row->indent = rows[i].indent; + row->visible_index = i; + m_visible_rows.push_back(i); + } + } + + if (m_has_tree_column) { + // Treeview: convert tree to indent cells on leaf rows + for (s32 i = 0; i < rowcount; ++i) { + if (i == rowcount-1 || m_rows[i].indent >= m_rows[i+1].indent) + for (s32 j = 0; j < m_rows[i].cellcount; ++j) + if (m_rows[i].cells[j].content_type == COLUMN_TYPE_TREE) + m_rows[i].cells[j].content_type = COLUMN_TYPE_INDENT; + } + + // Treeview: close rows according to opendepth option + std::set opened_trees; + for (s32 i = 0; i < rowcount; ++i) + if (m_rows[i].indent < opendepth) + opened_trees.insert(i); + setOpenedTrees(opened_trees); + } + + // Delete temporary information used only during setTable() + delete[] rows; + allocationComplete(); + + // Clamp scroll bar position + updateScrollBar(); +} + +void GUITable::clear() +{ + // Clean up cells and rows + for (size_t i = 0; i < m_rows.size(); ++i) + delete[] m_rows[i].cells; + m_rows.clear(); + m_visible_rows.clear(); + + // Get colors from skin + gui::IGUISkin *skin = Environment->getSkin(); + m_color = skin->getColor(gui::EGDC_BUTTON_TEXT); + m_background = skin->getColor(gui::EGDC_3D_HIGH_LIGHT); + m_highlight = skin->getColor(gui::EGDC_HIGH_LIGHT); + m_highlight_text = skin->getColor(gui::EGDC_HIGH_LIGHT_TEXT); + + // Reset members + m_is_textlist = false; + m_has_tree_column = false; + m_selected = -1; + m_sel_column = 0; + m_sel_doubleclick = false; + m_keynav_time = 0; + m_keynav_buffer = L""; + m_border = true; + m_strings.clear(); + m_images.clear(); + m_alloc_strings.clear(); + m_alloc_images.clear(); +} + +std::string GUITable::checkEvent() +{ + s32 sel = getSelected(); + assert(sel >= 0); + + if (sel == 0) { + return "INV"; + } + + std::ostringstream os(std::ios::binary); + if (m_sel_doubleclick) { + os<<"DCL:"; + m_sel_doubleclick = false; + } + else { + os<<"CHG:"; + } + os<= 0 && m_selected < (s32) m_visible_rows.size()); + return m_visible_rows[m_selected] + 1; +} + +void GUITable::setSelected(s32 index) +{ + m_selected = -1; + m_sel_column = 0; + m_sel_doubleclick = false; + + --index; + + s32 rowcount = m_rows.size(); + + if (index >= rowcount) + index = rowcount - 1; + while (index >= 0 && m_rows[index].visible_index < 0) + --index; + if (index >= 0) { + m_selected = m_rows[index].visible_index; + assert(m_selected >= 0 && m_selected < (s32) m_visible_rows.size()); + } + + autoScroll(); +} + +GUITable::DynamicData GUITable::getDynamicData() const +{ + DynamicData dyndata; + dyndata.selected = getSelected(); + dyndata.scrollpos = m_scrollbar->getPos(); + dyndata.keynav_time = m_keynav_time; + dyndata.keynav_buffer = m_keynav_buffer; + if (m_has_tree_column) + getOpenedTrees(dyndata.opened_trees); + return dyndata; +} + +void GUITable::setDynamicData(const DynamicData &dyndata) +{ + if (m_has_tree_column) + setOpenedTrees(dyndata.opened_trees); + + m_keynav_time = dyndata.keynav_time; + m_keynav_buffer = dyndata.keynav_buffer; + + m_scrollbar->setPos(dyndata.scrollpos); + + setSelected(dyndata.selected); + m_sel_column = 0; + m_sel_doubleclick = false; +} + +const c8* GUITable::getTypeName() const +{ + return "GUITable"; +} + +void GUITable::updateAbsolutePosition() +{ + IGUIElement::updateAbsolutePosition(); + updateScrollBar(); +} + +void GUITable::draw() +{ + if (!IsVisible) + return; + + gui::IGUISkin *skin = Environment->getSkin(); + + // draw background + + bool draw_background = m_background.getAlpha() > 0; + if (m_border) + skin->draw3DSunkenPane(this, m_background, + true, draw_background, + AbsoluteRect, &AbsoluteClippingRect); + else if (draw_background) + skin->draw2DRectangle(this, m_background, + AbsoluteRect, &AbsoluteClippingRect); + + // get clipping rect + + core::rect client_clip(AbsoluteRect); + client_clip.UpperLeftCorner.Y += 1; + client_clip.UpperLeftCorner.X += 1; + client_clip.LowerRightCorner.Y -= 1; + client_clip.LowerRightCorner.X -= 1; + if (m_scrollbar->isVisible()) { + client_clip.LowerRightCorner.X = + m_scrollbar->getAbsolutePosition().UpperLeftCorner.X; + } + client_clip.clipAgainst(AbsoluteClippingRect); + + // draw visible rows + + s32 scrollpos = m_scrollbar->getPos(); + s32 row_min = scrollpos / m_rowheight; + s32 row_max = (scrollpos + AbsoluteRect.getHeight() - 1) + / m_rowheight + 1; + row_max = MYMIN(row_max, (s32) m_visible_rows.size()); + + core::rect row_rect(AbsoluteRect); + if (m_scrollbar->isVisible()) + row_rect.LowerRightCorner.X -= + skin->getSize(gui::EGDS_SCROLLBAR_SIZE); + row_rect.UpperLeftCorner.Y += row_min * m_rowheight - scrollpos; + row_rect.LowerRightCorner.Y = row_rect.UpperLeftCorner.Y + m_rowheight; + + for (s32 i = row_min; i < row_max; ++i) { + Row *row = &m_rows[m_visible_rows[i]]; + bool is_sel = i == m_selected; + video::SColor color = m_color; + + if (is_sel) { + skin->draw2DRectangle(this, m_highlight, row_rect, &client_clip); + color = m_highlight_text; + } + + for (s32 j = 0; j < row->cellcount; ++j) + drawCell(&row->cells[j], color, row_rect, client_clip); + + row_rect.UpperLeftCorner.Y += m_rowheight; + row_rect.LowerRightCorner.Y += m_rowheight; + } + + // Draw children + IGUIElement::draw(); +} + +void GUITable::drawCell(const Cell *cell, video::SColor color, + const core::rect &row_rect, + const core::rect &client_clip) +{ + if ((cell->content_type == COLUMN_TYPE_TEXT) + || (cell->content_type == COLUMN_TYPE_TREE)) { + + core::rect text_rect = row_rect; + text_rect.UpperLeftCorner.X = row_rect.UpperLeftCorner.X + + cell->xpos; + text_rect.LowerRightCorner.X = row_rect.UpperLeftCorner.X + + cell->xmax; + + if (cell->color_defined) + color = cell->color; + + if (m_font) { + if (cell->content_type == COLUMN_TYPE_TEXT) + m_font->draw(m_strings[cell->content_index], + text_rect, color, + false, true, &client_clip); + else // tree + m_font->draw(cell->content_index ? L"+" : L"-", + text_rect, color, + false, true, &client_clip); + } + } + else if (cell->content_type == COLUMN_TYPE_IMAGE) { + + if (cell->content_index < 0) + return; + + video::IVideoDriver *driver = Environment->getVideoDriver(); + video::ITexture *image = m_images[cell->content_index]; + + if (image) { + core::position2d dest_pos = + row_rect.UpperLeftCorner; + dest_pos.X += cell->xpos; + core::rect source_rect( + core::position2d(0, 0), + image->getOriginalSize()); + s32 imgh = source_rect.LowerRightCorner.Y; + s32 rowh = row_rect.getHeight(); + if (imgh < rowh) + dest_pos.Y += (rowh - imgh) / 2; + else + source_rect.LowerRightCorner.Y = rowh; + + video::SColor color(255, 255, 255, 255); + + driver->draw2DImage(image, dest_pos, source_rect, + &client_clip, color, true); + } + } +} + +bool GUITable::OnEvent(const SEvent &event) +{ + if (!isEnabled()) + return IGUIElement::OnEvent(event); + + if (event.EventType == EET_KEY_INPUT_EVENT) { + if (event.KeyInput.PressedDown && ( + event.KeyInput.Key == KEY_DOWN || + event.KeyInput.Key == KEY_UP || + event.KeyInput.Key == KEY_HOME || + event.KeyInput.Key == KEY_END || + event.KeyInput.Key == KEY_NEXT || + event.KeyInput.Key == KEY_PRIOR)) { + s32 offset = 0; + switch (event.KeyInput.Key) { + case KEY_DOWN: + offset = 1; + break; + case KEY_UP: + offset = -1; + break; + case KEY_HOME: + offset = - (s32) m_visible_rows.size(); + break; + case KEY_END: + offset = m_visible_rows.size(); + break; + case KEY_NEXT: + offset = AbsoluteRect.getHeight() / m_rowheight; + break; + case KEY_PRIOR: + offset = - (s32) (AbsoluteRect.getHeight() / m_rowheight); + break; + default: + break; + } + s32 old_selected = m_selected; + s32 rowcount = m_visible_rows.size(); + if (rowcount != 0) { + m_selected = rangelim(m_selected + offset, 0, rowcount-1); + autoScroll(); + } + + if (m_selected != old_selected) + sendTableEvent(0, false); + + return true; + } + else if (event.KeyInput.PressedDown && ( + event.KeyInput.Key == KEY_LEFT || + event.KeyInput.Key == KEY_RIGHT)) { + // Open/close subtree via keyboard + if (m_selected >= 0) { + int dir = event.KeyInput.Key == KEY_LEFT ? -1 : 1; + toggleVisibleTree(m_selected, dir, true); + } + return true; + } + else if (!event.KeyInput.PressedDown && ( + event.KeyInput.Key == KEY_RETURN || + event.KeyInput.Key == KEY_SPACE)) { + sendTableEvent(0, true); + return true; + } + else if (event.KeyInput.Key == KEY_ESCAPE || + event.KeyInput.Key == KEY_SPACE) { + // pass to parent + } + else if (event.KeyInput.PressedDown && event.KeyInput.Char) { + // change selection based on text as it is typed + s32 now = getTimeMs(); + if (now - m_keynav_time >= 500) + m_keynav_buffer = L""; + m_keynav_time = now; + + // add to key buffer if not a key repeat + if (!(m_keynav_buffer.size() == 1 && + m_keynav_buffer[0] == event.KeyInput.Char)) { + m_keynav_buffer.append(event.KeyInput.Char); + } + + // find the selected item, starting at the current selection + // don't change selection if the key buffer matches the current item + s32 old_selected = m_selected; + s32 start = MYMAX(m_selected, 0); + s32 rowcount = m_visible_rows.size(); + for (s32 k = 1; k < rowcount; ++k) { + s32 current = start + k; + if (current >= rowcount) + current -= rowcount; + if (doesRowStartWith(getRow(current), m_keynav_buffer)) { + m_selected = current; + break; + } + } + autoScroll(); + if (m_selected != old_selected) + sendTableEvent(0, false); + + return true; + } + } + if (event.EventType == EET_MOUSE_INPUT_EVENT) { + core::position2d p(event.MouseInput.X, event.MouseInput.Y); + + if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) { + m_scrollbar->setPos(m_scrollbar->getPos() + + (event.MouseInput.Wheel < 0 ? -3 : 3) * + - (s32) m_rowheight / 2); + return true; + } + + // Find hovered row and cell + bool really_hovering = false; + s32 row_i = getRowAt(p.Y, really_hovering); + const Cell *cell = NULL; + if (really_hovering) { + s32 cell_j = getCellAt(p.X, row_i); + if (cell_j >= 0) + cell = &(getRow(row_i)->cells[cell_j]); + } + + // Update tooltip + setToolTipText(cell ? m_strings[cell->tooltip_index].c_str() : L""); + + // Fix for #1567/#1806: + // IGUIScrollBar passes double click events to its parent, + // which we don't want. Detect this case and discard the event + if (event.MouseInput.Event != EMIE_MOUSE_MOVED && + m_scrollbar->isVisible() && + m_scrollbar->isPointInside(p)) + return true; + + if (event.MouseInput.isLeftPressed() && + (isPointInside(p) || + event.MouseInput.Event == EMIE_MOUSE_MOVED)) { + s32 sel_column = 0; + bool sel_doubleclick = (event.MouseInput.Event + == EMIE_LMOUSE_DOUBLE_CLICK); + bool plusminus_clicked = false; + + // For certain events (left click), report column + // Also open/close subtrees when the +/- is clicked + if (cell && ( + event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN || + event.MouseInput.Event == EMIE_LMOUSE_DOUBLE_CLICK || + event.MouseInput.Event == EMIE_LMOUSE_TRIPLE_CLICK)) { + sel_column = cell->reported_column; + if (cell->content_type == COLUMN_TYPE_TREE) + plusminus_clicked = true; + } + + if (plusminus_clicked) { + if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { + toggleVisibleTree(row_i, 0, false); + } + } + else { + // Normal selection + s32 old_selected = m_selected; + m_selected = row_i; + autoScroll(); + + if (m_selected != old_selected || + sel_column >= 1 || + sel_doubleclick) { + sendTableEvent(sel_column, sel_doubleclick); + } + } + } + return true; + } + if (event.EventType == EET_GUI_EVENT && + event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED && + event.GUIEvent.Caller == m_scrollbar) { + // Don't pass events from our scrollbar to the parent + return true; + } + + return IGUIElement::OnEvent(event); +} + +/******************************************************************************/ +/* GUITable helper functions */ +/******************************************************************************/ + +s32 GUITable::allocString(const std::string &text) +{ + std::map::iterator it = m_alloc_strings.find(text); + if (it == m_alloc_strings.end()) { + s32 id = m_strings.size(); + std::wstring wtext = narrow_to_wide(text); + m_strings.push_back(core::stringw(wtext.c_str())); + m_alloc_strings.insert(std::make_pair(text, id)); + return id; + } + else { + return it->second; + } +} + +s32 GUITable::allocImage(const std::string &imagename) +{ + std::map::iterator it = m_alloc_images.find(imagename); + if (it == m_alloc_images.end()) { + s32 id = m_images.size(); + m_images.push_back(m_tsrc->getTexture(imagename)); + m_alloc_images.insert(std::make_pair(imagename, id)); + return id; + } + else { + return it->second; + } +} + +void GUITable::allocationComplete() +{ + // Called when done with creating rows and cells from table data, + // i.e. when allocString and allocImage won't be called anymore + m_alloc_strings.clear(); + m_alloc_images.clear(); +} + +const GUITable::Row* GUITable::getRow(s32 i) const +{ + if (i >= 0 && i < (s32) m_visible_rows.size()) + return &m_rows[m_visible_rows[i]]; + else + return NULL; +} + +bool GUITable::doesRowStartWith(const Row *row, const core::stringw &str) const +{ + if (row == NULL) + return false; + + for (s32 j = 0; j < row->cellcount; ++j) { + Cell *cell = &row->cells[j]; + if (cell->content_type == COLUMN_TYPE_TEXT) { + const core::stringw &cellstr = m_strings[cell->content_index]; + if (cellstr.size() >= str.size() && + str.equals_ignore_case(cellstr.subString(0, str.size()))) + return true; + } + } + return false; +} + +s32 GUITable::getRowAt(s32 y, bool &really_hovering) const +{ + really_hovering = false; + + s32 rowcount = m_visible_rows.size(); + if (rowcount == 0) + return -1; + + // Use arithmetic to find row + s32 rel_y = y - AbsoluteRect.UpperLeftCorner.Y - 1; + s32 i = (rel_y + m_scrollbar->getPos()) / m_rowheight; + + if (i >= 0 && i < rowcount) { + really_hovering = true; + return i; + } + else if (i < 0) + return 0; + else + return rowcount - 1; + +} + +s32 GUITable::getCellAt(s32 x, s32 row_i) const +{ + const Row *row = getRow(row_i); + if (row == NULL) + return -1; + + // Use binary search to find cell in row + s32 rel_x = x - AbsoluteRect.UpperLeftCorner.X - 1; + s32 jmin = 0; + s32 jmax = row->cellcount - 1; + while (jmin < jmax) { + s32 pivot = jmin + (jmax - jmin) / 2; + assert(pivot >= 0 && pivot < row->cellcount); + const Cell *cell = &row->cells[pivot]; + + if (rel_x >= cell->xmin && rel_x <= cell->xmax) + return pivot; + else if (rel_x < cell->xmin) + jmax = pivot - 1; + else + jmin = pivot + 1; + } + + if (jmin >= 0 && jmin < row->cellcount && + rel_x >= row->cells[jmin].xmin && + rel_x <= row->cells[jmin].xmax) + return jmin; + else + return -1; +} + +void GUITable::autoScroll() +{ + if (m_selected >= 0) { + s32 pos = m_scrollbar->getPos(); + s32 maxpos = m_selected * m_rowheight; + s32 minpos = maxpos - (AbsoluteRect.getHeight() - m_rowheight); + if (pos > maxpos) + m_scrollbar->setPos(maxpos); + else if (pos < minpos) + m_scrollbar->setPos(minpos); + } +} + +void GUITable::updateScrollBar() +{ + s32 totalheight = m_rowheight * m_visible_rows.size(); + s32 scrollmax = MYMAX(0, totalheight - AbsoluteRect.getHeight()); + m_scrollbar->setVisible(scrollmax > 0); + m_scrollbar->setMax(scrollmax); + m_scrollbar->setSmallStep(m_rowheight); + m_scrollbar->setLargeStep(2 * m_rowheight); +} + +void GUITable::sendTableEvent(s32 column, bool doubleclick) +{ + m_sel_column = column; + m_sel_doubleclick = doubleclick; + if (Parent) { + SEvent e; + memset(&e, 0, sizeof e); + e.EventType = EET_GUI_EVENT; + e.GUIEvent.Caller = this; + e.GUIEvent.Element = 0; + e.GUIEvent.EventType = gui::EGET_TABLE_CHANGED; + Parent->OnEvent(e); + } +} + +void GUITable::getOpenedTrees(std::set &opened_trees) const +{ + opened_trees.clear(); + s32 rowcount = m_rows.size(); + for (s32 i = 0; i < rowcount - 1; ++i) { + if (m_rows[i].indent < m_rows[i+1].indent && + m_rows[i+1].visible_index != -2) + opened_trees.insert(i); + } +} + +void GUITable::setOpenedTrees(const std::set &opened_trees) +{ + s32 old_selected = getSelected(); + + std::vector parents; + std::vector closed_parents; + + m_visible_rows.clear(); + + for (size_t i = 0; i < m_rows.size(); ++i) { + Row *row = &m_rows[i]; + + // Update list of ancestors + while (!parents.empty() && m_rows[parents.back()].indent >= row->indent) + parents.pop_back(); + while (!closed_parents.empty() && + m_rows[closed_parents.back()].indent >= row->indent) + closed_parents.pop_back(); + + assert(closed_parents.size() <= parents.size()); + + if (closed_parents.empty()) { + // Visible row + row->visible_index = m_visible_rows.size(); + m_visible_rows.push_back(i); + } + else if (parents.back() == closed_parents.back()) { + // Invisible row, direct parent is closed + row->visible_index = -2; + } + else { + // Invisible row, direct parent is open, some ancestor is closed + row->visible_index = -1; + } + + // If not a leaf, add to parents list + if (i < m_rows.size()-1 && row->indent < m_rows[i+1].indent) { + parents.push_back(i); + + s32 content_index = 0; // "-", open + if (opened_trees.count(i) == 0) { + closed_parents.push_back(i); + content_index = 1; // "+", closed + } + + // Update all cells of type "tree" + for (s32 j = 0; j < row->cellcount; ++j) + if (row->cells[j].content_type == COLUMN_TYPE_TREE) + row->cells[j].content_index = content_index; + } + } + + updateScrollBar(); + + setSelected(old_selected); +} + +void GUITable::openTree(s32 to_open) +{ + std::set opened_trees; + getOpenedTrees(opened_trees); + opened_trees.insert(to_open); + setOpenedTrees(opened_trees); +} + +void GUITable::closeTree(s32 to_close) +{ + std::set opened_trees; + getOpenedTrees(opened_trees); + opened_trees.erase(to_close); + setOpenedTrees(opened_trees); +} + +// The following function takes a visible row index (hidden rows skipped) +// dir: -1 = left (close), 0 = auto (toggle), 1 = right (open) +void GUITable::toggleVisibleTree(s32 row_i, int dir, bool move_selection) +{ + // Check if the chosen tree is currently open + const Row *row = getRow(row_i); + if (row == NULL) + return; + + bool was_open = false; + for (s32 j = 0; j < row->cellcount; ++j) { + if (row->cells[j].content_type == COLUMN_TYPE_TREE) { + was_open = row->cells[j].content_index == 0; + break; + } + } + + // Check if the chosen tree should be opened + bool do_open = !was_open; + if (dir < 0) + do_open = false; + else if (dir > 0) + do_open = true; + + // Close or open the tree; the heavy lifting is done by setOpenedTrees + if (was_open && !do_open) + closeTree(m_visible_rows[row_i]); + else if (!was_open && do_open) + openTree(m_visible_rows[row_i]); + + // Change selected row if requested by caller, + // this is useful for keyboard navigation + if (move_selection) { + s32 sel = row_i; + if (was_open && do_open) { + // Move selection to first child + const Row *maybe_child = getRow(sel + 1); + if (maybe_child && maybe_child->indent > row->indent) + sel++; + } + else if (!was_open && !do_open) { + // Move selection to parent + assert(getRow(sel) != NULL); + while (sel > 0 && getRow(sel - 1)->indent >= row->indent) + sel--; + sel--; + if (sel < 0) // was root already selected? + sel = row_i; + } + if (sel != m_selected) { + m_selected = sel; + autoScroll(); + sendTableEvent(0, false); + } + } +} + +void GUITable::alignContent(Cell *cell, s32 xmax, s32 content_width, s32 align) +{ + // requires that cell.xmin, cell.xmax are properly set + // align = 0: left aligned, 1: centered, 2: right aligned, 3: inline + if (align == 0) { + cell->xpos = cell->xmin; + cell->xmax = xmax; + } + else if (align == 1) { + cell->xpos = (cell->xmin + xmax - content_width) / 2; + cell->xmax = xmax; + } + else if (align == 2) { + cell->xpos = xmax - content_width; + cell->xmax = xmax; + } + else { + // inline alignment: the cells of the column don't have an aligned + // right border, the right border of each cell depends on the content + cell->xpos = cell->xmin; + cell->xmax = cell->xmin + content_width; + } +} diff --git a/src/guiTable.h b/src/guiTable.h new file mode 100644 index 0000000..4d5b391 --- /dev/null +++ b/src/guiTable.h @@ -0,0 +1,269 @@ +/* +Minetest +Copyright (C) 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. +*/ + + +#ifndef GUITABLE_HEADER +#define GUITABLE_HEADER + +#include +#include +#include +#include +#include + +#include "irrlichttypes_extrabloated.h" + +class ISimpleTextureSource; + +/* + A table GUI element for GUIFormSpecMenu. + + Sends a EGET_TABLE_CHANGED event to the parent when + an item is selected or double-clicked. + Call checkEvent() to get info. + + Credits: The interface and implementation of this class are (very) + loosely based on the Irrlicht classes CGUITable and CGUIListBox. + CGUITable and CGUIListBox are licensed under the Irrlicht license; + they are Copyright (C) 2002-2012 Nikolaus Gebhardt +*/ +class GUITable : public gui::IGUIElement +{ +public: + /* + Stores dynamic data that should be preserved + when updating a formspec + */ + struct DynamicData + { + s32 selected; + s32 scrollpos; + s32 keynav_time; + core::stringw keynav_buffer; + std::set opened_trees; + + DynamicData() + { + selected = 0; + scrollpos = 0; + keynav_time = 0; + } + }; + + /* + An option of the form = + */ + struct Option + { + std::string name; + std::string value; + + Option(const std::string &name_, const std::string &value_) + { + name = name_; + value = value_; + } + }; + + /* + A list of options that concern the entire table + */ + typedef std::vector