Initial Commit
0
.classpath
Normal file
0
.settings/org.eclipse.jdt.core.prefs
Normal file
48
AndroidManifest.xml
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.pipsqueake.squeakecraftplus"
|
||||
|
||||
android:versionCode="1100"
|
||||
android:versionName="1.1.00"
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-sdk android:minSdkVersion="15"
|
||||
android:targetSdkVersion="22" />
|
||||
|
||||
<uses-feature android:glEsVersion="0x00010000" android:required="true"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
|
||||
|
||||
<activity android:name="com.pipsqueake.squeakecraft.MtNativeActivity"
|
||||
android:label="SqueakeCraft PLUS"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:screenOrientation="landscape"
|
||||
android:clearTaskOnLaunch="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.app.lib_name" android:value="minetest" />
|
||||
</activity>
|
||||
|
||||
|
||||
<activity android:name="com.pipsqueake.squeakecraft.MinetestTextEntry"
|
||||
android:theme="@style/Theme.Transparent"
|
||||
android:excludeFromRecents="true">
|
||||
</activity>
|
||||
<activity android:name="com.pipsqueake.squeakecraft.MinetestAssetCopy"
|
||||
android:theme="@style/Theme.Transparent"
|
||||
android:excludeFromRecents="true">
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
519
assets/Minetest/README.txt
Normal file
@ -0,0 +1,519 @@
|
||||
Minetest
|
||||
========
|
||||
|
||||
An InfiniMiner/Minecraft inspired game.
|
||||
|
||||
Copyright (c) 2010-2013 Perttu Ahola <celeron55@gmail.com>
|
||||
and contributors (see source file comments and the version control log)
|
||||
|
||||
In case you downloaded the source code:
|
||||
---------------------------------------
|
||||
If you downloaded the Minetest Engine source code in which this file is
|
||||
contained, you probably want to download the minetest_game project too:
|
||||
https://github.com/minetest/minetest_game/
|
||||
See the README.txt in it.
|
||||
|
||||
Further documentation
|
||||
----------------------
|
||||
- Website: http://minetest.net/
|
||||
- Wiki: http://wiki.minetest.net/
|
||||
- Developer wiki: http://dev.minetest.net/
|
||||
- Forum: http://forum.minetest.net/
|
||||
- Github: https://github.com/minetest/minetest/
|
||||
- doc/ directory of source distribution
|
||||
|
||||
This game is not finished
|
||||
--------------------------
|
||||
- Don't expect it to work as well as a finished game will.
|
||||
- Please report any bugs. When doing that, debug.txt is useful.
|
||||
|
||||
Default Controls
|
||||
-----------------
|
||||
- WASD: move
|
||||
- Space: jump/climb
|
||||
- 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
|
||||
|
||||
- Settable in the configuration file, see the section below.
|
||||
|
||||
Paths
|
||||
------
|
||||
$bin - Compiled binaries
|
||||
$share - Distributed read-only data
|
||||
$user - User-created modifiable data
|
||||
|
||||
Windows .zip / RUN_IN_PLACE source:
|
||||
$bin = bin
|
||||
$share = .
|
||||
$user = .
|
||||
|
||||
Linux installed:
|
||||
$bin = /usr/bin
|
||||
$share = /usr/share/minetest
|
||||
$user = ~/.minetest
|
||||
|
||||
OS X:
|
||||
$bin = Contents/MacOS
|
||||
$share = Contents/Resources
|
||||
$user = Contents/User OR ~/Library/Application Support/minetest
|
||||
|
||||
World directory
|
||||
----------------
|
||||
- Worlds can be found as separate folders in:
|
||||
$user/worlds/
|
||||
|
||||
Configuration file:
|
||||
-------------------
|
||||
- Default location:
|
||||
$user/minetest.conf
|
||||
- It is created by Minetest when it is ran the first time.
|
||||
- A specific file can be specified on the command line:
|
||||
--config <path-to-file>
|
||||
|
||||
Command-line options:
|
||||
---------------------
|
||||
- Use --help
|
||||
|
||||
Compiling on GNU/Linux:
|
||||
-----------------------
|
||||
|
||||
Install dependencies. Here's an example for Debian/Ubuntu:
|
||||
$ sudo apt-get install build-essential libirrlicht-dev cmake libbz2-dev libpng12-dev libjpeg8-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libjsoncpp-dev
|
||||
|
||||
You can install git for easily keeping your copy up to date.
|
||||
If you dont want git, read below on how to get the source without git.
|
||||
This is an example for installing git on Debian/Ubuntu:
|
||||
$ sudo apt-get install git-core
|
||||
|
||||
Download source (this is the URL to the latest of source repository, which might not work at all times) using git:
|
||||
$ git clone --depth 1 https://github.com/minetest/minetest.git
|
||||
$ cd minetest
|
||||
|
||||
Download minetest_game (otherwise only the "Minimal development test" game is available) using git:
|
||||
$ git clone --depth 1 https://github.com/minetest/minetest_game.git games/minetest_game
|
||||
|
||||
Download source, without using git:
|
||||
$ wget https://github.com/minetest/minetest/archive/master.tar.gz
|
||||
$ tar xf master.tar.gz
|
||||
$ cd minetest-master
|
||||
|
||||
Download minetest_game, without using git:
|
||||
$ cd games/
|
||||
$ wget https://github.com/minetest/minetest_game/archive/master.tar.gz
|
||||
$ tar xf master.tar.gz
|
||||
$ mv minetest_game-master minetest_game
|
||||
$ cd ..
|
||||
|
||||
Build a version that runs directly from the source directory:
|
||||
$ cmake . -DRUN_IN_PLACE=TRUE
|
||||
$ make -j <number of processors>
|
||||
|
||||
Run it:
|
||||
$ ./bin/minetest
|
||||
|
||||
- Use cmake . -LH to see all CMake options and their current state
|
||||
- If you want to install it system-wide (or are making a distribution package),
|
||||
you will want to use -DRUN_IN_PLACE=FALSE
|
||||
- You can build a bare server by specifying -DBUILD_SERVER=TRUE
|
||||
- You can disable the client build by specifying -DBUILD_CLIENT=FALSE
|
||||
- You can select between Release and Debug build by -DCMAKE_BUILD_TYPE=<Debug or Release>
|
||||
- Debug build is slower, but gives much more useful output in a debugger
|
||||
- If you build a bare server, you don't need to have Irrlicht installed.
|
||||
In that case use -DIRRLICHT_SOURCE_DIR=/the/irrlicht/source
|
||||
|
||||
CMake options
|
||||
-------------
|
||||
General options:
|
||||
|
||||
BUILD_CLIENT - Build Minetest client
|
||||
BUILD_SERVER - Build Minetest server
|
||||
CMAKE_BUILD_TYPE - Type of build (Release vs. Debug)
|
||||
Release - Release build
|
||||
Debug - Debug build
|
||||
SemiDebug - Partially optimized debug build
|
||||
RelWithDebInfo - Release build with Debug information
|
||||
MinSizeRel - Release build with -Os passed to compiler to make executable as small as possible
|
||||
ENABLE_CURL - Build with cURL; Enables use of online mod repo, public serverlist and remote media fetching via http
|
||||
ENABLE_FREETYPE - Build with FreeType2; Allows using TTF fonts
|
||||
ENABLE_GETTEXT - Build with Gettext; Allows using translations
|
||||
ENABLE_GLES - Search for Open GLES headers & libraries and use them
|
||||
ENABLE_LEVELDB - Build with LevelDB; Enables use of LevelDB map backend (faster than SQLite3)
|
||||
ENABLE_REDIS - Build with libhiredis; Enables use of Redis map backend
|
||||
ENABLE_SOUND - Build with OpenAL, libogg & libvorbis; in-game Sounds
|
||||
ENABLE_LUAJIT - Build with LuaJIT (much faster than non-JIT Lua)
|
||||
RUN_IN_PLACE - Create a portable install (worlds, settings etc. in current directory)
|
||||
USE_GPROF - Enable profiling using GProf
|
||||
VERSION_EXTRA - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar)
|
||||
|
||||
Library specific options:
|
||||
|
||||
BZIP2_INCLUDE_DIR - Linux only; directory where bzlib.h is located
|
||||
BZIP2_LIBRARY - Linux only; path to libbz2.a/libbz2.so
|
||||
CURL_DLL - Only if building with cURL on Windows; path to libcurl.dll
|
||||
CURL_INCLUDE_DIR - Only if building with cURL; directory where curl.h is located
|
||||
CURL_LIBRARY - Only if building with cURL; path to libcurl.a/libcurl.so/libcurl.lib
|
||||
EGL_INCLUDE_DIR - Only if building with GLES; directory that contains egl.h
|
||||
EGL_LIBRARY - Only if building with GLES; path to libEGL.a/libEGL.so
|
||||
FREETYPE_INCLUDE_DIR_freetype2 - Only if building with Freetype2; directory that contains an freetype directory with files such as ftimage.h in it
|
||||
FREETYPE_INCLUDE_DIR_ft2build - Only if building with Freetype2; directory that contains ft2build.h
|
||||
FREETYPE_LIBRARY - Only if building with Freetype2; path to libfreetype.a/libfreetype.so/freetype.lib
|
||||
FREETYPE_DLL - Only if building with Freetype2 on Windows; path to libfreetype.dll
|
||||
GETTEXT_DLL - Only when building with Gettext on Windows; path to libintl3.dll
|
||||
GETTEXT_ICONV_DLL - Only when building with Gettext on Windows; path to libiconv2.dll
|
||||
GETTEXT_INCLUDE_DIR - Only when building with Gettext; directory that contains iconv.h
|
||||
GETTEXT_LIBRARY - Only when building with Gettext on Windows; path to libintl.dll.a
|
||||
GETTEXT_MSGFMT - Only when building with Gettext; path to msgfmt/msgfmt.exe
|
||||
IRRLICHT_DLL - path to Irrlicht.dll
|
||||
IRRLICHT_INCLUDE_DIR - directory that contains IrrCompileConfig.h
|
||||
IRRLICHT_LIBRARY - path to libIrrlicht.a/libIrrlicht.so/libIrrlicht.dll.a
|
||||
LEVELDB_INCLUDE_DIR - Only when building with LevelDB; directory that contains db.h
|
||||
LEVELDB_LIBRARY - Only when building with LevelDB; path to libleveldb.a/libleveldb.so/libleveldb.dll.a
|
||||
LEVELDB_DLL - Only when building with LevelDB on Windows; path to libleveldb.dll
|
||||
REDIS_INCLUDE_DIR - Only when building with Redis support; directory that contains hiredis.h
|
||||
REDIS_LIBRARY - Only when building with Redis support; path to libhiredis.a/libhiredis.so
|
||||
LUA_INCLUDE_DIR - Only if you want to use LuaJIT; directory where luajit.h is located
|
||||
LUA_LIBRARY - Only if you want to use LuaJIT; path to libluajit.a/libluajit.so
|
||||
MINGWM10_DLL - Only if compiling with MinGW; path to mingwm10.dll
|
||||
OGG_DLL - Only if building with sound on Windows; path to libogg.dll
|
||||
OGG_INCLUDE_DIR - Only if building with sound; directory that contains an ogg directory which contains ogg.h
|
||||
OGG_LIBRARY - Only if building with sound; path to libogg.a/libogg.so/libogg.dll.a
|
||||
OPENAL_DLL - Only if building with sound on Windows; path to OpenAL32.dll
|
||||
OPENAL_INCLUDE_DIR - Only if building with sound; directory where al.h is located
|
||||
OPENAL_LIBRARY - Only if building with sound; path to libopenal.a/libopenal.so/OpenAL32.lib
|
||||
OPENGLES2_INCLUDE_DIR - Only if building with GLES; directory that contains gl2.h
|
||||
OPENGLES2_LIBRARY - Only if building with GLES; path to libGLESv2.a/libGLESv2.so
|
||||
SQLITE3_INCLUDE_DIR - Only if you want to use SQLite from your OS; directory that contains sqlite3.h
|
||||
SQLITE3_LIBRARY - Only if you want to use the SQLite from your OS; path to libsqlite3.a/libsqlite3.so
|
||||
VORBISFILE_DLL - Only if building with sound on Windows; path to libvorbisfile-3.dll
|
||||
VORBISFILE_LIBRARY - Only if building with sound; path to libvorbisfile.a/libvorbisfile.so/libvorbisfile.dll.a
|
||||
VORBIS_DLL - Only if building with sound on Windows; path to libvorbis-0.dll
|
||||
VORBIS_INCLUDE_DIR - Only if building with sound; directory that contains a directory vorbis with vorbisenc.h inside
|
||||
VORBIS_LIBRARY - Only if building with sound; path to libvorbis.a/libvorbis.so/libvorbis.dll.a
|
||||
XXF86VM_LIBRARY - Only on Linux; path to libXXf86vm.a/libXXf86vm.so
|
||||
ZLIB_DLL - Only on Windows; path to zlib1.dll
|
||||
ZLIBWAPI_DLL - Only on Windows; path to zlibwapi.dll
|
||||
ZLIB_INCLUDE_DIR - directory where zlib.h is located
|
||||
ZLIB_LIBRARY - path to libz.a/libz.so/zlibwapi.lib
|
||||
|
||||
Compiling on Windows:
|
||||
---------------------
|
||||
- This section is outdated. In addition to what is described here:
|
||||
- In addition to minetest, you need to download minetest_game.
|
||||
- If you wish to have sound support, you need libogg, libvorbis and libopenal
|
||||
|
||||
- You need:
|
||||
* CMake:
|
||||
http://www.cmake.org/cmake/resources/software.html
|
||||
* MinGW or Visual Studio
|
||||
http://www.mingw.org/
|
||||
http://msdn.microsoft.com/en-us/vstudio/default
|
||||
* Irrlicht SDK 1.7:
|
||||
http://irrlicht.sourceforge.net/downloads.html
|
||||
* Zlib headers (zlib125.zip)
|
||||
http://www.winimage.com/zLibDll/index.html
|
||||
* Zlib library (zlibwapi.lib and zlibwapi.dll from zlib125dll.zip):
|
||||
http://www.winimage.com/zLibDll/index.html
|
||||
* Optional: gettext library and tools:
|
||||
http://gnuwin32.sourceforge.net/downlinks/gettext.php
|
||||
- This is used for other UI languages. Feel free to leave it out.
|
||||
* And, of course, Minetest:
|
||||
http://minetest.net/download
|
||||
- Steps:
|
||||
- Select a directory called DIR hereafter in which you will operate.
|
||||
- Make sure you have CMake and a compiler installed.
|
||||
- Download all the other stuff to DIR and extract them into there.
|
||||
("extract here", not "extract to packagename/")
|
||||
NOTE: zlib125dll.zip needs to be extracted into zlib125dll
|
||||
- All those packages contain a nice base directory in them, which
|
||||
should end up being the direct subdirectories of DIR.
|
||||
- You will end up with a directory structure like this (+=dir, -=file):
|
||||
-----------------
|
||||
+ DIR
|
||||
- zlib-1.2.5.tar.gz
|
||||
- zlib125dll.zip
|
||||
- irrlicht-1.7.1.zip
|
||||
- 110214175330.zip (or whatever, this is the minetest source)
|
||||
+ zlib-1.2.5
|
||||
- zlib.h
|
||||
+ win32
|
||||
...
|
||||
+ zlib125dll
|
||||
- readme.txt
|
||||
+ dll32
|
||||
...
|
||||
+ irrlicht-1.7.1
|
||||
+ lib
|
||||
+ include
|
||||
...
|
||||
+ gettext (optional)
|
||||
+bin
|
||||
+include
|
||||
+lib
|
||||
+ minetest
|
||||
+ src
|
||||
+ doc
|
||||
- CMakeLists.txt
|
||||
...
|
||||
-----------------
|
||||
- Start up the CMake GUI
|
||||
- Select "Browse Source..." and select DIR/minetest
|
||||
- Now, if using MSVC:
|
||||
- Select "Browse Build..." and select DIR/minetest-build
|
||||
- Else if using MinGW:
|
||||
- Select "Browse Build..." and select DIR/minetest
|
||||
- Select "Configure"
|
||||
- Select your compiler
|
||||
- It will warn about missing stuff, ignore that at this point. (later don't)
|
||||
- Make sure the configuration is as follows
|
||||
(note that the versions may differ for you):
|
||||
-----------------
|
||||
BUILD_CLIENT [X]
|
||||
BUILD_SERVER [ ]
|
||||
CMAKE_BUILD_TYPE Release
|
||||
CMAKE_INSTALL_PREFIX DIR/minetest-install
|
||||
IRRLICHT_SOURCE_DIR DIR/irrlicht-1.7.1
|
||||
RUN_IN_PLACE [X]
|
||||
WARN_ALL [ ]
|
||||
ZLIB_DLL DIR/zlib125dll/dll32/zlibwapi.dll
|
||||
ZLIB_INCLUDE_DIR DIR/zlib-1.2.5
|
||||
ZLIB_LIBRARIES DIR/zlib125dll/dll32/zlibwapi.lib
|
||||
GETTEXT_BIN_DIR DIR/gettext/bin
|
||||
GETTEXT_INCLUDE_DIR DIR/gettext/include
|
||||
GETTEXT_LIBRARIES DIR/gettext/lib/intl.lib
|
||||
GETTEXT_MSGFMT DIR/gettext/bin/msgfmt
|
||||
-----------------
|
||||
- Hit "Configure"
|
||||
- Hit "Configure" once again 8)
|
||||
- If something is still coloured red, you have a problem.
|
||||
- Hit "Generate"
|
||||
If using MSVC:
|
||||
- Open the generated minetest.sln
|
||||
- The project defaults to the "Debug" configuration. Make very sure to
|
||||
select "Release", unless you want to debug some stuff (it's slower
|
||||
and might not even work at all)
|
||||
- Build the ALL_BUILD project
|
||||
- Build the INSTALL project
|
||||
- You should now have a working game with the executable in
|
||||
DIR/minetest-install/bin/minetest.exe
|
||||
- Additionally you may create a zip package by building the PACKAGE
|
||||
project.
|
||||
If using MinGW:
|
||||
- Using the command line, browse to the build directory and run 'make'
|
||||
(or mingw32-make or whatever it happens to be)
|
||||
- You may need to copy some of the downloaded DLLs into bin/, see what
|
||||
running the produced executable tells you it doesn't have.
|
||||
- You should now have a working game with the executable in
|
||||
DIR/minetest/bin/minetest.exe
|
||||
|
||||
Windows releases of minetest are built using a bat script like this:
|
||||
--------------------------------------------------------------------
|
||||
|
||||
set sourcedir=%CD%
|
||||
set installpath="C:\tmp\minetest_install"
|
||||
set irrlichtpath="C:\tmp\irrlicht-1.7.2"
|
||||
|
||||
set builddir=%sourcedir%\bvc10
|
||||
mkdir %builddir%
|
||||
pushd %builddir%
|
||||
cmake %sourcedir% -G "Visual Studio 10" -DIRRLICHT_SOURCE_DIR=%irrlichtpath% -DRUN_IN_PLACE=TRUE -DCMAKE_INSTALL_PREFIX=%installpath%
|
||||
if %errorlevel% neq 0 goto fail
|
||||
"C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" ALL_BUILD.vcxproj /p:Configuration=Release
|
||||
if %errorlevel% neq 0 goto fail
|
||||
"C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" INSTALL.vcxproj /p:Configuration=Release
|
||||
if %errorlevel% neq 0 goto fail
|
||||
"C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" PACKAGE.vcxproj /p:Configuration=Release
|
||||
if %errorlevel% neq 0 goto fail
|
||||
popd
|
||||
echo Finished.
|
||||
exit /b 0
|
||||
|
||||
:fail
|
||||
popd
|
||||
echo Failed.
|
||||
exit /b 1
|
||||
|
||||
License of Minetest textures and sounds
|
||||
---------------------------------------
|
||||
|
||||
This applies to textures and sounds contained in the main Minetest
|
||||
distribution.
|
||||
|
||||
Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
|
||||
http://creativecommons.org/licenses/by-sa/3.0/
|
||||
|
||||
Authors of media files
|
||||
-----------------------
|
||||
Everything not listed in here:
|
||||
Copyright (C) 2010-2012 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
|
||||
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 <celeron55@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Irrlicht
|
||||
---------------
|
||||
|
||||
This program uses the Irrlicht Engine. http://irrlicht.sourceforge.net/
|
||||
|
||||
The Irrlicht Engine License
|
||||
|
||||
Copyright © 2002-2005 Nikolaus Gebhardt
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute
|
||||
it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you
|
||||
must not claim that you wrote the original software. If you use
|
||||
this software in a product, an acknowledgment in the product
|
||||
documentation would be appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must
|
||||
not be misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
|
||||
|
||||
JThread
|
||||
---------------
|
||||
|
||||
This program uses the JThread library. License for JThread follows:
|
||||
|
||||
Copyright (c) 2000-2006 Jori Liesenborgs (jori.liesenborgs@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
|
||||
Lua
|
||||
---------------
|
||||
|
||||
Lua is licensed under the terms of the MIT license reproduced below.
|
||||
This means that Lua is free software and can be used for both academic
|
||||
and commercial purposes at absolutely no cost.
|
||||
|
||||
For details and rationale, see http://www.lua.org/license.html .
|
||||
|
||||
Copyright (C) 1994-2008 Lua.org, PUC-Rio.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Fonts
|
||||
---------------
|
||||
|
||||
DejaVu Sans Mono:
|
||||
|
||||
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
|
||||
Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
|
||||
|
||||
Bitstream Vera Fonts Copyright:
|
||||
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
|
||||
a trademark of Bitstream, Inc.
|
||||
|
||||
Arev Fonts Copyright:
|
||||
|
||||
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
|
||||
|
||||
Liberation Fonts Copyright:
|
||||
|
||||
Copyright (c) 2007 Red Hat, Inc. All rights reserved. LIBERATION is a trademark of Red Hat, Inc.
|
||||
|
||||
DroidSansFallback:
|
||||
|
||||
Copyright (C) 2008 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
17
assets/Minetest/builtin/async/init.lua
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
core.log("info", "Initializing Asynchronous environment")
|
||||
|
||||
function core.job_processor(serialized_func, serialized_param)
|
||||
local func = loadstring(serialized_func)
|
||||
local param = core.deserialize(serialized_param)
|
||||
local retval = nil
|
||||
|
||||
if type(func) == "function" then
|
||||
retval = core.serialize(func(param))
|
||||
else
|
||||
core.log("error", "ASYNC WORKER: Unable to deserialize function")
|
||||
end
|
||||
|
||||
return retval or core.serialize(nil)
|
||||
end
|
||||
|
40
assets/Minetest/builtin/common/async_event.lua
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
core.async_jobs = {}
|
||||
|
||||
local function handle_job(jobid, serialized_retval)
|
||||
local retval = core.deserialize(serialized_retval)
|
||||
assert(type(core.async_jobs[jobid]) == "function")
|
||||
core.async_jobs[jobid](retval)
|
||||
core.async_jobs[jobid] = nil
|
||||
end
|
||||
|
||||
if core.register_globalstep then
|
||||
core.register_globalstep(function(dtime)
|
||||
for i, job in ipairs(core.get_finished_jobs()) do
|
||||
handle_job(job.jobid, job.retval)
|
||||
end
|
||||
end)
|
||||
else
|
||||
core.async_event_handler = handle_job
|
||||
end
|
||||
|
||||
function core.handle_async(func, parameter, callback)
|
||||
-- Serialize function
|
||||
local serialized_func = string.dump(func)
|
||||
|
||||
assert(serialized_func ~= nil)
|
||||
|
||||
-- Serialize parameters
|
||||
local serialized_param = core.serialize(parameter)
|
||||
|
||||
if serialized_param == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
local jobid = core.do_async_callback(serialized_func, serialized_param)
|
||||
|
||||
core.async_jobs[jobid] = callback
|
||||
|
||||
return true
|
||||
end
|
||||
|
317
assets/Minetest/builtin/common/filterlist.lua
Normal file
@ -0,0 +1,317 @@
|
||||
--Minetest
|
||||
--Copyright (C) 2013 sapier
|
||||
--
|
||||
--This program is free software; you can redistribute it and/or modify
|
||||
--it under the terms of the GNU Lesser General Public License as published by
|
||||
--the Free Software Foundation; either version 2.1 of the License, or
|
||||
--(at your option) any later version.
|
||||
--
|
||||
--This program is distributed in the hope that it will be useful,
|
||||
--but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
--GNU Lesser General Public License for more details.
|
||||
--
|
||||
--You should have received a copy of the GNU Lesser General Public License along
|
||||
--with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- TODO improve doc --
|
||||
-- TODO code cleanup --
|
||||
-- Generic implementation of a filter/sortable list --
|
||||
-- Usage: --
|
||||
-- Filterlist needs to be initialized on creation. To achieve this you need to --
|
||||
-- pass following functions: --
|
||||
-- raw_fct() (mandatory): --
|
||||
-- function returning a table containing the elements to be filtered --
|
||||
-- compare_fct(element1,element2) (mandatory): --
|
||||
-- function returning true/false if element1 is same element as element2 --
|
||||
-- uid_match_fct(element1,uid) (optional) --
|
||||
-- function telling if uid is attached to element1 --
|
||||
-- filter_fct(element,filtercriteria) (optional) --
|
||||
-- function returning true/false if filtercriteria met to element --
|
||||
-- fetch_param (optional) --
|
||||
-- parameter passed to raw_fct to aquire correct raw data --
|
||||
-- --
|
||||
--------------------------------------------------------------------------------
|
||||
filterlist = {}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function filterlist.refresh(self)
|
||||
self.m_raw_list = self.m_raw_list_fct(self.m_fetch_param)
|
||||
filterlist.process(self)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function filterlist.create(raw_fct,compare_fct,uid_match_fct,filter_fct,fetch_param)
|
||||
|
||||
assert((raw_fct ~= nil) and (type(raw_fct) == "function"))
|
||||
assert((compare_fct ~= nil) and (type(compare_fct) == "function"))
|
||||
|
||||
local self = {}
|
||||
|
||||
self.m_raw_list_fct = raw_fct
|
||||
self.m_compare_fct = compare_fct
|
||||
self.m_filter_fct = filter_fct
|
||||
self.m_uid_match_fct = uid_match_fct
|
||||
|
||||
self.m_filtercriteria = nil
|
||||
self.m_fetch_param = fetch_param
|
||||
|
||||
self.m_sortmode = "none"
|
||||
self.m_sort_list = {}
|
||||
|
||||
self.m_processed_list = nil
|
||||
self.m_raw_list = self.m_raw_list_fct(self.m_fetch_param)
|
||||
|
||||
self.add_sort_mechanism = filterlist.add_sort_mechanism
|
||||
self.set_filtercriteria = filterlist.set_filtercriteria
|
||||
self.get_filtercriteria = filterlist.get_filtercriteria
|
||||
self.set_sortmode = filterlist.set_sortmode
|
||||
self.get_list = filterlist.get_list
|
||||
self.get_raw_list = filterlist.get_raw_list
|
||||
self.get_raw_element = filterlist.get_raw_element
|
||||
self.get_raw_index = filterlist.get_raw_index
|
||||
self.get_current_index = filterlist.get_current_index
|
||||
self.size = filterlist.size
|
||||
self.uid_exists_raw = filterlist.uid_exists_raw
|
||||
self.raw_index_by_uid = filterlist.raw_index_by_uid
|
||||
self.refresh = filterlist.refresh
|
||||
|
||||
filterlist.process(self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function filterlist.add_sort_mechanism(self,name,fct)
|
||||
self.m_sort_list[name] = fct
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function filterlist.set_filtercriteria(self,criteria)
|
||||
if criteria == self.m_filtercriteria and
|
||||
type(criteria) ~= "table" then
|
||||
return
|
||||
end
|
||||
self.m_filtercriteria = criteria
|
||||
filterlist.process(self)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function filterlist.get_filtercriteria(self)
|
||||
return self.m_filtercriteria
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--supported sort mode "alphabetic|none"
|
||||
function filterlist.set_sortmode(self,mode)
|
||||
if (mode == self.m_sortmode) then
|
||||
return
|
||||
end
|
||||
self.m_sortmode = mode
|
||||
filterlist.process(self)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function filterlist.get_list(self)
|
||||
return self.m_processed_list
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function filterlist.get_raw_list(self)
|
||||
return self.m_raw_list
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function filterlist.get_raw_element(self,idx)
|
||||
if type(idx) ~= "number" then
|
||||
idx = tonumber(idx)
|
||||
end
|
||||
|
||||
if idx ~= nil and idx > 0 and idx <= #self.m_raw_list then
|
||||
return self.m_raw_list[idx]
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function filterlist.get_raw_index(self,listindex)
|
||||
assert(self.m_processed_list ~= nil)
|
||||
|
||||
if listindex ~= nil and listindex > 0 and
|
||||
listindex <= #self.m_processed_list then
|
||||
local entry = self.m_processed_list[listindex]
|
||||
|
||||
for i,v in ipairs(self.m_raw_list) do
|
||||
|
||||
if self.m_compare_fct(v,entry) then
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function filterlist.get_current_index(self,listindex)
|
||||
assert(self.m_processed_list ~= nil)
|
||||
|
||||
if listindex ~= nil and listindex > 0 and
|
||||
listindex <= #self.m_raw_list then
|
||||
local entry = self.m_raw_list[listindex]
|
||||
|
||||
for i,v in ipairs(self.m_processed_list) do
|
||||
|
||||
if self.m_compare_fct(v,entry) then
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function filterlist.process(self)
|
||||
assert(self.m_raw_list ~= nil)
|
||||
|
||||
if self.m_sortmode == "none" and
|
||||
self.m_filtercriteria == nil then
|
||||
self.m_processed_list = self.m_raw_list
|
||||
return
|
||||
end
|
||||
|
||||
self.m_processed_list = {}
|
||||
|
||||
for k,v in pairs(self.m_raw_list) do
|
||||
if self.m_filtercriteria == nil or
|
||||
self.m_filter_fct(v,self.m_filtercriteria) then
|
||||
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
|
596
assets/Minetest/builtin/common/misc_helpers.lua
Normal file
@ -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 "<circular reference>"
|
||||
end
|
||||
nested[o] = true
|
||||
indent = indent or "\t"
|
||||
level = level or 1
|
||||
local t = {}
|
||||
local dumped_indexes = {}
|
||||
for i, v in ipairs(o) do
|
||||
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
|
||||
|
218
assets/Minetest/builtin/common/serialize.lua
Normal file
@ -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 <metalua@gmail.com>
|
||||
-- @author Fabien Fleutot <metalua@gmail.com>
|
||||
-- @author ShadowNinja <shadowninja@minetest.net>
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--- Serialize an object into a source code string. This string, when passed as
|
||||
-- an argument to deserialize(), returns an object structurally identical to
|
||||
-- the original one. The following are currently supported:
|
||||
-- * Booleans, numbers, strings, and nil.
|
||||
-- * Functions; uses interpreter-dependent (and sometimes platform-dependent) bytecode!
|
||||
-- * Tables; they can cantain multiple references and can be recursive, but metatables aren't saved.
|
||||
-- This works in two phases:
|
||||
-- 1. Recursively find and record multiple references and recursion.
|
||||
-- 2. Recursively dump the value into a string.
|
||||
-- @param x Value to serialize (nil is allowed).
|
||||
-- @return load()able string containing the value.
|
||||
function core.serialize(x)
|
||||
local local_index = 1 -- Top index of the "_" local table in the dump
|
||||
-- table->nil/1/2 set of tables seen.
|
||||
-- nil = not seen, 1 = seen once, 2 = seen multiple times.
|
||||
local seen = {}
|
||||
|
||||
-- nest_points are places where a table appears within itself, directly
|
||||
-- or not. For instance, all of these chunks create nest points in
|
||||
-- table x: "x = {}; x[x] = 1", "x = {}; x[1] = x",
|
||||
-- "x = {}; x[1] = {y = {x}}".
|
||||
-- To handle those, two tables are used by mark_nest_point:
|
||||
-- * nested - Transient set of tables being currently traversed.
|
||||
-- Used for detecting nested tables.
|
||||
-- * nest_points - parent->{key=value, ...} table cantaining the nested
|
||||
-- keys and values in the parent. They're all dumped after all the
|
||||
-- other table operations have been performed.
|
||||
--
|
||||
-- mark_nest_point(p, k, v) fills nest_points with information required
|
||||
-- to remember that key/value (k, v) creates a nest point in table
|
||||
-- parent. It also marks "parent" and the nested item(s) as occuring
|
||||
-- multiple times, since several references to it will be required in
|
||||
-- order to patch the nest points.
|
||||
local nest_points = {}
|
||||
local nested = {}
|
||||
local function mark_nest_point(parent, k, v)
|
||||
local nk, nv = nested[k], nested[v]
|
||||
local np = nest_points[parent]
|
||||
if not np then
|
||||
np = {}
|
||||
nest_points[parent] = np
|
||||
end
|
||||
np[k] = v
|
||||
seen[parent] = 2
|
||||
if nk then seen[k] = 2 end
|
||||
if nv then seen[v] = 2 end
|
||||
end
|
||||
|
||||
-- First phase, list the tables and functions which appear more than
|
||||
-- once in x.
|
||||
local function mark_multiple_occurences(x)
|
||||
local tp = type(x)
|
||||
if tp ~= "table" and tp ~= "function" then
|
||||
-- No identity (comparison is done by value, not by instance)
|
||||
return
|
||||
end
|
||||
if seen[x] == 1 then
|
||||
seen[x] = 2
|
||||
elseif seen[x] ~= 2 then
|
||||
seen[x] = 1
|
||||
end
|
||||
|
||||
if tp == "table" then
|
||||
nested[x] = true
|
||||
for k, v in pairs(x) do
|
||||
if nested[k] or nested[v] then
|
||||
mark_nest_point(x, k, v)
|
||||
else
|
||||
mark_multiple_occurences(k)
|
||||
mark_multiple_occurences(v)
|
||||
end
|
||||
end
|
||||
nested[x] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local dumped = {} -- object->varname set
|
||||
local local_defs = {} -- Dumped local definitions as source code lines
|
||||
|
||||
-- Mutually recursive local functions:
|
||||
local dump_val, dump_or_ref_val
|
||||
|
||||
-- If x occurs multiple times, dump the local variable rather than
|
||||
-- the value. If it's the first time it's dumped, also dump the
|
||||
-- content in local_defs.
|
||||
function dump_or_ref_val(x)
|
||||
if seen[x] ~= 2 then
|
||||
return dump_val(x)
|
||||
end
|
||||
local var = dumped[x]
|
||||
if var then -- Already referenced
|
||||
return var
|
||||
end
|
||||
-- First occurence, create and register reference
|
||||
local val = dump_val(x)
|
||||
local i = local_index
|
||||
local_index = local_index + 1
|
||||
var = "_["..i.."]"
|
||||
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)
|
||||
|
54
assets/Minetest/builtin/common/strict.lua
Normal file
@ -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)
|
||||
|
133
assets/Minetest/builtin/common/vector.lua
Normal file
@ -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
|
||||
|
210
assets/Minetest/builtin/fstk/buttonbar.lua
Normal file
@ -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
|
69
assets/Minetest/builtin/fstk/dialog.lua
Normal file
@ -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
|
273
assets/Minetest/builtin/fstk/tabview.lua
Normal file
@ -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
|
171
assets/Minetest/builtin/fstk/ui.lua
Normal file
@ -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
|
200
assets/Minetest/builtin/game/auth.lua
Normal file
@ -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)
|
||||
|
811
assets/Minetest/builtin/game/chatcommands.lua
Normal file
@ -0,0 +1,811 @@
|
||||
-- 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 = "<action>",
|
||||
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/<cmd>]",
|
||||
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 <cmd>' 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 = "<name>",
|
||||
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 = "<name> <privilege>|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 = "<name> <privilege>|all",
|
||||
description = "Remove privilege from player",
|
||||
privs = {},
|
||||
func = function(name, param)
|
||||
if not core.check_player_privs(name, {privs=true}) and
|
||||
not core.check_player_privs(name, {basic_privs=true}) then
|
||||
return false, "Your privileges are insufficient."
|
||||
end
|
||||
local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)")
|
||||
if not revoke_name or not revoke_priv_str then
|
||||
return false, "Invalid parameters (see /help revoke)"
|
||||
elseif not core.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 = "<name> <password>",
|
||||
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 = "<name>",
|
||||
description = "set empty password",
|
||||
privs = {password=true},
|
||||
func = function(name, param)
|
||||
local toname = param
|
||||
if toname == "" then
|
||||
return false, "Name field required"
|
||||
end
|
||||
core.set_player_password(toname, '')
|
||||
|
||||
core.log("action", name .. " clears password of " .. toname .. ".")
|
||||
|
||||
return true, "Password of player \"" .. toname .. "\" cleared"
|
||||
end,
|
||||
})
|
||||
|
||||
core.register_chatcommand("auth_reload", {
|
||||
params = "",
|
||||
description = "reload authentication data",
|
||||
privs = {server=true},
|
||||
func = function(name, param)
|
||||
local done = core.auth_reload()
|
||||
return done, (done and "Done." or "Failed.")
|
||||
end,
|
||||
})
|
||||
|
||||
core.register_chatcommand("teleport", {
|
||||
params = "<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>",
|
||||
description = "teleport to 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] <name> <value> | <name>",
|
||||
description = "set or read server configuration setting",
|
||||
privs = {server=true},
|
||||
func = function(name, param)
|
||||
local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
|
||||
if arg and arg == "-n" and setname and setvalue then
|
||||
core.setting_set(setname, setvalue)
|
||||
return true, setname .. " = " .. setvalue
|
||||
end
|
||||
local setname, setvalue = string.match(param, "([^ ]+) (.+)")
|
||||
if setname and setvalue then
|
||||
if not core.setting_get(setname) then
|
||||
return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
|
||||
end
|
||||
core.setting_set(setname, setvalue)
|
||||
return true, setname .. " = " .. setvalue
|
||||
end
|
||||
local setname = string.match(param, "([^ ]+)")
|
||||
if setname then
|
||||
local setvalue = core.setting_get(setname)
|
||||
if not setvalue then
|
||||
setvalue = "<not set>"
|
||||
end
|
||||
return true, setname .. " = " .. setvalue
|
||||
end
|
||||
return false, "Invalid parameters (see /help set)."
|
||||
end,
|
||||
})
|
||||
|
||||
core.register_chatcommand("deleteblocks", {
|
||||
params = "(here [radius]) | (<pos1> <pos2>)",
|
||||
description = "delete map blocks contained in area pos1 to pos2",
|
||||
privs = {server=true},
|
||||
func = function(name, param)
|
||||
local p1 = {}
|
||||
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 = "<name> <ItemString>",
|
||||
description = "give item to player",
|
||||
privs = {give=true},
|
||||
func = function(name, param)
|
||||
local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
|
||||
if not toname or not itemstring then
|
||||
return false, "Name and ItemString required"
|
||||
end
|
||||
return handle_give_command("/give", name, toname, itemstring)
|
||||
end,
|
||||
})
|
||||
|
||||
core.register_chatcommand("giveme", {
|
||||
params = "<ItemString>",
|
||||
description = "give item to yourself",
|
||||
privs = {give=true},
|
||||
func = function(name, param)
|
||||
local itemstring = string.match(param, "(.+)$")
|
||||
if not itemstring then
|
||||
return false, "ItemString required"
|
||||
end
|
||||
return handle_give_command("/giveme", name, name, itemstring)
|
||||
end,
|
||||
})
|
||||
|
||||
core.register_chatcommand("spawnentity", {
|
||||
params = "<EntityName>",
|
||||
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 = "[<range>] [<seconds>] [limit]",
|
||||
description = "Check who has last touched a node or near it,"
|
||||
.. " max. <seconds> 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 = "<player name> [<seconds>] | :<actor> [<seconds>]",
|
||||
description = "revert actions of a player; default for <seconds> is 60",
|
||||
privs = {rollback=true},
|
||||
func = function(name, param)
|
||||
if not core.setting_getbool("enable_rollback_recording") then
|
||||
return false, "Rollback functions are disabled."
|
||||
end
|
||||
local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
|
||||
if not target_name then
|
||||
local player_name = nil
|
||||
player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
|
||||
if not player_name then
|
||||
return false, "Invalid parameters. See /help rollback"
|
||||
.. " and /help rollback_check."
|
||||
end
|
||||
target_name = "player:"..player_name
|
||||
end
|
||||
seconds = tonumber(seconds) or 60
|
||||
core.chat_send_player(name, "Reverting actions of "
|
||||
.. target_name .. " since "
|
||||
.. seconds .. " seconds.")
|
||||
local success, log = core.rollback_revert_actions_by(
|
||||
target_name, seconds)
|
||||
local response = ""
|
||||
if #log > 100 then
|
||||
response = "(log is too long to show)\n"
|
||||
else
|
||||
for _, line in pairs(log) do
|
||||
response = response .. line .. "\n"
|
||||
end
|
||||
end
|
||||
response = response .. "Reverting actions "
|
||||
.. (success and "succeeded." or "FAILED.")
|
||||
return success, response
|
||||
end,
|
||||
})
|
||||
|
||||
core.register_chatcommand("status", {
|
||||
description = "Print server status",
|
||||
func = function(name, param)
|
||||
return true, core.get_server_status()
|
||||
end,
|
||||
})
|
||||
|
||||
core.register_chatcommand("time", {
|
||||
params = "<0...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 = "<name>",
|
||||
description = "Ban IP of player",
|
||||
privs = {ban=true},
|
||||
func = function(name, param)
|
||||
if param == "" then
|
||||
return true, "Ban list: " .. core.get_ban_list()
|
||||
end
|
||||
if not core.get_player_by_name(param) then
|
||||
return false, "No such player."
|
||||
end
|
||||
if not core.ban_player(param) then
|
||||
return false, "Failed to ban player."
|
||||
end
|
||||
local desc = core.get_ban_description(param)
|
||||
core.log("action", name .. " bans " .. desc .. ".")
|
||||
return true, "Banned " .. desc .. "."
|
||||
end,
|
||||
})
|
||||
|
||||
core.register_chatcommand("unban", {
|
||||
params = "<name/ip>",
|
||||
description = "remove IP ban",
|
||||
privs = {ban=true},
|
||||
func = function(name, param)
|
||||
if not core.unban_player_or_ip(param) then
|
||||
return false, "Failed to unban player/IP."
|
||||
end
|
||||
core.log("action", name .. " unbans " .. param)
|
||||
return true, "Unbanned " .. param
|
||||
end,
|
||||
})
|
||||
|
||||
core.register_chatcommand("kick", {
|
||||
params = "<name> [reason]",
|
||||
description = "kick a player",
|
||||
privs = {kick=true},
|
||||
func = function(name, param)
|
||||
local tokick, reason = param:match("([^ ]+) (.+)")
|
||||
tokick = tokick or param
|
||||
if not core.kick_player(tokick, reason) then
|
||||
return false, "Failed to kick player " .. tokick
|
||||
end
|
||||
local log_reason = ""
|
||||
if reason then
|
||||
log_reason = " with reason \"" .. reason .. "\""
|
||||
end
|
||||
core.log("action", name .. " kicks " .. tokick .. log_reason)
|
||||
return true, "Kicked " .. tokick
|
||||
end,
|
||||
})
|
||||
|
||||
core.register_chatcommand("clearobjects", {
|
||||
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 = "<name> <message>",
|
||||
description = "Send a private message",
|
||||
privs = {shout=true},
|
||||
func = function(name, param)
|
||||
local sendto, message = param:match("^(%S+)%s(.+)$")
|
||||
if not sendto then
|
||||
return false, "Invalid usage, see /help msg."
|
||||
end
|
||||
if not core.get_player_by_name(sendto) then
|
||||
return false, "The player " .. sendto
|
||||
.. " is not online."
|
||||
end
|
||||
core.log("action", "PM from " .. name .. " to " .. sendto
|
||||
.. ": " .. message)
|
||||
core.chat_send_player(sendto, "PM from " .. name .. ": "
|
||||
.. message)
|
||||
return true, "Message sent."
|
||||
end,
|
||||
})
|
||||
|
||||
core.register_chatcommand("last-login", {
|
||||
params = "[name]",
|
||||
description = "Get the last login time of a player",
|
||||
func = function(name, param)
|
||||
if param == "" then
|
||||
param = name
|
||||
end
|
||||
local pauth = core.get_auth_handler().get_auth(param)
|
||||
if pauth and pauth.last_login then
|
||||
-- Time in UTC, ISO 8601 format
|
||||
return true, "Last login time was " ..
|
||||
os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
|
||||
end
|
||||
return false, "Last login time is unknown"
|
||||
end,
|
||||
})
|
53
assets/Minetest/builtin/game/deprecated.lua
Normal file
@ -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
|
||||
|
19
assets/Minetest/builtin/game/detached_inventory.lua
Normal file
@ -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
|
||||
|
224
assets/Minetest/builtin/game/falling.lua
Normal file
@ -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)
|
30
assets/Minetest/builtin/game/features.lua
Normal file
@ -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
|
79
assets/Minetest/builtin/game/forceloading.lua
Normal file
@ -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)
|
28
assets/Minetest/builtin/game/init.lua
Normal file
@ -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")
|
||||
|
618
assets/Minetest/builtin/game/item.lua
Normal file
@ -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,
|
||||
}
|
189
assets/Minetest/builtin/game/item_entity.lua
Normal file
@ -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,
|
||||
})
|
114
assets/Minetest/builtin/game/misc.lua
Normal file
@ -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
|
||||
|
356
assets/Minetest/builtin/game/mod_profiling.lua
Normal file
@ -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()
|
53
assets/Minetest/builtin/game/privileges.lua
Normal file
@ -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")
|
||||
|
438
assets/Minetest/builtin/game/register.lua
Normal file
@ -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
|
||||
|
165
assets/Minetest/builtin/game/statbars.lua
Normal file
@ -0,0 +1,165 @@
|
||||
|
||||
local health_bar_definition =
|
||||
{
|
||||
hud_elem_type = "statbar",
|
||||
position = { x=0.5, y=1 },
|
||||
text = "heart.png",
|
||||
number = 20,
|
||||
direction = 0,
|
||||
size = { x=24, y=24 },
|
||||
offset = { x=(-10*24)-25, y=-(48+24+16)},
|
||||
}
|
||||
|
||||
local breath_bar_definition =
|
||||
{
|
||||
hud_elem_type = "statbar",
|
||||
position = { x=0.5, y=1 },
|
||||
text = "bubble.png",
|
||||
number = 20,
|
||||
direction = 0,
|
||||
size = { x=24, y=24 },
|
||||
offset = {x=25,y=-(48+24+16)},
|
||||
}
|
||||
|
||||
local hud_ids = {}
|
||||
|
||||
local function initialize_builtin_statbars(player)
|
||||
|
||||
if not player:is_player() then
|
||||
return
|
||||
end
|
||||
|
||||
local name = player:get_player_name()
|
||||
|
||||
if name == "" then
|
||||
return
|
||||
end
|
||||
|
||||
if (hud_ids[name] == nil) then
|
||||
hud_ids[name] = {}
|
||||
-- flags are not transmitted to client on connect, we need to make sure
|
||||
-- our current flags are transmitted by sending them actively
|
||||
player:hud_set_flags(player:hud_get_flags())
|
||||
end
|
||||
|
||||
if player:hud_get_flags().healthbar and
|
||||
core.is_yes(core.setting_get("enable_damage")) then
|
||||
if hud_ids[name].id_healthbar == nil then
|
||||
health_bar_definition.number = player:get_hp()
|
||||
hud_ids[name].id_healthbar = player:hud_add(health_bar_definition)
|
||||
end
|
||||
else
|
||||
if hud_ids[name].id_healthbar ~= nil then
|
||||
player:hud_remove(hud_ids[name].id_healthbar)
|
||||
hud_ids[name].id_healthbar = nil
|
||||
end
|
||||
end
|
||||
|
||||
if (player:get_breath() < 11) then
|
||||
if player:hud_get_flags().breathbar and
|
||||
core.is_yes(core.setting_get("enable_damage")) then
|
||||
if hud_ids[name].id_breathbar == nil then
|
||||
hud_ids[name].id_breathbar = player:hud_add(breath_bar_definition)
|
||||
end
|
||||
else
|
||||
if hud_ids[name].id_breathbar ~= nil then
|
||||
player:hud_remove(hud_ids[name].id_breathbar)
|
||||
hud_ids[name].id_breathbar = nil
|
||||
end
|
||||
end
|
||||
elseif hud_ids[name].id_breathbar ~= nil then
|
||||
player:hud_remove(hud_ids[name].id_breathbar)
|
||||
hud_ids[name].id_breathbar = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function cleanup_builtin_statbars(player)
|
||||
|
||||
if not player:is_player() then
|
||||
return
|
||||
end
|
||||
|
||||
local name = player:get_player_name()
|
||||
|
||||
if name == "" then
|
||||
return
|
||||
end
|
||||
|
||||
hud_ids[name] = nil
|
||||
end
|
||||
|
||||
local function player_event_handler(player,eventname)
|
||||
assert(player:is_player())
|
||||
|
||||
local name = player:get_player_name()
|
||||
|
||||
if name == "" then
|
||||
return
|
||||
end
|
||||
|
||||
if eventname == "health_changed" then
|
||||
initialize_builtin_statbars(player)
|
||||
|
||||
if hud_ids[name].id_healthbar ~= nil then
|
||||
player:hud_change(hud_ids[name].id_healthbar,"number",player:get_hp())
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if eventname == "breath_changed" then
|
||||
initialize_builtin_statbars(player)
|
||||
|
||||
if hud_ids[name].id_breathbar ~= nil then
|
||||
player:hud_change(hud_ids[name].id_breathbar,"number",player:get_breath()*2)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if eventname == "hud_changed" then
|
||||
initialize_builtin_statbars(player)
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function core.hud_replace_builtin(name, definition)
|
||||
|
||||
if definition == nil or
|
||||
type(definition) ~= "table" or
|
||||
definition.hud_elem_type ~= "statbar" then
|
||||
return false
|
||||
end
|
||||
|
||||
if name == "health" then
|
||||
health_bar_definition = definition
|
||||
|
||||
for name,ids in pairs(hud_ids) do
|
||||
local player = core.get_player_by_name(name)
|
||||
if player and hud_ids[name].id_healthbar then
|
||||
player:hud_remove(hud_ids[name].id_healthbar)
|
||||
initialize_builtin_statbars(player)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if name == "breath" then
|
||||
breath_bar_definition = definition
|
||||
|
||||
for name,ids in pairs(hud_ids) do
|
||||
local player = core.get_player_by_name(name)
|
||||
if player and hud_ids[name].id_breathbar then
|
||||
player:hud_remove(hud_ids[name].id_breathbar)
|
||||
initialize_builtin_statbars(player)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
core.register_on_joinplayer(initialize_builtin_statbars)
|
||||
core.register_on_leaveplayer(cleanup_builtin_statbars)
|
||||
core.register_playerevent(player_event_handler)
|
33
assets/Minetest/builtin/game/static_spawn.lua
Normal file
@ -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)
|
||||
|
109
assets/Minetest/builtin/game/voxelarea.lua
Normal file
@ -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
|
38
assets/Minetest/builtin/init.lua
Normal file
@ -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
|
||||
|
292
assets/Minetest/builtin/mainmenu/common.lua
Normal file
@ -0,0 +1,292 @@
|
||||
--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
|
||||
|
||||
local function configure_selected_world_params(idx)
|
||||
local worldconfig = modmgr.get_worldconfig(
|
||||
menudata.worldlist:get_list()[idx].path)
|
||||
|
||||
if worldconfig.creative_mode ~= nil then
|
||||
core.setting_set("creative_mode", worldconfig.creative_mode)
|
||||
end
|
||||
if worldconfig.enable_damage ~= nil then
|
||||
core.setting_set("enable_damage", worldconfig.enable_damage)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function image_column(tooltip, flagname)
|
||||
return "image," ..
|
||||
"tooltip=" .. core.formspec_escape(tooltip) .. "," ..
|
||||
"0=" .. core.formspec_escape(defaulttexturedir .. "blank.png") .. "," ..
|
||||
"1=" .. core.formspec_escape(defaulttexturedir .. "server_flags_" .. flagname .. ".png")
|
||||
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))
|
||||
|
||||
configure_selected_world_params(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))
|
||||
|
||||
configure_selected_world_params(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
|
311
assets/Minetest/builtin/mainmenu/dlg_config_world.lua
Normal file
@ -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
|
143
assets/Minetest/builtin/mainmenu/dlg_create_world.lua
Normal file
@ -0,0 +1,143 @@
|
||||
--Minetest
|
||||
--Copyright (C) 2014 sapier
|
||||
--
|
||||
--This program is free software; you can redistribute it and/or modify
|
||||
--it under the terms of the GNU Lesser General Public License as published by
|
||||
--the Free Software Foundation; either version 2.1 of the License, or
|
||||
--(at your option) any later version.
|
||||
--
|
||||
--This program is distributed in the hope that it will be useful,
|
||||
--but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
--GNU Lesser General Public License for more details.
|
||||
--
|
||||
--You should have received a copy of the GNU Lesser General Public License along
|
||||
--with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
local function create_world_formspec(dialogdata)
|
||||
local mapgens = core.get_mapgen_names()
|
||||
|
||||
local current_seed = core.setting_get("fixed_map_seed") or ""
|
||||
local current_mg = core.setting_get("mg_name")
|
||||
|
||||
local mglist = ""
|
||||
local selindex = 1
|
||||
local i = 1
|
||||
for k,v in pairs(mapgens) do
|
||||
if current_mg == v then
|
||||
selindex = i
|
||||
end
|
||||
i = i + 1
|
||||
mglist = mglist .. v .. ","
|
||||
end
|
||||
mglist = mglist:sub(1, -2)
|
||||
|
||||
local gameid = core.setting_get("menu_last_game")
|
||||
|
||||
local game, gameidx = nil , 0
|
||||
if gameid ~= nil then
|
||||
game, gameidx = gamemgr.find_by_gameid(gameid)
|
||||
|
||||
if gameidx == nil then
|
||||
gameidx = 0
|
||||
end
|
||||
end
|
||||
|
||||
current_seed = core.formspec_escape(current_seed)
|
||||
local retval =
|
||||
"size[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
|
68
assets/Minetest/builtin/mainmenu/dlg_delete_mod.lua
Normal file
@ -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
|
64
assets/Minetest/builtin/mainmenu/dlg_delete_world.lua
Normal file
@ -0,0 +1,64 @@
|
||||
--Minetest
|
||||
--Copyright (C) 2014 sapier
|
||||
--
|
||||
--This program is free software; you can redistribute it and/or modify
|
||||
--it under the terms of the GNU Lesser General Public License as published by
|
||||
--the Free Software Foundation; either version 2.1 of the License, or
|
||||
--(at your option) any later version.
|
||||
--
|
||||
--This program is distributed in the hope that it will be useful,
|
||||
--but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
--GNU Lesser General Public License for more details.
|
||||
--
|
||||
--You should have received a copy of the GNU Lesser General Public License along
|
||||
--with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
|
||||
local function delete_world_formspec(dialogdata)
|
||||
|
||||
local retval =
|
||||
"size[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
|
69
assets/Minetest/builtin/mainmenu/dlg_rename_modpack.lua
Normal file
@ -0,0 +1,69 @@
|
||||
--Minetest
|
||||
--Copyright (C) 2014 sapier
|
||||
--
|
||||
--This program is free software; you can redistribute it and/or modify
|
||||
--it under the terms of the GNU Lesser General Public License as published by
|
||||
--the Free Software Foundation; either version 2.1 of the License, or
|
||||
--(at your option) any later version.
|
||||
--
|
||||
--This program is distributed in the hope that it will be useful,
|
||||
--but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
--GNU Lesser General Public License for more details.
|
||||
--
|
||||
--You should have received a copy of the GNU Lesser General Public License along
|
||||
--with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local function 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
|
83
assets/Minetest/builtin/mainmenu/gamemgr.lua
Normal file
@ -0,0 +1,83 @@
|
||||
--Minetest
|
||||
--Copyright (C) 2013 sapier
|
||||
--
|
||||
--This program is free software; you can redistribute it and/or modify
|
||||
--it under the terms of the GNU Lesser General Public License as published by
|
||||
--the Free Software Foundation; either version 2.1 of the License, or
|
||||
--(at your option) any later version.
|
||||
--
|
||||
--This program is distributed in the hope that it will be useful,
|
||||
--but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
--GNU Lesser General Public License for more details.
|
||||
--
|
||||
--You should have received a copy of the GNU Lesser General Public License along
|
||||
--with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
gamemgr = {}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function gamemgr.find_by_gameid(gameid)
|
||||
for i=1,#gamemgr.games,1 do
|
||||
if gamemgr.games[i].id == gameid then
|
||||
return gamemgr.games[i], i
|
||||
end
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function gamemgr.get_game_mods(gamespec, retval)
|
||||
if gamespec ~= nil and
|
||||
gamespec.gamemods_path ~= nil and
|
||||
gamespec.gamemods_path ~= "" then
|
||||
get_mods(gamespec.gamemods_path, retval)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function gamemgr.get_game_modlist(gamespec)
|
||||
local retval = ""
|
||||
local game_mods = {}
|
||||
gamemgr.get_game_mods(gamespec, game_mods)
|
||||
for i=1,#game_mods,1 do
|
||||
if retval ~= "" then
|
||||
retval = retval..","
|
||||
end
|
||||
retval = retval .. game_mods[i].name
|
||||
end
|
||||
return retval
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function gamemgr.get_game(index)
|
||||
if index > 0 and index <= #gamemgr.games then
|
||||
return gamemgr.games[index]
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function gamemgr.update_gamelist()
|
||||
gamemgr.games = core.get_games()
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function gamemgr.gamelist()
|
||||
local retval = ""
|
||||
if #gamemgr.games > 0 then
|
||||
retval = retval .. gamemgr.games[1].name
|
||||
|
||||
for i=2,#gamemgr.games,1 do
|
||||
retval = retval .. "," .. gamemgr.games[i].name
|
||||
end
|
||||
end
|
||||
return retval
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- read initial data
|
||||
--------------------------------------------------------------------------------
|
||||
gamemgr.update_gamelist()
|
126
assets/Minetest/builtin/mainmenu/init.lua
Normal file
@ -0,0 +1,126 @@
|
||||
--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")
|
||||
|
||||
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")
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
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
|
||||
|
||||
|
||||
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()
|
||||
|
||||
-- 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
|
||||
tv_main:add(tab_singleplayer)
|
||||
tv_main:add(tab_multiplayer)
|
||||
tv_main:add(tab_server)
|
||||
tv_main:add(tab_settings)
|
||||
tv_main:add(tab_texturepacks)
|
||||
-- tv_main:add(tab_mods)
|
||||
-- tv_main:add(tab_credits)
|
||||
|
||||
tv_main:set_global_event_handler(main_event_handler)
|
||||
|
||||
tv_main:set_fixed_size(false)
|
||||
|
||||
tv_main:set_tab(core.setting_get("maintab_LAST"))
|
||||
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()
|
||||
|
4
assets/Minetest/builtin/mainmenu/init_simple.lua
Normal file
@ -0,0 +1,4 @@
|
||||
-- helper file to be able to debug the simple menu on PC
|
||||
-- without messing around with actual menu code!
|
||||
PLATFORM="Android"
|
||||
dofile("builtin/mainmenu/init.lua")
|
566
assets/Minetest/builtin/mainmenu/modmgr.lua
Normal file
@ -0,0 +1,566 @@
|
||||
--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
|
||||
elseif key:sub(0, 9) == "load_mod_" then
|
||||
worldconfig.global_mods[key] = core.is_yes(value)
|
||||
else
|
||||
worldconfig[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
--read gamemods
|
||||
local gamespec = gamemgr.find_by_gameid(worldconfig.id)
|
||||
gamemgr.get_game_mods(gamespec, worldconfig.game_mods)
|
||||
|
||||
return worldconfig
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function modmgr.installmod(modfilename,basename)
|
||||
local modfile = modmgr.identify_filetype(modfilename)
|
||||
local modpath = modmgr.extract(modfile)
|
||||
|
||||
if modpath == nil then
|
||||
gamedata.errormessage = fgettext("Install Mod: file: \"$1\"", modfile.name) ..
|
||||
fgettext("\nInstall Mod: unsupported filetype \"$1\" or broken archive", modfile.type)
|
||||
return
|
||||
end
|
||||
|
||||
local basefolder = modmgr.getbasefolder(modpath)
|
||||
|
||||
if basefolder.type == "modpack" then
|
||||
local clean_path = nil
|
||||
|
||||
if basename ~= nil then
|
||||
clean_path = "mp_" .. basename
|
||||
end
|
||||
|
||||
if clean_path == nil then
|
||||
clean_path = get_last_folder(cleanup_path(basefolder.path))
|
||||
end
|
||||
|
||||
if clean_path ~= nil then
|
||||
local targetpath = core.get_modpath() .. DIR_DELIM .. clean_path
|
||||
if not core.copy_dir(basefolder.path,targetpath) then
|
||||
gamedata.errormessage = fgettext("Failed to install $1 to $2", basename, targetpath)
|
||||
end
|
||||
else
|
||||
gamedata.errormessage = fgettext("Install Mod: unable to find suitable foldername for modpack $1", modfilename)
|
||||
end
|
||||
end
|
||||
|
||||
if basefolder.type == "mod" then
|
||||
local targetfolder = basename
|
||||
|
||||
if targetfolder == nil then
|
||||
targetfolder = modmgr.identify_modname(basefolder.path,"init.lua")
|
||||
end
|
||||
|
||||
--if heuristic failed try to use current foldername
|
||||
if targetfolder == nil then
|
||||
targetfolder = get_last_folder(basefolder.path)
|
||||
end
|
||||
|
||||
if targetfolder ~= nil and modmgr.isValidModname(targetfolder) then
|
||||
local targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder
|
||||
core.copy_dir(basefolder.path,targetpath)
|
||||
else
|
||||
gamedata.errormessage = fgettext("Install Mod: unable to find real modname for: $1", modfilename)
|
||||
end
|
||||
end
|
||||
|
||||
core.delete_dir(modpath)
|
||||
|
||||
modmgr.refresh_globals()
|
||||
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
function modmgr.preparemodlist(data)
|
||||
local retval = {}
|
||||
|
||||
local global_mods = {}
|
||||
local game_mods = {}
|
||||
|
||||
--read global mods
|
||||
local modpath = core.get_modpath()
|
||||
|
||||
if modpath ~= nil and
|
||||
modpath ~= "" then
|
||||
get_mods(modpath,global_mods)
|
||||
end
|
||||
|
||||
for i=1,#global_mods,1 do
|
||||
global_mods[i].typ = "global_mod"
|
||||
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
|
614
assets/Minetest/builtin/mainmenu/store.lua
Normal file
@ -0,0 +1,614 @@
|
||||
--Minetest
|
||||
--Copyright (C) 2013 sapier
|
||||
--
|
||||
--This program is free software; you can redistribute it and/or modify
|
||||
--it under the terms of the GNU Lesser General Public License as published by
|
||||
--the Free Software Foundation; either version 2.1 of the License, or
|
||||
--(at your option) any later version.
|
||||
--
|
||||
--This program is distributed in the hope that it will be useful,
|
||||
--but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
--GNU Lesser General Public License for more details.
|
||||
--
|
||||
--You should have received a copy of the GNU Lesser General Public License along
|
||||
--with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--modstore implementation
|
||||
modstore = {}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- @function [parent=#modstore] init
|
||||
function modstore.init(size, unsortedmods, searchmods)
|
||||
|
||||
modstore.mods_on_unsorted_page = unsortedmods
|
||||
modstore.mods_on_search_page = searchmods
|
||||
modstore.modsperpage = modstore.mods_on_unsorted_page
|
||||
|
||||
modstore.basetexturedir = core.get_texturepath() .. DIR_DELIM .. "base" ..
|
||||
DIR_DELIM .. "pack" .. DIR_DELIM
|
||||
|
||||
modstore.lastmodtitle = ""
|
||||
modstore.last_search = ""
|
||||
|
||||
modstore.searchlist = filterlist.create(
|
||||
function()
|
||||
if modstore.modlist_unsorted ~= nil and
|
||||
modstore.modlist_unsorted.data ~= nil then
|
||||
return modstore.modlist_unsorted.data
|
||||
end
|
||||
return {}
|
||||
end,
|
||||
function(element,modid)
|
||||
if element.id == modid then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end, --compare fct
|
||||
nil, --uid match fct
|
||||
function(element,substring)
|
||||
if substring == nil or
|
||||
substring == "" then
|
||||
return false
|
||||
end
|
||||
substring = substring:upper()
|
||||
|
||||
if element.title ~= nil and
|
||||
element.title:upper():find(substring) ~= nil then
|
||||
return true
|
||||
end
|
||||
|
||||
if element.details ~= nil and
|
||||
element.details.author ~= nil and
|
||||
element.details.author:upper():find(substring) ~= nil then
|
||||
return true
|
||||
end
|
||||
|
||||
if element.details ~= nil and
|
||||
element.details.description ~= nil and
|
||||
element.details.description:upper():find(substring) ~= nil then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end --filter fct
|
||||
)
|
||||
|
||||
modstore.current_list = nil
|
||||
|
||||
modstore.tv_store = tabview_create("modstore",size,{x=0,y=0})
|
||||
|
||||
modstore.tv_store:set_global_event_handler(modstore.handle_events)
|
||||
|
||||
modstore.tv_store:add(
|
||||
{
|
||||
name = "unsorted",
|
||||
caption = fgettext("Unsorted"),
|
||||
cbf_formspec = modstore.unsorted_tab,
|
||||
cbf_button_handler = modstore.handle_buttons,
|
||||
on_change =
|
||||
function() modstore.modsperpage = modstore.mods_on_unsorted_page end
|
||||
}
|
||||
)
|
||||
|
||||
modstore.tv_store:add(
|
||||
{
|
||||
name = "search",
|
||||
caption = fgettext("Search"),
|
||||
cbf_formspec = modstore.getsearchpage,
|
||||
cbf_button_handler = modstore.handle_buttons,
|
||||
on_change = modstore.activate_search_tab
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- @function [parent=#modstore] nametoindex
|
||||
function modstore.nametoindex(name)
|
||||
|
||||
for i=1,#modstore.tabnames,1 do
|
||||
if modstore.tabnames[i] == name then
|
||||
return i
|
||||
end
|
||||
end
|
||||
|
||||
return 1
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- @function [parent=#modstore] showdownloading
|
||||
function modstore.showdownloading(title)
|
||||
local new_dlg = dialog_create("store_downloading",
|
||||
function(data)
|
||||
return "size[6,2]label[0.25,0.75;" ..
|
||||
fgettext("Downloading $1, please wait...", data.title) .. "]"
|
||||
end,
|
||||
function(this,fields)
|
||||
if fields["btn_hidden_close_download"] ~= nil then
|
||||
if fields["btn_hidden_close_download"].successfull then
|
||||
modstore.lastmodentry = fields["btn_hidden_close_download"]
|
||||
modstore.successfulldialog(this)
|
||||
else
|
||||
this.parent:show()
|
||||
this:delete()
|
||||
modstore.lastmodtitle = ""
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end,
|
||||
nil)
|
||||
|
||||
new_dlg:set_parent(modstore.tv_store)
|
||||
modstore.tv_store:hide()
|
||||
new_dlg.data.title = title
|
||||
new_dlg:show()
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- @function [parent=#modstore] successfulldialog
|
||||
function modstore.successfulldialog(downloading_dlg)
|
||||
local new_dlg = dialog_create("store_downloading",
|
||||
function(data)
|
||||
local retval = ""
|
||||
retval = retval .. "size[6,2,true]"
|
||||
if modstore.lastmodentry ~= nil then
|
||||
retval = retval .. "label[0,0.25;" .. fgettext("Successfully installed:") .. "]"
|
||||
retval = retval .. "label[3,0.25;" .. modstore.lastmodentry.moddetails.title .. "]"
|
||||
retval = retval .. "label[0,0.75;" .. fgettext("Shortname:") .. "]"
|
||||
retval = retval .. "label[3,0.75;" .. core.formspec_escape(modstore.lastmodentry.moddetails.basename) .. "]"
|
||||
end
|
||||
retval = retval .. "button[2.2,1.5;1.5,0.5;btn_confirm_mod_successfull;" .. fgettext("Ok") .. "]"
|
||||
return retval
|
||||
end,
|
||||
function(this,fields)
|
||||
if fields["btn_confirm_mod_successfull"] ~= nil then
|
||||
this.parent:show()
|
||||
downloading_dlg:delete()
|
||||
this:delete()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end,
|
||||
nil)
|
||||
|
||||
new_dlg:set_parent(modstore.tv_store)
|
||||
modstore.tv_store:hide()
|
||||
new_dlg:show()
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- @function [parent=#modstore] handle_buttons
|
||||
function modstore.handle_buttons(parent, fields, name, data)
|
||||
|
||||
if fields["btn_modstore_page_up"] then
|
||||
if modstore.current_list ~= nil and modstore.current_list.page > 0 then
|
||||
modstore.current_list.page = modstore.current_list.page - 1
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if fields["btn_modstore_page_down"] then
|
||||
if modstore.current_list ~= nil and
|
||||
modstore.current_list.page <modstore.current_list.pagecount-1 then
|
||||
modstore.current_list.page = modstore.current_list.page +1
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if fields["btn_modstore_search"] or
|
||||
(fields["key_enter"] and fields["te_modstore_search"] ~= nil) then
|
||||
modstore.last_search = fields["te_modstore_search"]
|
||||
filterlist.set_filtercriteria(modstore.searchlist,fields["te_modstore_search"])
|
||||
filterlist.refresh(modstore.searchlist)
|
||||
modstore.currentlist = {
|
||||
page = 0,
|
||||
pagecount = math.ceil(filterlist.size(modstore.searchlist) / modstore.modsperpage),
|
||||
data = filterlist.get_list(modstore.searchlist),
|
||||
}
|
||||
return true
|
||||
end
|
||||
|
||||
if fields["btn_modstore_close"] then
|
||||
local maintab = ui.find_by_name("maintab")
|
||||
parent:hide()
|
||||
maintab:show()
|
||||
return true
|
||||
end
|
||||
|
||||
for key,value in pairs(fields) do
|
||||
local foundat = key:find("btn_install_mod_")
|
||||
if ( foundat == 1) then
|
||||
local modid = tonumber(key:sub(17))
|
||||
for i=1,#modstore.modlist_unsorted.data,1 do
|
||||
if modstore.modlist_unsorted.data[i].id == modid then
|
||||
local moddetails = modstore.modlist_unsorted.data[i].details
|
||||
modstore.lastmodtitle = moddetails.title
|
||||
|
||||
if not core.handle_async(
|
||||
function(param)
|
||||
local fullurl = core.setting_get("modstore_download_url") ..
|
||||
param.moddetails.download_url
|
||||
|
||||
if param.version ~= nil then
|
||||
local found = false
|
||||
for i=1,#param.moddetails.versions, 1 do
|
||||
if param.moddetails.versions[i].date:sub(1,10) == param.version then
|
||||
fullurl = core.setting_get("modstore_download_url") ..
|
||||
param.moddetails.versions[i].download_url
|
||||
found = true
|
||||
end
|
||||
end
|
||||
|
||||
if not found then
|
||||
core.log("error","no download url found for version " .. dump(param.version))
|
||||
return {
|
||||
moddetails = param.moddetails,
|
||||
successfull = false
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
if core.download_file(fullurl,param.filename) then
|
||||
return {
|
||||
texturename = param.texturename,
|
||||
moddetails = param.moddetails,
|
||||
filename = param.filename,
|
||||
successfull = true
|
||||
}
|
||||
else
|
||||
core.log("error","downloading " .. dump(fullurl) .. " failed")
|
||||
return {
|
||||
moddetails = param.moddetails,
|
||||
successfull = false
|
||||
}
|
||||
end
|
||||
end,
|
||||
{
|
||||
moddetails = moddetails,
|
||||
version = fields["dd_version" .. modid],
|
||||
filename = os.tempfolder() .. "_MODNAME_" .. moddetails.basename .. ".zip",
|
||||
texturename = modstore.modlist_unsorted.data[i].texturename
|
||||
},
|
||||
function(result)
|
||||
--print("Result from async: " .. dump(result.successfull))
|
||||
if result.successfull then
|
||||
modmgr.installmod(result.filename,result.moddetails.basename)
|
||||
os.remove(result.filename)
|
||||
else
|
||||
gamedata.errormessage = "Failed to download " .. result.moddetails.title
|
||||
end
|
||||
|
||||
if gamedata.errormessage == nil then
|
||||
core.button_handler({btn_hidden_close_download=result})
|
||||
else
|
||||
core.button_handler({btn_hidden_close_download={successfull=false}})
|
||||
end
|
||||
end
|
||||
) then
|
||||
print("ERROR: async event failed")
|
||||
gamedata.errormessage = "Failed to download " .. modstore.lastmodtitle
|
||||
end
|
||||
|
||||
modstore.showdownloading(modstore.lastmodtitle)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- @function [parent=#modstore] handle_events
|
||||
function modstore.handle_events(this,event)
|
||||
if (event == "MenuQuit") then
|
||||
this:hide()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- @function [parent=#modstore] update_modlist
|
||||
function modstore.update_modlist()
|
||||
modstore.modlist_unsorted = {}
|
||||
modstore.modlist_unsorted.data = {}
|
||||
modstore.modlist_unsorted.pagecount = 1
|
||||
modstore.modlist_unsorted.page = 0
|
||||
|
||||
core.handle_async(
|
||||
function(param)
|
||||
return core.get_modstore_list()
|
||||
end,
|
||||
nil,
|
||||
function(result)
|
||||
if result ~= nil then
|
||||
modstore.modlist_unsorted = {}
|
||||
modstore.modlist_unsorted.data = result
|
||||
|
||||
if modstore.modlist_unsorted.data ~= nil then
|
||||
modstore.modlist_unsorted.pagecount =
|
||||
math.ceil((#modstore.modlist_unsorted.data / modstore.modsperpage))
|
||||
else
|
||||
modstore.modlist_unsorted.data = {}
|
||||
modstore.modlist_unsorted.pagecount = 1
|
||||
end
|
||||
modstore.modlist_unsorted.page = 0
|
||||
modstore.fetchdetails()
|
||||
core.event_handler("Refresh")
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- @function [parent=#modstore] fetchdetails
|
||||
function modstore.fetchdetails()
|
||||
|
||||
for i=1,#modstore.modlist_unsorted.data,1 do
|
||||
core.handle_async(
|
||||
function(param)
|
||||
param.details = core.get_modstore_details(tostring(param.modid))
|
||||
return param
|
||||
end,
|
||||
{
|
||||
modid=modstore.modlist_unsorted.data[i].id,
|
||||
listindex=i
|
||||
},
|
||||
function(result)
|
||||
if result ~= nil and
|
||||
modstore.modlist_unsorted ~= nil
|
||||
and modstore.modlist_unsorted.data ~= nil and
|
||||
modstore.modlist_unsorted.data[result.listindex] ~= nil and
|
||||
modstore.modlist_unsorted.data[result.listindex].id ~= nil then
|
||||
|
||||
modstore.modlist_unsorted.data[result.listindex].details = result.details
|
||||
core.event_handler("Refresh")
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- @function [parent=#modstore] getscreenshot
|
||||
function modstore.getscreenshot(ypos,listentry)
|
||||
|
||||
if listentry.details ~= nil and
|
||||
(listentry.details.screenshot_url == nil or
|
||||
listentry.details.screenshot_url == "") then
|
||||
|
||||
if listentry.texturename == nil then
|
||||
listentry.texturename = defaulttexturedir .. "no_screenshot.png"
|
||||
end
|
||||
|
||||
return "image[0,".. ypos .. ";3,2;" ..
|
||||
core.formspec_escape(listentry.texturename) .. "]"
|
||||
end
|
||||
|
||||
if listentry.details ~= nil and
|
||||
listentry.texturename == nil then
|
||||
--make sure we don't download multiple times
|
||||
listentry.texturename = "in progress"
|
||||
|
||||
--prepare url and filename
|
||||
local fullurl = core.setting_get("modstore_download_url") ..
|
||||
listentry.details.screenshot_url
|
||||
local filename = os.tempfolder() .. "_MID_" .. listentry.id
|
||||
|
||||
--trigger download
|
||||
core.handle_async(
|
||||
--first param is downloadfct
|
||||
function(param)
|
||||
param.successfull = core.download_file(param.fullurl,param.filename)
|
||||
return param
|
||||
end,
|
||||
--second parameter is data passed to async job
|
||||
{
|
||||
fullurl = fullurl,
|
||||
filename = filename,
|
||||
modid = listentry.id
|
||||
},
|
||||
--integrate result to raw list
|
||||
function(result)
|
||||
if result.successfull then
|
||||
local found = false
|
||||
for i=1,#modstore.modlist_unsorted.data,1 do
|
||||
if modstore.modlist_unsorted.data[i].id == result.modid then
|
||||
found = true
|
||||
modstore.modlist_unsorted.data[i].texturename = result.filename
|
||||
break
|
||||
end
|
||||
end
|
||||
if found then
|
||||
core.event_handler("Refresh")
|
||||
else
|
||||
core.log("error","got screenshot but didn't find matching mod: " .. result.modid)
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
if listentry.texturename ~= nil and
|
||||
listentry.texturename ~= "in progress" then
|
||||
return "image[0,".. ypos .. ";3,2;" ..
|
||||
core.formspec_escape(listentry.texturename) .. "]"
|
||||
end
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--@function [parent=#modstore] getshortmodinfo
|
||||
function modstore.getshortmodinfo(ypos,listentry,details)
|
||||
local retval = ""
|
||||
|
||||
retval = retval .. "box[0," .. ypos .. ";11.4,1.75;#FFFFFF]"
|
||||
|
||||
--screenshot
|
||||
retval = retval .. modstore.getscreenshot(ypos,listentry)
|
||||
|
||||
--title + author
|
||||
retval = retval .."label[2.75," .. ypos .. ";" ..
|
||||
core.formspec_escape(details.title) .. " (" .. details.author .. ")]"
|
||||
|
||||
--description
|
||||
local descriptiony = ypos + 0.5
|
||||
retval = retval .. "textarea[3," .. descriptiony .. ";6.5,1.55;;" ..
|
||||
core.formspec_escape(details.description) .. ";]"
|
||||
|
||||
--rating
|
||||
local ratingy = ypos
|
||||
retval = retval .."label[7," .. ratingy .. ";" ..
|
||||
fgettext("Rating") .. ":]"
|
||||
retval = retval .. "label[8.7," .. ratingy .. ";" .. details.rating .."]"
|
||||
|
||||
--versions (IMPORTANT has to be defined AFTER rating)
|
||||
if details.versions ~= nil and
|
||||
#details.versions > 1 then
|
||||
local versiony = ypos + 0.05
|
||||
retval = retval .. "dropdown[9.1," .. versiony .. ";2.48,0.25;dd_version" .. details.id .. ";"
|
||||
local versions = ""
|
||||
for i=1,#details.versions , 1 do
|
||||
if versions ~= "" then
|
||||
versions = versions .. ","
|
||||
end
|
||||
|
||||
versions = versions .. details.versions[i].date:sub(1,10)
|
||||
end
|
||||
retval = retval .. versions .. ";1]"
|
||||
end
|
||||
|
||||
if details.basename then
|
||||
--install button
|
||||
local buttony = ypos + 1.2
|
||||
retval = retval .."button[9.1," .. buttony .. ";2.5,0.5;btn_install_mod_" .. details.id .. ";"
|
||||
|
||||
if modmgr.mod_exists(details.basename) then
|
||||
retval = retval .. fgettext("re-Install") .."]"
|
||||
else
|
||||
retval = retval .. fgettext("Install") .."]"
|
||||
end
|
||||
end
|
||||
|
||||
return retval
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--@function [parent=#modstore] getmodlist
|
||||
function modstore.getmodlist(list,yoffset)
|
||||
modstore.current_list = list
|
||||
|
||||
if yoffset == nil then
|
||||
yoffset = 0
|
||||
end
|
||||
|
||||
local sb_y_start = 0.2 + yoffset
|
||||
local sb_y_end = (modstore.modsperpage * 1.75) + ((modstore.modsperpage-1) * 0.15)
|
||||
local close_button = "button[4," .. (sb_y_end + 0.3 + yoffset) ..
|
||||
";4,0.5;btn_modstore_close;" .. fgettext("Close store") .. "]"
|
||||
|
||||
if #list.data == 0 then
|
||||
return close_button
|
||||
end
|
||||
|
||||
local scrollbar = ""
|
||||
scrollbar = scrollbar .. "label[0.1,".. (sb_y_end + 0.25 + yoffset) ..";"
|
||||
.. fgettext("Page $1 of $2", list.page+1, list.pagecount) .. "]"
|
||||
scrollbar = scrollbar .. "box[11.6," .. sb_y_start .. ";0.28," .. sb_y_end .. ";#000000]"
|
||||
local scrollbarpos = (sb_y_start + 0.5) +
|
||||
((sb_y_end -1.6)/(list.pagecount-1)) * list.page
|
||||
scrollbar = scrollbar .. "box[11.6," ..scrollbarpos .. ";0.28,0.5;#32CD32]"
|
||||
scrollbar = scrollbar .. "button[11.6," .. (sb_y_start)
|
||||
.. ";0.5,0.5;btn_modstore_page_up;^]"
|
||||
scrollbar = scrollbar .. "button[11.6," .. (sb_y_start + sb_y_end - 0.5)
|
||||
.. ";0.5,0.5;btn_modstore_page_down;v]"
|
||||
|
||||
local retval = ""
|
||||
|
||||
local endmod = (list.page * modstore.modsperpage) + modstore.modsperpage
|
||||
|
||||
if (endmod > #list.data) then
|
||||
endmod = #list.data
|
||||
end
|
||||
|
||||
for i=(list.page * modstore.modsperpage) +1, endmod, 1 do
|
||||
--getmoddetails
|
||||
local details = list.data[i].details
|
||||
|
||||
if details == nil then
|
||||
details = {}
|
||||
details.title = list.data[i].title
|
||||
details.author = ""
|
||||
details.rating = -1
|
||||
details.description = ""
|
||||
end
|
||||
|
||||
if details ~= nil then
|
||||
local screenshot_ypos =
|
||||
yoffset +(i-1 - (list.page * modstore.modsperpage))*1.9 +0.2
|
||||
|
||||
retval = retval .. modstore.getshortmodinfo(screenshot_ypos,
|
||||
list.data[i],
|
||||
details)
|
||||
end
|
||||
end
|
||||
|
||||
return retval .. scrollbar .. close_button
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--@function [parent=#modstore] getsearchpage
|
||||
function modstore.getsearchpage(tabview, name, tabdata)
|
||||
local retval = ""
|
||||
local search = ""
|
||||
|
||||
if modstore.last_search ~= nil then
|
||||
search = modstore.last_search
|
||||
end
|
||||
|
||||
retval = retval ..
|
||||
"button[9.5,0.2;2.5,0.5;btn_modstore_search;".. fgettext("Search") .. "]" ..
|
||||
"field[0.5,0.5;9,0.5;te_modstore_search;;" .. search .. "]"
|
||||
|
||||
retval = retval ..
|
||||
modstore.getmodlist(
|
||||
modstore.currentlist,
|
||||
1.75)
|
||||
|
||||
return retval;
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--@function [parent=#modstore] unsorted_tab
|
||||
function modstore.unsorted_tab()
|
||||
return modstore.getmodlist(modstore.modlist_unsorted)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
--@function [parent=#modstore] activate_search_tab
|
||||
function modstore.activate_search_tab(type, old_tab, new_tab)
|
||||
|
||||
if old_tab == new_tab then
|
||||
return
|
||||
end
|
||||
filterlist.set_filtercriteria(modstore.searchlist,modstore.last_search)
|
||||
filterlist.refresh(modstore.searchlist)
|
||||
modstore.modsperpage = modstore.mods_on_search_page
|
||||
modstore.currentlist = {
|
||||
page = 0,
|
||||
pagecount =
|
||||
math.ceil(filterlist.size(modstore.searchlist) / modstore.modsperpage),
|
||||
data = filterlist.get_list(modstore.searchlist),
|
||||
}
|
||||
end
|
||||
|
169
assets/Minetest/builtin/mainmenu/tab_mods.lua
Normal file
@ -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
|
||||
}
|
261
assets/Minetest/builtin/mainmenu/tab_multiplayer.lua
Normal file
@ -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
|
||||
}
|
222
assets/Minetest/builtin/mainmenu/tab_server.lua
Normal file
@ -0,0 +1,222 @@
|
||||
--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"])
|
||||
|
||||
local selected = core.get_textlist_index("srv_worlds")
|
||||
if selected ~= nil then
|
||||
local filename = menudata.worldlist:get_list()[selected].path
|
||||
local worldconfig = modmgr.get_worldconfig(filename)
|
||||
filename = filename .. DIR_DELIM .. "world.mt"
|
||||
|
||||
if worldconfig.creative_mode ~= nil then
|
||||
core.setting_set("creative_mode", worldconfig.creative_mode)
|
||||
else
|
||||
local worldfile = Settings(filename)
|
||||
worldfile:set("creative_mode", core.setting_get("creative_mode"))
|
||||
if not worldfile:write() then
|
||||
core.log("error", "Failed to write world config file")
|
||||
end
|
||||
end
|
||||
if worldconfig.enable_damage ~= nil then
|
||||
core.setting_set("enable_damage", worldconfig.enable_damage)
|
||||
else
|
||||
local worldfile = Settings(filename)
|
||||
worldfile:set("enable_damage", core.setting_get("enable_damage"))
|
||||
if not worldfile:write() then
|
||||
core.log("error", "Failed to write world config file")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if event.type == "DCL" then
|
||||
world_doubleclick = true
|
||||
end
|
||||
if event.type == "CHG" then
|
||||
core.setting_set("mainmenu_last_selected_world",
|
||||
menudata.worldlist:get_raw_index(core.get_textlist_index("srv_worlds")))
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if menu_handle_key_up_down(fields,"srv_worlds","mainmenu_last_selected_world") then
|
||||
return true
|
||||
end
|
||||
|
||||
if fields["cb_creative_mode"] then
|
||||
core.setting_set("creative_mode", fields["cb_creative_mode"])
|
||||
local selected = core.get_textlist_index("srv_worlds")
|
||||
local filename = menudata.worldlist:get_list()[selected].path ..
|
||||
DIR_DELIM .. "world.mt"
|
||||
|
||||
local worldfile = Settings(filename)
|
||||
worldfile:set("creative_mode", fields["cb_creative_mode"])
|
||||
if not worldfile:write() then
|
||||
core.log("error", "Failed to write world config file")
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if fields["cb_enable_damage"] then
|
||||
core.setting_set("enable_damage", fields["cb_enable_damage"])
|
||||
local selected = core.get_textlist_index("srv_worlds")
|
||||
local filename = menudata.worldlist:get_list()[selected].path ..
|
||||
DIR_DELIM .. "world.mt"
|
||||
|
||||
local worldfile = Settings(filename)
|
||||
worldfile:set("enable_damage", fields["cb_enable_damage"])
|
||||
if not worldfile:write() then
|
||||
core.log("error", "Failed to write world config file")
|
||||
end
|
||||
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
|
||||
}
|
401
assets/Minetest/builtin/mainmenu/tab_settings.lua
Normal file
@ -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
|
||||
}
|
212
assets/Minetest/builtin/mainmenu/tab_simple_main.lua
Normal file
@ -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_table_event(fields["favourites"])
|
||||
|
||||
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
|
||||
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
|
||||
}
|
272
assets/Minetest/builtin/mainmenu/tab_singleplayer.lua
Normal file
@ -0,0 +1,272 @@
|
||||
--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"])
|
||||
|
||||
local selected = core.get_textlist_index("sp_worlds")
|
||||
if selected ~= nil then
|
||||
local filename = menudata.worldlist:get_list()[selected].path
|
||||
local worldconfig = modmgr.get_worldconfig(filename)
|
||||
filename = filename .. DIR_DELIM .. "world.mt"
|
||||
|
||||
if worldconfig.creative_mode ~= nil then
|
||||
core.setting_set("creative_mode", worldconfig.creative_mode)
|
||||
else
|
||||
local worldfile = Settings(filename)
|
||||
worldfile:set("creative_mode", core.setting_get("creative_mode"))
|
||||
if not worldfile:write() then
|
||||
core.log("error", "Failed to write world config file")
|
||||
end
|
||||
end
|
||||
if worldconfig.enable_damage ~= nil then
|
||||
core.setting_set("enable_damage", worldconfig.enable_damage)
|
||||
else
|
||||
local worldfile = Settings(filename)
|
||||
worldfile:set("enable_damage", core.setting_get("enable_damage"))
|
||||
if not worldfile:write() then
|
||||
core.log("error", "Failed to write world config file")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if event.type == "DCL" then
|
||||
world_doubleclick = true
|
||||
end
|
||||
|
||||
if event.type == "CHG" and selected ~= nil then
|
||||
core.setting_set("mainmenu_last_selected_world",
|
||||
menudata.worldlist:get_raw_index(selected))
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if menu_handle_key_up_down(fields,"sp_worlds","mainmenu_last_selected_world") then
|
||||
return true
|
||||
end
|
||||
|
||||
if fields["cb_creative_mode"] then
|
||||
core.setting_set("creative_mode", fields["cb_creative_mode"])
|
||||
local selected = core.get_textlist_index("sp_worlds")
|
||||
local filename = menudata.worldlist:get_list()[selected].path ..
|
||||
DIR_DELIM .. "world.mt"
|
||||
|
||||
local worldfile = Settings(filename)
|
||||
worldfile:set("creative_mode", fields["cb_creative_mode"])
|
||||
if not worldfile:write() then
|
||||
core.log("error", "Failed to write world config file")
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if fields["cb_enable_damage"] then
|
||||
core.setting_set("enable_damage", fields["cb_enable_damage"])
|
||||
local selected = core.get_textlist_index("sp_worlds")
|
||||
local filename = menudata.worldlist:get_list()[selected].path ..
|
||||
DIR_DELIM .. "world.mt"
|
||||
|
||||
local worldfile = Settings(filename)
|
||||
worldfile:set("enable_damage", fields["cb_enable_damage"])
|
||||
if not worldfile:write() then
|
||||
core.log("error", "Failed to write world config file")
|
||||
end
|
||||
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
|
||||
}
|
118
assets/Minetest/builtin/mainmenu/tab_texturepacks.lua
Normal file
@ -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
|
||||
}
|
166
assets/Minetest/builtin/mainmenu/textures.lua
Normal file
@ -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
|
12
assets/Minetest/client/serverlist/favoriteservers.txt
Normal file
@ -0,0 +1,12 @@
|
||||
[server]
|
||||
|
||||
87.110.8.195
|
||||
30000
|
||||
|
||||
|
||||
[server]
|
||||
|
||||
98.166.169.32
|
||||
30000
|
||||
|
||||
|
121
assets/Minetest/client/shaders/nodes_shader/opengl_fragment.glsl
Normal file
@ -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
|
||||
}
|
144
assets/Minetest/client/shaders/nodes_shader/opengl_vertex.glsl
Normal file
@ -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);
|
||||
}
|
@ -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
|
||||
}
|
@ -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);
|
||||
}
|
32
assets/Minetest/doc/Doxyfile.in
Normal file
@ -0,0 +1,32 @@
|
||||
DOXYFILE_ENCODING = UTF-8
|
||||
|
||||
PROJECT_NAME = "Minetest"
|
||||
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
|
||||
|
130
assets/Minetest/doc/README.android
Normal file
@ -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=<path to your keystore>
|
||||
> 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
|
171
assets/Minetest/doc/fst_api.txt
Normal file
@ -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
|
||||
}
|
502
assets/Minetest/doc/lgpl-2.1.txt
Normal file
@ -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.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
3355
assets/Minetest/doc/lua_api.txt
Normal file
583
assets/Minetest/doc/mapformat.txt
Normal file
@ -0,0 +1,583 @@
|
||||
=============================
|
||||
Minetest World Format 22...25
|
||||
=============================
|
||||
|
||||
This applies to a world format carrying the block serialization version
|
||||
22...25, used at least in
|
||||
- 0.4.dev-20120322 ... 0.4.dev-20120606 (22...23)
|
||||
- 0.4.0 (23)
|
||||
- 24 was never released as stable and existed for ~2 days
|
||||
|
||||
The block serialization version does not fully specify every aspect of this
|
||||
format; if compliance with this format is to be checked, it needs to be
|
||||
done by detecting if the files and data indeed follows it.
|
||||
|
||||
Legacy stuff
|
||||
=============
|
||||
Data can, in theory, be contained in the flat file directory structure
|
||||
described below in Version 17, but it is not officially supported. Also you
|
||||
may stumble upon all kinds of oddities in not-so-recent formats.
|
||||
|
||||
Files
|
||||
======
|
||||
Everything is contained in a directory, the name of which is freeform, but
|
||||
often serves as the name of the world.
|
||||
|
||||
Currently the authentication and ban data is stored on a per-world basis.
|
||||
It can be copied over from an old world to a newly created world.
|
||||
|
||||
World
|
||||
|-- auth.txt ----- Authentication data
|
||||
|-- env_meta.txt - Environment metadata
|
||||
|-- ipban.txt ---- Banned ips/users
|
||||
|-- map_meta.txt - Map metadata
|
||||
|-- map.sqlite --- Map data
|
||||
|-- players ------ Player directory
|
||||
| |-- player1 -- Player file
|
||||
| '-- Foo ------ Player file
|
||||
`-- world.mt ----- World metadata
|
||||
|
||||
auth.txt
|
||||
---------
|
||||
Contains authentication data, player per line.
|
||||
<name>:<password hash>:<privilege1,...>
|
||||
Format of password hash is <name><password> SHA1'd, in the base64 encoding.
|
||||
|
||||
Example lines:
|
||||
- Player "celeron55", no password, privileges "interact" and "shout":
|
||||
celeron55::interact,shout
|
||||
- Player "Foo", password "bar", privilege "shout":
|
||||
foo:iEPX+SQWIR3p67lj/0zigSWTKHg:shout
|
||||
- Player "bar", no password, no privileges:
|
||||
bar::
|
||||
|
||||
env_meta.txt
|
||||
-------------
|
||||
Simple global environment variables.
|
||||
Example content (added indentation):
|
||||
game_time = 73471
|
||||
time_of_day = 19118
|
||||
EnvArgsEnd
|
||||
|
||||
ipban.txt
|
||||
----------
|
||||
Banned IP addresses and usernames.
|
||||
Example content (added indentation):
|
||||
123.456.78.9|foo
|
||||
123.456.78.10|bar
|
||||
|
||||
map_meta.txt
|
||||
-------------
|
||||
Simple global map variables.
|
||||
Example content (added indentation):
|
||||
seed = 7980462765762429666
|
||||
[end_of_params]
|
||||
|
||||
map.sqlite
|
||||
-----------
|
||||
Map data.
|
||||
See Map File Format below.
|
||||
|
||||
player1, Foo
|
||||
-------------
|
||||
Player data.
|
||||
Filename can be anything.
|
||||
See Player File Format below.
|
||||
|
||||
world.mt
|
||||
---------
|
||||
World metadata.
|
||||
Example content (added indentation):
|
||||
gameid = mesetint
|
||||
|
||||
Player File Format
|
||||
===================
|
||||
|
||||
- Should be pretty self-explanatory.
|
||||
- Note: position is in nodes * 10
|
||||
|
||||
Example content (added indentation):
|
||||
hp = 11
|
||||
name = celeron55
|
||||
pitch = 39.77
|
||||
position = (-5231.97,15,1961.41)
|
||||
version = 1
|
||||
yaw = 101.37
|
||||
PlayerArgsEnd
|
||||
List main 32
|
||||
Item default:torch 13
|
||||
Item default:pick_steel 1 50112
|
||||
Item experimental:tnt
|
||||
Item default:cobble 99
|
||||
Item default:pick_stone 1 13104
|
||||
Item default:shovel_steel 1 51838
|
||||
Item default:dirt 61
|
||||
Item default:rail 78
|
||||
Item default:coal_lump 3
|
||||
Item default:cobble 99
|
||||
Item default:leaves 22
|
||||
Item default:gravel 52
|
||||
Item default:axe_steel 1 2045
|
||||
Item default:cobble 98
|
||||
Item default:sand 61
|
||||
Item default:water_source 94
|
||||
Item default:glass 2
|
||||
Item default:mossycobble
|
||||
Item default:pick_steel 1 64428
|
||||
Item animalmaterials:bone
|
||||
Item default:sword_steel
|
||||
Item default:sapling
|
||||
Item default:sword_stone 1 10647
|
||||
Item default:dirt 99
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
EndInventoryList
|
||||
List craft 9
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
EndInventoryList
|
||||
List craftpreview 1
|
||||
Empty
|
||||
EndInventoryList
|
||||
List craftresult 1
|
||||
Empty
|
||||
EndInventoryList
|
||||
EndInventory
|
||||
|
||||
Map File Format
|
||||
================
|
||||
|
||||
Minetest maps consist of MapBlocks, chunks of 16x16x16 nodes.
|
||||
|
||||
In addition to the bulk node data, MapBlocks stored on disk also contain
|
||||
other things.
|
||||
|
||||
History
|
||||
--------
|
||||
We need a bit of history in here. Initially Minetest stored maps in a
|
||||
format called the "sectors" format. It was a directory/file structure like
|
||||
this:
|
||||
sectors2/XXX/ZZZ/YYYY
|
||||
For example, the MapBlock at (0,1,-2) was this file:
|
||||
sectors2/000/ffd/0001
|
||||
|
||||
Eventually Minetest outgrow this directory structure, as filesystems were
|
||||
struggling under the amount of files and directories.
|
||||
|
||||
Large servers seriously needed a new format, and thus the base of the
|
||||
current format was invented, suggested by celeron55 and implemented by
|
||||
JacobF.
|
||||
|
||||
SQLite3 was slammed in, and blocks files were directly inserted as blobs
|
||||
in a single table, indexed by integer primary keys, oddly mangled from
|
||||
coordinates.
|
||||
|
||||
Today we know that SQLite3 allows multiple primary keys (which would allow
|
||||
storing coordinates separately), but the format has been kept unchanged for
|
||||
that part. So, this is where it has come.
|
||||
</history>
|
||||
|
||||
So here goes
|
||||
-------------
|
||||
map.sqlite is an sqlite3 database, containing a single table, called
|
||||
"blocks". It looks like this:
|
||||
|
||||
CREATE TABLE `blocks` (`pos` INT NOT NULL PRIMARY KEY,`data` BLOB);
|
||||
|
||||
The key
|
||||
--------
|
||||
"pos" is created from the three coordinates of a MapBlock using this
|
||||
algorithm, defined here in Python:
|
||||
|
||||
def getBlockAsInteger(p):
|
||||
return int64(p[2]*16777216 + p[1]*4096 + p[0])
|
||||
|
||||
def int64(u):
|
||||
while u >= 2**63:
|
||||
u -= 2**64
|
||||
while u <= -2**63:
|
||||
u += 2**64
|
||||
return u
|
||||
|
||||
It can be converted the other way by using this code:
|
||||
|
||||
def getIntegerAsBlock(i):
|
||||
x = unsignedToSigned(i % 4096, 2048)
|
||||
i = int((i - x) / 4096)
|
||||
y = unsignedToSigned(i % 4096, 2048)
|
||||
i = int((i - y) / 4096)
|
||||
z = unsignedToSigned(i % 4096, 2048)
|
||||
return x,y,z
|
||||
|
||||
def unsignedToSigned(i, max_positive):
|
||||
if i < max_positive:
|
||||
return i
|
||||
else:
|
||||
return i - 2*max_positive
|
||||
|
||||
The blob
|
||||
---------
|
||||
The blob is the data that would have otherwise gone into the file.
|
||||
|
||||
See below for description.
|
||||
|
||||
MapBlock serialization format
|
||||
==============================
|
||||
NOTE: Byte order is MSB first (big-endian).
|
||||
NOTE: Zlib data is in such a format that Python's zlib at least can
|
||||
directly decompress.
|
||||
|
||||
u8 version
|
||||
- map format version number, this one is version 22
|
||||
|
||||
u8 flags
|
||||
- Flag bitmasks:
|
||||
- 0x01: is_underground: Should be set to 0 if there will be no light
|
||||
obstructions above the block. If/when sunlight of a block is updated
|
||||
and there is no block above it, this value is checked for determining
|
||||
whether sunlight comes from the top.
|
||||
- 0x02: day_night_differs: Whether the lighting of the block is different
|
||||
on day and night. Only blocks that have this bit set are updated when
|
||||
day transforms to night.
|
||||
- 0x04: lighting_expired: If true, lighting is invalid and should be
|
||||
updated. If you can't calculate lighting in your generator properly,
|
||||
you could try setting this 1 to everything and setting the uppermost
|
||||
block in every sector as is_underground=0. I am quite sure it doesn't
|
||||
work properly, though.
|
||||
- 0x08: generated: True if the block has been generated. If false, block
|
||||
is mostly filled with CONTENT_IGNORE and is likely to contain eg. parts
|
||||
of trees of neighboring blocks.
|
||||
|
||||
u8 content_width
|
||||
- Number of bytes in the content (param0) fields of nodes
|
||||
if map format version <= 23:
|
||||
- Always 1
|
||||
if map format version >= 24:
|
||||
- Always 2
|
||||
|
||||
u8 params_width
|
||||
- Number of bytes used for parameters per node
|
||||
- Always 2
|
||||
|
||||
zlib-compressed node data:
|
||||
if content_width == 1:
|
||||
- content:
|
||||
u8[4096]: param0 fields
|
||||
u8[4096]: param1 fields
|
||||
u8[4096]: param2 fields
|
||||
if content_width == 2:
|
||||
- content:
|
||||
u16[4096]: param0 fields
|
||||
u8[4096]: param1 fields
|
||||
u8[4096]: param2 fields
|
||||
- The location of a node in each of those arrays is (z*16*16 + y*16 + x).
|
||||
|
||||
zlib-compressed node metadata list
|
||||
- content:
|
||||
u16 version (=1)
|
||||
u16 count of metadata
|
||||
foreach count:
|
||||
u16 position (p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X)
|
||||
u16 type_id
|
||||
u16 content_size
|
||||
u8[content_size] (content of metadata)
|
||||
|
||||
- Node timers
|
||||
if map format version == 23:
|
||||
u8 unused version (always 0)
|
||||
if map format version == 24: (NOTE: Not released as stable)
|
||||
u8 nodetimer_version
|
||||
if nodetimer_version == 0:
|
||||
(nothing else)
|
||||
if nodetimer_version == 1:
|
||||
u16 num_of_timers
|
||||
foreach num_of_timers:
|
||||
u16 timer position (z*16*16 + y*16 + x)
|
||||
s32 timeout*1000
|
||||
s32 elapsed*1000
|
||||
|
||||
u8 static object version:
|
||||
- Always 0
|
||||
|
||||
u16 static_object_count
|
||||
|
||||
foreach static_object_count:
|
||||
u8 type (object type-id)
|
||||
s32 pos_x_nodes * 10000
|
||||
s32 pos_y_nodes * 10000
|
||||
s32 pos_z_nodes * 10000
|
||||
u16 data_size
|
||||
u8[data_size] data
|
||||
|
||||
u32 timestamp
|
||||
- Timestamp when last saved, as seconds from starting the game.
|
||||
- 0xffffffff = invalid/unknown timestamp, nothing should be done with the time
|
||||
difference when loaded
|
||||
|
||||
u8 name-id-mapping version
|
||||
- Always 0
|
||||
|
||||
u16 num_name_id_mappings
|
||||
|
||||
foreach num_name_id_mappings
|
||||
u16 id
|
||||
u16 name_len
|
||||
u8[name_len] name
|
||||
|
||||
- Node timers
|
||||
if map format version == 25:
|
||||
u8 length of the data of a single timer (always 2+4+4=10)
|
||||
u16 num_of_timers
|
||||
foreach num_of_timers:
|
||||
u16 timer position (z*16*16 + y*16 + x)
|
||||
s32 timeout*1000
|
||||
s32 elapsed*1000
|
||||
|
||||
EOF.
|
||||
|
||||
Format of nodes
|
||||
----------------
|
||||
A node is composed of the u8 fields param0, param1 and param2.
|
||||
|
||||
if map format version <= 23:
|
||||
The content id of a node is determined as so:
|
||||
- If param0 < 0x80,
|
||||
content_id = param0
|
||||
- Otherwise
|
||||
content_id = (param0<<4) + (param2>>4)
|
||||
if map format version >= 24:
|
||||
The content id of a node is param0.
|
||||
|
||||
The purpose of param1 and param2 depend on the definition of the node.
|
||||
|
||||
The name-id-mapping
|
||||
--------------------
|
||||
The mapping maps node content ids to node names.
|
||||
|
||||
Node metadata format
|
||||
---------------------
|
||||
|
||||
1: Generic metadata
|
||||
serialized inventory
|
||||
u32 len
|
||||
u8[len] text
|
||||
u16 len
|
||||
u8[len] owner
|
||||
u16 len
|
||||
u8[len] infotext
|
||||
u16 len
|
||||
u8[len] inventory drawspec
|
||||
u8 allow_text_input (bool)
|
||||
u8 removal_disabled (bool)
|
||||
u8 enforce_owner (bool)
|
||||
u32 num_vars
|
||||
foreach num_vars
|
||||
u16 len
|
||||
u8[len] name
|
||||
u32 len
|
||||
u8[len] value
|
||||
|
||||
14: Sign metadata
|
||||
u16 text_len
|
||||
u8[text_len] text
|
||||
|
||||
15: Chest metadata
|
||||
serialized inventory
|
||||
|
||||
16: Furnace metadata
|
||||
TBD
|
||||
|
||||
17: Locked Chest metadata
|
||||
u16 len
|
||||
u8[len] owner
|
||||
serialized inventory
|
||||
|
||||
Static objects
|
||||
---------------
|
||||
Static objects are persistent freely moving objects in the world.
|
||||
|
||||
Object types:
|
||||
1: Test object
|
||||
2: Item
|
||||
3: Rat (deprecated)
|
||||
4: Oerkki (deprecated)
|
||||
5: Firefly (deprecated)
|
||||
6: MobV2 (deprecated)
|
||||
7: LuaEntity
|
||||
|
||||
1: Item:
|
||||
u8 version
|
||||
version 0:
|
||||
u16 len
|
||||
u8[len] itemstring
|
||||
|
||||
7: LuaEntity:
|
||||
u8 version
|
||||
version 1:
|
||||
u16 len
|
||||
u8[len] entity name
|
||||
u32 len
|
||||
u8[len] static data
|
||||
s16 hp
|
||||
s32 velocity.x * 10000
|
||||
s32 velocity.y * 10000
|
||||
s32 velocity.z * 10000
|
||||
s32 yaw * 1000
|
||||
|
||||
Itemstring format
|
||||
------------------
|
||||
eg. 'default:dirt 5'
|
||||
eg. 'default:pick_wood 21323'
|
||||
eg. '"default:apple" 2'
|
||||
eg. 'default:apple'
|
||||
- The wear value in tools is 0...65535
|
||||
- There are also a number of older formats that you might stumble upon:
|
||||
eg. 'node "default:dirt" 5'
|
||||
eg. 'NodeItem default:dirt 5'
|
||||
eg. 'ToolItem WPick 21323'
|
||||
|
||||
Inventory serialization format
|
||||
-------------------------------
|
||||
- The inventory serialization format is line-based
|
||||
- The newline character used is "\n"
|
||||
- The end condition of a serialized inventory is always "EndInventory\n"
|
||||
- All the slots in a list must always be serialized.
|
||||
|
||||
Example (format does not include "---"):
|
||||
---
|
||||
List foo 4
|
||||
Item default:sapling
|
||||
Item default:sword_stone 1 10647
|
||||
Item default:dirt 99
|
||||
Empty
|
||||
EndInventoryList
|
||||
List bar 9
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
Empty
|
||||
EndInventoryList
|
||||
EndInventory
|
||||
---
|
||||
|
||||
==============================================
|
||||
Minetest World Format used as of 2011-05 or so
|
||||
==============================================
|
||||
|
||||
Map data serialization format version 17.
|
||||
|
||||
0.3.1 does not use this format, but a more recent one. This exists here for
|
||||
historical reasons.
|
||||
|
||||
Directory structure:
|
||||
sectors/XXXXZZZZ or sectors2/XXX/ZZZ
|
||||
XXXX, ZZZZ, XXX and ZZZ being the hexadecimal X and Z coordinates.
|
||||
Under these, the block files are stored, called YYYY.
|
||||
|
||||
There also exists files map_meta.txt and chunk_meta, that are used by the
|
||||
generator. If they are not found or invalid, the generator will currently
|
||||
behave quite strangely.
|
||||
|
||||
The MapBlock file format (sectors2/XXX/ZZZ/YYYY):
|
||||
-------------------------------------------------
|
||||
|
||||
NOTE: Byte order is MSB first.
|
||||
|
||||
u8 version
|
||||
- map format version number, this one is version 17
|
||||
|
||||
u8 flags
|
||||
- Flag bitmasks:
|
||||
- 0x01: is_underground: Should be set to 0 if there will be no light
|
||||
obstructions above the block. If/when sunlight of a block is updated and
|
||||
there is no block above it, this value is checked for determining whether
|
||||
sunlight comes from the top.
|
||||
- 0x02: day_night_differs: Whether the lighting of the block is different on
|
||||
day and night. Only blocks that have this bit set are updated when day
|
||||
transforms to night.
|
||||
- 0x04: lighting_expired: If true, lighting is invalid and should be updated.
|
||||
If you can't calculate lighting in your generator properly, you could try
|
||||
setting this 1 to everything and setting the uppermost block in every
|
||||
sector as is_underground=0. I am quite sure it doesn't work properly,
|
||||
though.
|
||||
|
||||
zlib-compressed map data:
|
||||
- content:
|
||||
u8[4096]: content types
|
||||
u8[4096]: param1 values
|
||||
u8[4096]: param2 values
|
||||
|
||||
zlib-compressed node metadata
|
||||
- content:
|
||||
u16 version (=1)
|
||||
u16 count of metadata
|
||||
foreach count:
|
||||
u16 position (= p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X)
|
||||
u16 type_id
|
||||
u16 content_size
|
||||
u8[content_size] misc. stuff contained in the metadata
|
||||
|
||||
u16 mapblockobject_count
|
||||
- always write as 0.
|
||||
- if read != 0, just fail.
|
||||
|
||||
foreach mapblockobject_count:
|
||||
- deprecated, should not be used. Length of this data can only be known by
|
||||
properly parsing it. Just hope not to run into any of this.
|
||||
|
||||
u8 static object version:
|
||||
- currently 0
|
||||
|
||||
u16 static_object_count
|
||||
|
||||
foreach static_object_count:
|
||||
u8 type (object type-id)
|
||||
s32 pos_x * 1000
|
||||
s32 pos_y * 1000
|
||||
s32 pos_z * 1000
|
||||
u16 data_size
|
||||
u8[data_size] data
|
||||
|
||||
u32 timestamp
|
||||
- Timestamp when last saved, as seconds from starting the game.
|
||||
- 0xffffffff = invalid/unknown timestamp, nothing will be done with the time
|
||||
difference when loaded (recommended)
|
||||
|
||||
Node metadata format:
|
||||
---------------------
|
||||
|
||||
Sign metadata:
|
||||
u16 string_len
|
||||
u8[string_len] string
|
||||
|
||||
Furnace metadata:
|
||||
TBD
|
||||
|
||||
Chest metadata:
|
||||
TBD
|
||||
|
||||
Locking Chest metadata:
|
||||
u16 string_len
|
||||
u8[string_len] string
|
||||
TBD
|
||||
|
||||
// END
|
||||
|
239
assets/Minetest/doc/menu_lua_api.txt
Normal file
@ -0,0 +1,239 @@
|
||||
Minetest Lua Mainmenu API Reference 0.4.12
|
||||
========================================
|
||||
|
||||
Introduction
|
||||
-------------
|
||||
The main menu is defined as a formspec by Lua in builtin/mainmenu/
|
||||
Description of formspec language to show your menu is in lua_api.txt
|
||||
|
||||
Callbacks
|
||||
---------
|
||||
core.buttonhandler(fields): called when a button is pressed.
|
||||
^ fields = {name1 = value1, name2 = value2, ...}
|
||||
core.event_handler(event)
|
||||
^ event: "MenuQuit", "KeyEnter", "ExitButton" or "EditBoxEnter"
|
||||
|
||||
Gamedata
|
||||
--------
|
||||
The "gamedata" table is read when calling core.start(). It should contain:
|
||||
{
|
||||
playername = <name>,
|
||||
password = <password>,
|
||||
address = <IP/adress>,
|
||||
port = <port>,
|
||||
selected_world = <index>, -- 0 for client mode
|
||||
singleplayer = <true/false>,
|
||||
}
|
||||
|
||||
Functions
|
||||
---------
|
||||
core.start()
|
||||
core.close()
|
||||
|
||||
Filesystem:
|
||||
core.get_scriptdir()
|
||||
^ returns directory of script
|
||||
core.get_modpath() (possible in async calls)
|
||||
^ returns path to global modpath
|
||||
core.get_modstore_details(modid) (possible in async calls)
|
||||
^ modid numeric id of mod in modstore
|
||||
^ returns {
|
||||
id = <numeric id of mod in modstore>,
|
||||
title = <human readable title>,
|
||||
basename = <basename for mod>,
|
||||
description = <description of mod>,
|
||||
author = <author of mod>,
|
||||
download_url= <best match download url>,
|
||||
license = <short description of license>,
|
||||
rating = <float value of current rating>
|
||||
}
|
||||
core.get_modstore_list() (possible in async calls)
|
||||
^ returns {
|
||||
[1] = {
|
||||
id = <numeric id of mod in modstore>,
|
||||
title = <human readable title>,
|
||||
basename = <basename for mod>
|
||||
}
|
||||
}
|
||||
core.get_gamepath() (possible in async calls)
|
||||
^ returns path to global gamepath
|
||||
core.get_texturepath() (possible in async calls)
|
||||
^ returns path to default textures
|
||||
core.get_dirlist(path,onlydirs) (possible in async calls)
|
||||
^ path to get subdirs from
|
||||
^ onlydirs should result contain only dirs?
|
||||
^ returns list of folders within path
|
||||
core.create_dir(absolute_path) (possible in async calls)
|
||||
^ absolute_path to directory to create (needs to be absolute)
|
||||
^ returns true/false
|
||||
core.delete_dir(absolute_path) (possible in async calls)
|
||||
^ absolute_path to directory to delete (needs to be absolute)
|
||||
^ returns true/false
|
||||
core.copy_dir(source,destination,keep_soure) (possible in async calls)
|
||||
^ source folder
|
||||
^ destination folder
|
||||
^ keep_source DEFAULT true --> if set to false source is deleted after copying
|
||||
^ returns true/false
|
||||
core.extract_zip(zipfile,destination) [unzip within path required]
|
||||
^ zipfile to extract
|
||||
^ destination folder to extract to
|
||||
^ returns true/false
|
||||
core.download_file(url,target) (possible in async calls)
|
||||
^ url to download
|
||||
^ target to store to
|
||||
^ returns true/false
|
||||
core.get_version() (possible in async calls)
|
||||
^ returns current core version
|
||||
core.sound_play(spec, looped) -> handle
|
||||
^ spec = SimpleSoundSpec (see lua-api.txt)
|
||||
^ looped = bool
|
||||
core.sound_stop(handle)
|
||||
core.get_video_drivers()
|
||||
^ get list of video drivers supported by engine (not all modes are guaranteed to work)
|
||||
^ returns list of available video drivers' settings name and 'friendly' display name
|
||||
^ e.g. { {name="opengl", friendly_name="OpenGL"}, {name="software", friendly_name="Software Renderer"} }
|
||||
^ first element of returned list is guaranteed to be the NULL driver
|
||||
|
||||
Formspec:
|
||||
core.update_formspec(formspec)
|
||||
core.get_table_index(tablename) -> index
|
||||
^ can also handle textlists
|
||||
core.formspec_escape(string) -> string
|
||||
^ escapes characters [ ] \ , ; that can not be used in formspecs
|
||||
core.explode_table_event(string) -> table
|
||||
^ returns e.g. {type="CHG", row=1, column=2}
|
||||
^ type: "INV" (no row selected), "CHG" (selected) or "DCL" (double-click)
|
||||
core.explode_textlist_event(string) -> table
|
||||
^ returns e.g. {type="CHG", index=1}
|
||||
^ type: "INV" (no row selected), "CHG" (selected) or "DCL" (double-click)
|
||||
|
||||
GUI:
|
||||
core.set_background(type, texturepath,[tile],[minsize])
|
||||
^ type: "background", "overlay", "header" or "footer"
|
||||
^ tile: tile the image instead of scaling (background only)
|
||||
^ minsize: minimum tile size, images are scaled to at least this size prior
|
||||
^ doing tiling (background only)
|
||||
core.set_clouds(<true/false>)
|
||||
core.set_topleft_text(text)
|
||||
core.show_keys_menu()
|
||||
core.file_open_dialog(formname,caption)
|
||||
^ shows a file open dialog
|
||||
^ formname is base name of dialog response returned in fields
|
||||
^ -if dialog was accepted "_accepted"
|
||||
^^ will be added to fieldname containing the path
|
||||
^ -if dialog was canceled "_cancelled"
|
||||
^ will be added to fieldname value is set to formname itself
|
||||
^ returns nil or selected file/folder
|
||||
core.get_screen_info()
|
||||
^ returns {
|
||||
density = <screen density 0.75,1.0,2.0,3.0 ... (dpi)>,
|
||||
display_width = <width of display>,
|
||||
display_height = <height of display>,
|
||||
window_width = <current window width>,
|
||||
window_height = <current window height>
|
||||
}
|
||||
|
||||
Games:
|
||||
core.get_game(index)
|
||||
^ returns {
|
||||
id = <id>,
|
||||
path = <full path to game>,
|
||||
gamemods_path = <path>,
|
||||
name = <name of game>,
|
||||
menuicon_path = <full path to menuicon>,
|
||||
DEPRECATED:
|
||||
addon_mods_paths = {[1] = <path>,},
|
||||
}
|
||||
core.get_games() -> table of all games in upper format (possible in async calls)
|
||||
core.get_mapgen_names() -> table of all map generator algorithms registered in
|
||||
the core (possible in async calls)
|
||||
|
||||
Favorites:
|
||||
core.get_favorites(location) -> list of favorites (possible in async calls)
|
||||
^ location: "local" or "online"
|
||||
^ returns {
|
||||
[1] = {
|
||||
clients = <number of clients/nil>,
|
||||
clients_max = <maximum number of clients/nil>,
|
||||
version = <server version/nil>,
|
||||
password = <true/nil>,
|
||||
creative = <true/nil>,
|
||||
damage = <true/nil>,
|
||||
pvp = <true/nil>,
|
||||
description = <server description/nil>,
|
||||
name = <server name/nil>,
|
||||
address = <address of server/nil>,
|
||||
port = <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 = <full path to world>,
|
||||
name = <name of world>,
|
||||
gameid = <gameid of world>,
|
||||
},
|
||||
}
|
||||
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
|
111
assets/Minetest/doc/minetest.6
Normal file
@ -0,0 +1,111 @@
|
||||
.TH minetest 6 "10 September 2013" "" ""
|
||||
|
||||
.SH NAME
|
||||
minetest, minetestserver \- Multiplayer infinite-world block sandbox
|
||||
|
||||
.SH SYNOPSIS
|
||||
.B minetest
|
||||
[\fB--server SERVER OPTIONS\fR | \fBCLIENT OPTIONS\fR]
|
||||
[\fBCOMMON OPTIONS\fR]
|
||||
[\fBWORLD PATH\fR]
|
||||
|
||||
.B minetestserver
|
||||
[\fBSERVER OPTIONS\fR]
|
||||
[\fBCOMMON OPTIONS\fR]
|
||||
[\fBWORLD PATH\fR]
|
||||
|
||||
.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 COMMON OPTIONS
|
||||
.TP
|
||||
.B \-\-help
|
||||
Print allowed options and exit
|
||||
.TP
|
||||
.B \-\-version
|
||||
Print version information and exit
|
||||
.TP
|
||||
.B \-\-config <value>
|
||||
Load configuration from specified file
|
||||
.TP
|
||||
.B \-\-logfile <value>
|
||||
Set logfile path (debug.txt)
|
||||
.TP
|
||||
.B \-\-info
|
||||
Print more information to console
|
||||
.TP
|
||||
.B \-\-verbose
|
||||
Print even more information to console
|
||||
.TP
|
||||
.B \-\-trace
|
||||
Print enormous amounts of information to console
|
||||
.TP
|
||||
.B \-\-gameid <value>
|
||||
Set gameid
|
||||
.TP
|
||||
.B \-\-worldname <value>
|
||||
Set world path by name
|
||||
.TP
|
||||
.B \-\-world <value> | list
|
||||
Set world path or list worlds
|
||||
.TP
|
||||
.B \-\-map\-dir <value>
|
||||
Same as \-\-world (deprecated)
|
||||
.TP
|
||||
.B \-\-port <value>
|
||||
Set network port (UDP) to use
|
||||
.TP
|
||||
.B \-\-run\-unittests
|
||||
Run unit tests and exit
|
||||
|
||||
.SH CLIENT OPTIONS
|
||||
.TP
|
||||
.B \-\-address <value>
|
||||
Address to connect to
|
||||
.TP
|
||||
.B \-\-go
|
||||
Disable main menu
|
||||
.TP
|
||||
.B \-\-name <value>
|
||||
Set player name
|
||||
.TP
|
||||
.B \-\-password <value>
|
||||
Set password
|
||||
.TP
|
||||
.B \-\-random\-input
|
||||
Enable random user input, for testing (client only)
|
||||
.TP
|
||||
.B \-\-videomodes
|
||||
List available video modes (client only)
|
||||
.TP
|
||||
.B \-\-speedtests
|
||||
Run speed tests
|
||||
|
||||
.SH SERVER OPTIONS
|
||||
.TP
|
||||
.B \-\-migrate <value>
|
||||
Migrate from current map backend to another. Possible values are sqlite3,
|
||||
leveldb, redis, and dummy.
|
||||
|
||||
.SH ENVIRONMENT
|
||||
.TP
|
||||
.B MINETEST_SUBGAME_PATH
|
||||
Colon delimited list of directories to search for subgames.
|
||||
|
||||
.SH BUGS
|
||||
Please report all bugs to Perttu Ahola <celeron55@gmail.com>.
|
||||
|
||||
.SH AUTHOR
|
||||
.PP
|
||||
Perttu Ahola <celeron55@gmail.com>
|
||||
and contributors.
|
||||
.PP
|
||||
This man page was originally written by
|
||||
Juhani Numminen <juhaninumminen0@gmail.com>.
|
||||
|
||||
.SH WWW
|
||||
http://www.minetest.net/
|
||||
|
2
assets/Minetest/doc/minetestserver.6
Normal file
@ -0,0 +1,2 @@
|
||||
.so man6/minetest.6
|
||||
|
345
assets/Minetest/doc/old/ancient_main_comment.txt
Normal file
@ -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:
|
||||
----------------
|
||||
|
||||
======================================================================
|
||||
|
||||
*/
|
147
assets/Minetest/doc/old/changelog.txt
Normal file
@ -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
|
||||
|
108
assets/Minetest/doc/protocol.txt
Normal file
@ -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 <address>" % 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])
|
BIN
assets/Minetest/fonts/DroidSansFallbackFull.ttf
Normal file
BIN
assets/Minetest/fonts/liberationmono.ttf
Normal file
BIN
assets/Minetest/fonts/liberationsans.ttf
Normal file
BIN
assets/Minetest/fonts/lucida_sans_10.xml
Normal file
BIN
assets/Minetest/fonts/lucida_sans_100.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
assets/Minetest/fonts/lucida_sans_11.xml
Normal file
BIN
assets/Minetest/fonts/lucida_sans_110.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
assets/Minetest/fonts/lucida_sans_12.xml
Normal file
BIN
assets/Minetest/fonts/lucida_sans_120.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
assets/Minetest/fonts/lucida_sans_14.xml
Normal file
BIN
assets/Minetest/fonts/lucida_sans_140.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
assets/Minetest/fonts/lucida_sans_16.xml
Normal file
BIN
assets/Minetest/fonts/lucida_sans_160.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
assets/Minetest/fonts/lucida_sans_18.xml
Normal file
BIN
assets/Minetest/fonts/lucida_sans_180.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
assets/Minetest/fonts/lucida_sans_20.xml
Normal file
BIN
assets/Minetest/fonts/lucida_sans_200.png
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
assets/Minetest/fonts/lucida_sans_22.xml
Normal file
BIN
assets/Minetest/fonts/lucida_sans_220.png
Normal file
After Width: | Height: | Size: 81 KiB |
BIN
assets/Minetest/fonts/lucida_sans_24.xml
Normal file
BIN
assets/Minetest/fonts/lucida_sans_240.png
Normal file
After Width: | Height: | Size: 93 KiB |
BIN
assets/Minetest/fonts/lucida_sans_26.xml
Normal file
BIN
assets/Minetest/fonts/lucida_sans_260.png
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
assets/Minetest/fonts/lucida_sans_28.xml
Normal file
BIN
assets/Minetest/fonts/lucida_sans_280.png
Normal file
After Width: | Height: | Size: 112 KiB |
BIN
assets/Minetest/fonts/lucida_sans_36.xml
Normal file
BIN
assets/Minetest/fonts/lucida_sans_360.png
Normal file
After Width: | Height: | Size: 158 KiB |
BIN
assets/Minetest/fonts/lucida_sans_4.xml
Normal file
BIN
assets/Minetest/fonts/lucida_sans_40.png
Normal file
After Width: | Height: | Size: 8.3 KiB |