aboutsummaryrefslogtreecommitdiff
path: root/lib/jsoncpp/CMakeLists.txt
blob: 9056e4b6d044ab106fd72abed8787909551bf371 (plain)
1
2
3
4
5
6
7
if(MSVC)
	set(CMAKE_CXX_FLAGS_RELEASE "/MT /O2 /Ob2 /D NDEBUG")
endif()

add_library(jsoncpp jsoncpp.cpp)
target_link_libraries(jsoncpp)

n>
Diffstat
-rw-r--r--.dockerignore4
-rwxr-xr-x.editorconfig9
-rw-r--r--.github/CONTRIBUTING.md110
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md1
-rw-r--r--.github/workflows/android.yml10
-rw-r--r--.github/workflows/build.yml83
-rw-r--r--.github/workflows/cpp_lint.yml27
-rw-r--r--.github/workflows/macos.yml66
-rw-r--r--.gitignore14
-rw-r--r--.gitlab-ci.yml130
-rw-r--r--.luacheckrc2
-rw-r--r--AppImageBuilder.yml21
-rw-r--r--CMakeLists.txt103
-rw-r--r--Dockerfile38
-rw-r--r--LICENSE.txt13
-rw-r--r--README.md151
-rw-r--r--android/app/build.gradle21
-rw-r--r--android/app/src/main/AndroidManifest.xml8
-rw-r--r--android/app/src/main/java/net/minetest/minetest/GameActivity.java33
-rw-r--r--android/app/src/main/java/net/minetest/minetest/MainActivity.java3
-rw-r--r--android/app/src/main/java/net/minetest/minetest/UnzipService.java5
-rw-r--r--android/build.gradle9
-rw-r--r--android/gradle/wrapper/gradle-wrapper.properties3
-rwxr-xr-xandroid/gradlew2
-rw-r--r--android/icons/aux_btn.svg411
-rw-r--r--android/native/build.gradle51
-rw-r--r--android/native/jni/Android.mk123
-rw-r--r--android/native/jni/Application.mk16
-rw-r--r--builtin/async/init.lua10
-rw-r--r--builtin/client/chatcommands.lua9
-rw-r--r--builtin/client/death_formspec.lua1
-rw-r--r--builtin/client/init.lua1
-rw-r--r--builtin/common/after.lua9
-rw-r--r--builtin/common/chatcommands.lua142
-rw-r--r--builtin/common/information_formspecs.lua60
-rw-r--r--builtin/common/misc_helpers.lua46
-rw-r--r--builtin/common/tests/misc_helpers_spec.lua5
-rw-r--r--builtin/common/tests/serialize_spec.lua13
-rw-r--r--builtin/common/tests/vector_spec.lua303
-rw-r--r--builtin/common/vector.lua250
-rw-r--r--builtin/fstk/tabview.lua50
-rw-r--r--builtin/fstk/ui.lua2
-rw-r--r--builtin/game/auth.lua7
-rw-r--r--builtin/game/chat.lua853
-rw-r--r--builtin/game/falling.lua93
-rw-r--r--builtin/game/features.lua3
-rw-r--r--builtin/game/forceloading.lua9
-rw-r--r--builtin/game/init.lua2
-rw-r--r--builtin/game/item.lua164
-rw-r--r--builtin/game/misc.lua86
-rw-r--r--builtin/game/privileges.lua47
-rw-r--r--builtin/game/register.lua12
-rw-r--r--builtin/game/voxelarea.lua14
-rw-r--r--builtin/init.lua1
-rw-r--r--builtin/locale/__builtin.de.tr245
-rw-r--r--builtin/locale/__builtin.it.tr258
-rw-r--r--builtin/locale/template.txt245
-rw-r--r--builtin/mainmenu/common.lua134
-rw-r--r--builtin/mainmenu/dlg_contentstore.lua135
-rw-r--r--builtin/mainmenu/dlg_create_world.lua266
-rw-r--r--builtin/mainmenu/dlg_settings_advanced.lua6
-rw-r--r--builtin/mainmenu/game_theme.lua (renamed from builtin/mainmenu/textures.lua)98
-rw-r--r--builtin/mainmenu/init.lua12
-rw-r--r--builtin/mainmenu/pkgmgr.lua88
-rw-r--r--builtin/mainmenu/tab_about.lua (renamed from builtin/mainmenu/tab_credits.lua)61
-rw-r--r--builtin/mainmenu/tab_content.lua15
-rw-r--r--builtin/mainmenu/tab_local.lua180
-rw-r--r--builtin/mainmenu/tab_online.lua456
-rw-r--r--builtin/mainmenu/tab_settings.lua64
-rw-r--r--builtin/mainmenu/tests/serverlistmgr_spec.lua1
-rw-r--r--builtin/profiler/init.lua18
-rw-r--r--builtin/profiler/instrumentation.lua5
-rw-r--r--builtin/profiler/reporter.lua19
-rw-r--r--builtin/settingtypes.txt170
-rw-r--r--client/shaders/3d_interlaced_merge/opengl_fragment.glsl2
-rw-r--r--client/shaders/3d_interlaced_merge/opengl_vertex.glsl2
-rw-r--r--client/shaders/default_shader/opengl_vertex.glsl4
-rw-r--r--client/shaders/minimap_shader/opengl_vertex.glsl4
-rw-r--r--client/shaders/nodes_shader/opengl_fragment.glsl463
-rw-r--r--client/shaders/nodes_shader/opengl_vertex.glsl67
-rw-r--r--client/shaders/object_shader/opengl_fragment.glsl310
-rw-r--r--client/shaders/object_shader/opengl_vertex.glsl50
-rw-r--r--client/shaders/selection_shader/opengl_vertex.glsl4
-rw-r--r--client/shaders/shadow_shaders/pass1_fragment.glsl13
-rw-r--r--client/shaders/shadow_shaders/pass1_trans_fragment.glsl42
-rw-r--r--client/shaders/shadow_shaders/pass1_trans_vertex.glsl33
-rw-r--r--client/shaders/shadow_shaders/pass1_vertex.glsl26
-rw-r--r--client/shaders/shadow_shaders/pass2_fragment.glsl23
-rw-r--r--client/shaders/shadow_shaders/pass2_vertex.glsl9
-rw-r--r--clientmods/preview/mod.conf1
-rw-r--r--cmake/Modules/FindGMP.cmake2
-rw-r--r--cmake/Modules/FindGettextLib.cmake9
-rw-r--r--cmake/Modules/FindIrrlicht.cmake77
-rw-r--r--cmake/Modules/FindJson.cmake23
-rw-r--r--cmake/Modules/FindLuaJIT.cmake17
-rw-r--r--cmake/Modules/FindOpenGLES2.cmake3
-rw-r--r--cmake/Modules/FindZstd.cmake25
-rw-r--r--cmake/Modules/MinetestFindIrrlichtHeaders.cmake26
-rw-r--r--doc/Doxyfile.in1
-rw-r--r--doc/client_lua_api.txt23
-rw-r--r--doc/direction.md69
-rw-r--r--doc/lua_api.txt776
-rw-r--r--doc/menu_lua_api.txt17
-rw-r--r--doc/minetest.67
-rw-r--r--doc/texture_packs.txt3
-rw-r--r--doc/world_format.txt91
-rw-r--r--fonts/mono_dejavu_sans_10.xmlbin257014 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_100.pngbin56121 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_11.xmlbin263644 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_110.pngbin67613 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_12.xmlbin268932 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_120.pngbin73938 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_14.xmlbin269188 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_140.pngbin89073 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_16.xmlbin275642 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_160.pngbin101939 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_18.xmlbin279962 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_180.pngbin122274 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_20.xmlbin282588 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_200.pngbin138662 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_22.xmlbin283950 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_220.pngbin152844 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_24.xmlbin286626 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_240.pngbin170247 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_26.xmlbin289710 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_260.pngbin190156 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_28.xmlbin292596 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_280.pngbin200848 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_4.xmlbin237740 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_40.pngbin15668 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_6.xmlbin245472 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_60.pngbin29291 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_8.xmlbin251876 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_80.pngbin45552 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_9.xmlbin254016 -> 0 bytes
-rw-r--r--fonts/mono_dejavu_sans_90.pngbin50995 -> 0 bytes
-rw-r--r--games/devtest/menu/background.pngbin152 -> 160 bytes
-rw-r--r--games/devtest/mods/basetools/init.lua44
-rw-r--r--games/devtest/mods/basetools/textures/basetools_dirtpick.pngbin307 -> 0 bytes
-rw-r--r--games/devtest/mods/broken/init.lua11
-rw-r--r--games/devtest/mods/broken/mod.conf2
-rw-r--r--games/devtest/mods/experimental/commands.lua10
-rw-r--r--games/devtest/mods/testformspec/formspec.lua100
-rw-r--r--games/devtest/mods/testhud/init.lua81
-rw-r--r--games/devtest/mods/testhud/mod.conf2
-rw-r--r--games/devtest/mods/testnodes/drawtypes.lua130
-rw-r--r--games/devtest/mods/testnodes/liquids.lua55
-rw-r--r--games/devtest/mods/testnodes/properties.lua108
-rw-r--r--games/devtest/mods/testnodes/settingtypes.txt4
-rw-r--r--games/devtest/mods/testnodes/textures.lua106
-rw-r--r--games/devtest/mods/testnodes/textures/testnodes_climbable_resistance_side.pngbin0 -> 295 bytes
-rw-r--r--games/devtest/mods/testnodes/textures/testnodes_move_resistance.pngbin0 -> 221 bytes
-rw-r--r--games/devtest/mods/testnodes/textures/testnodes_plantlike_rooted_base_side_wallmounted.pngbin0 -> 224 bytes
-rw-r--r--games/devtest/mods/testnodes/textures/testnodes_plantlike_rooted_wallmounted.pngbin0 -> 268 bytes
-rw-r--r--games/devtest/mods/testnodes/textures/testnodes_plantlike_wallmounted.pngbin0 -> 268 bytes
-rw-r--r--games/devtest/mods/testtools/README.md21
-rw-r--r--games/devtest/mods/testtools/init.lua196
-rw-r--r--games/devtest/mods/testtools/light.lua31
-rw-r--r--games/devtest/mods/testtools/textures/testtools_children_getter.pngbin0 -> 281 bytes
-rw-r--r--games/devtest/mods/testtools/textures/testtools_node_meta_editor.pngbin0 -> 135 bytes
-rw-r--r--games/devtest/mods/unittests/crafting.lua18
-rw-r--r--games/devtest/mods/unittests/init.lua199
-rw-r--r--games/devtest/mods/unittests/itemdescription.lua3
-rw-r--r--games/devtest/mods/unittests/misc.lua38
-rw-r--r--games/devtest/mods/unittests/player.lua46
-rw-r--r--games/devtest/mods/unittests/random.lua10
-rw-r--r--games/devtest/mods/util_commands/init.lua53
-rw-r--r--games/devtest/settingtypes.txt5
-rw-r--r--lib/bitop/CMakeLists.txt4
-rw-r--r--lib/bitop/bit.c189
-rw-r--r--lib/bitop/bit.h34
-rw-r--r--minetest.conf.example177
-rw-r--r--misc/Info.plist6
-rw-r--r--misc/debpkg-control11
-rw-r--r--misc/net.minetest.minetest.appdata.xml2
-rw-r--r--po/ar/minetest.po1097
-rw-r--r--po/be/minetest.po854
-rw-r--r--po/bg/minetest.po1450
-rw-r--r--po/ca/minetest.po784
-rw-r--r--po/cs/minetest.po1799
-rw-r--r--po/da/minetest.po834
-rw-r--r--po/de/minetest.po1009
-rw-r--r--po/dv/minetest.po717
-rw-r--r--po/el/minetest.po801
-rw-r--r--po/eo/minetest.po1122
-rw-r--r--po/es/minetest.po999
-rw-r--r--po/et/minetest.po868
-rw-r--r--po/eu/minetest.po729
-rw-r--r--po/fi/minetest.po1027
-rw-r--r--po/fil/minetest.po6638
-rw-r--r--po/fr/minetest.po2706
-rw-r--r--po/gd/minetest.po734
-rw-r--r--po/gl/minetest.po687
-rw-r--r--po/he/minetest.po844
-rw-r--r--po/hi/minetest.po754
-rw-r--r--po/hu/minetest.po1987
-rw-r--r--po/id/minetest.po950
-rw-r--r--po/it/minetest.po958
-rw-r--r--po/ja/minetest.po1135
-rw-r--r--po/jbo/minetest.po733
-rw-r--r--po/kk/minetest.po694
-rw-r--r--po/kn/minetest.po692
-rw-r--r--po/ko/minetest.po819
-rw-r--r--po/ky/minetest.po747
-rw-r--r--po/lt/minetest.po775
-rw-r--r--po/lv/minetest.po753
-rw-r--r--po/lzh/minetest.po6641
-rw-r--r--po/minetest.pot683
-rw-r--r--po/mr/minetest.po6649
-rw-r--r--po/ms/minetest.po1079
-rw-r--r--po/ms_Arab/minetest.po844
-rw-r--r--po/nb/minetest.po884
-rw-r--r--po/nl/minetest.po1052
-rw-r--r--po/nn/minetest.po761
-rw-r--r--po/pl/minetest.po1381
-rw-r--r--po/pt/minetest.po1318
-rw-r--r--po/pt_BR/minetest.po1045
-rw-r--r--po/ro/minetest.po885
-rw-r--r--po/ru/minetest.po1218
-rw-r--r--po/sk/minetest.po966
-rw-r--r--po/sl/minetest.po767
-rw-r--r--po/sr_Cyrl/minetest.po975
-rw-r--r--po/sr_Latn/minetest.po692
-rw-r--r--po/sv/minetest.po1818
-rw-r--r--po/sw/minetest.po848
-rw-r--r--po/th/minetest.po821
-rw-r--r--po/tr/minetest.po998
-rw-r--r--po/tt/minetest.po6645
-rw-r--r--po/uk/minetest.po1510
-rw-r--r--po/vi/minetest.po1254
-rw-r--r--po/yue/minetest.po6638
-rw-r--r--po/zh_CN/minetest.po969
-rw-r--r--po/zh_TW/minetest.po1137
-rw-r--r--src/CMakeLists.txt234
-rw-r--r--src/chat.cpp151
-rw-r--r--src/chat.h22
-rw-r--r--src/client/CMakeLists.txt4
-rw-r--r--src/client/camera.cpp90
-rw-r--r--src/client/camera.h12
-rw-r--r--src/client/client.cpp243
-rw-r--r--src/client/client.h30
-rw-r--r--src/client/clientenvironment.cpp65
-rw-r--r--src/client/clientevent.h55
-rw-r--r--src/client/clientlauncher.cpp92
-rw-r--r--src/client/clientlauncher.h2
-rw-r--r--src/client/clientmap.cpp383
-rw-r--r--src/client/clientmap.h42
-rw-r--r--src/client/clientmedia.cpp260
-rw-r--r--src/client/clientmedia.h166
-rw-r--r--src/client/clientobject.h2
-rw-r--r--src/client/clouds.cpp4
-rw-r--r--src/client/clouds.h5
-rw-r--r--src/client/content_cao.cpp147
-rw-r--r--src/client/content_cao.h4
-rw-r--r--src/client/content_mapblock.cpp80
-rw-r--r--src/client/content_mapblock.h7
-rw-r--r--src/client/fontengine.cpp284
-rw-r--r--src/client/fontengine.h22
-rw-r--r--src/client/game.cpp891
-rw-r--r--src/client/game.h6
-rw-r--r--src/client/gameui.cpp68
-rw-r--r--src/client/gameui.h9
-rw-r--r--src/client/guiscalingfilter.cpp7
-rw-r--r--src/client/hud.cpp173
-rw-r--r--src/client/hud.h26
-rw-r--r--src/client/imagefilters.cpp113
-rw-r--r--src/client/imagefilters.h6
-rw-r--r--src/client/inputhandler.cpp47
-rw-r--r--src/client/inputhandler.h95
-rw-r--r--src/client/joystick_controller.cpp80
-rw-r--r--src/client/joystick_controller.h5
-rw-r--r--src/client/keycode.cpp2
-rw-r--r--src/client/keys.h3
-rw-r--r--src/client/localplayer.cpp50
-rw-r--r--src/client/localplayer.h6
-rw-r--r--src/client/mapblock_mesh.cpp24
-rw-r--r--src/client/mesh.cpp597
-rw-r--r--src/client/mesh.h7
-rw-r--r--src/client/mesh_generator_thread.cpp53
-rw-r--r--src/client/mesh_generator_thread.h5
-rw-r--r--src/client/minimap.cpp5
-rw-r--r--src/client/particles.cpp4
-rw-r--r--src/client/render/anaglyph.cpp3
-rw-r--r--src/client/render/core.cpp19
-rw-r--r--src/client/render/core.h5
-rw-r--r--src/client/render/interlaced.cpp4
-rw-r--r--src/client/renderingengine.cpp168
-rw-r--r--src/client/renderingengine.h100
-rw-r--r--src/client/shader.cpp84
-rw-r--r--src/client/shader.h5
-rw-r--r--src/client/shadows/dynamicshadows.cpp165
-rw-r--r--src/client/shadows/dynamicshadows.h109
-rw-r--r--src/client/shadows/dynamicshadowsrender.cpp630
-rw-r--r--src/client/shadows/dynamicshadowsrender.h147
-rw-r--r--src/client/shadows/shadowsScreenQuad.cpp61
-rw-r--r--src/client/shadows/shadowsScreenQuad.h54
-rw-r--r--src/client/shadows/shadowsshadercallbacks.cpp (renamed from src/unittest/test_player.cpp)29
-rw-r--r--src/client/shadows/shadowsshadercallbacks.h48
-rw-r--r--src/client/sky.cpp224
-rw-r--r--src/client/sky.h18
-rw-r--r--src/client/sound_openal.cpp12
-rw-r--r--src/client/tile.cpp231
-rw-r--r--src/client/tile.h3
-rw-r--r--src/client/wieldmesh.cpp79
-rw-r--r--src/client/wieldmesh.h3
-rw-r--r--src/clientiface.cpp25
-rw-r--r--src/clientiface.h2
-rw-r--r--src/cmake_config.h.in1
-rw-r--r--src/constants.h3
-rw-r--r--src/content/mods.cpp146
-rw-r--r--src/content/mods.h15
-rw-r--r--src/content/subgames.cpp32
-rw-r--r--src/content/subgames.h2
-rw-r--r--src/convert_json.cpp54
-rw-r--r--src/convert_json.h4
-rw-r--r--src/database/database-dummy.cpp38
-rw-r--r--src/database/database-dummy.h9
-rw-r--r--src/database/database-files.cpp125
-rw-r--r--src/database/database-files.h26
-rw-r--r--src/database/database-leveldb.cpp16
-rw-r--r--src/database/database-postgresql.cpp41
-rw-r--r--src/database/database-postgresql.h2
-rw-r--r--src/database/database-redis.cpp6
-rw-r--r--src/database/database-sqlite3.cpp135
-rw-r--r--src/database/database-sqlite3.h25
-rw-r--r--src/database/database.h13
-rw-r--r--src/defaultsettings.cpp60
-rw-r--r--src/emerge.cpp90
-rw-r--r--src/emerge.h18
-rw-r--r--src/environment.cpp6
-rw-r--r--src/filesys.cpp139
-rw-r--r--src/filesys.h20
-rw-r--r--src/gamedef.h3
-rw-r--r--src/gettext.cpp4
-rw-r--r--src/gettext.h50
-rw-r--r--src/gui/CMakeLists.txt7
-rw-r--r--src/gui/guiButton.cpp86
-rw-r--r--src/gui/guiButton.h43
-rw-r--r--src/gui/guiChatConsole.cpp131
-rw-r--r--src/gui/guiChatConsole.h10
-rw-r--r--src/gui/guiConfirmRegistration.cpp19
-rw-r--r--src/gui/guiEditBox.cpp169
-rw-r--r--src/gui/guiEditBox.h9
-rw-r--r--src/gui/guiEditBoxWithScrollbar.cpp35
-rw-r--r--src/gui/guiEditBoxWithScrollbar.h6
-rw-r--r--src/gui/guiEngine.cpp79
-rw-r--r--src/gui/guiEngine.h7
-rw-r--r--src/gui/guiFormSpecMenu.cpp2305
-rw-r--r--src/gui/guiFormSpecMenu.h13
-rw-r--r--src/gui/guiHyperText.cpp23
-rw-r--r--src/gui/guiHyperText.h11
-rw-r--r--src/gui/guiKeyChangeMenu.cpp72
-rw-r--r--src/gui/guiPasswordChange.cpp8
-rw-r--r--src/gui/guiScene.cpp16
-rw-r--r--src/gui/guiScene.h1
-rw-r--r--src/gui/guiSkin.cpp42
-rw-r--r--src/gui/guiSkin.h10
-rw-r--r--src/gui/guiTable.cpp5
-rw-r--r--src/gui/guiVolumeChange.cpp15
-rw-r--r--src/gui/intlGUIEditBox.cpp626
-rw-r--r--src/gui/intlGUIEditBox.h68
-rw-r--r--src/gui/modalMenu.cpp13
-rw-r--r--src/gui/modalMenu.h4
-rw-r--r--src/gui/profilergraph.cpp15
-rw-r--r--src/gui/touchscreengui.cpp47
-rw-r--r--src/gui/touchscreengui.h14
-rw-r--r--src/httpfetch.cpp80
-rw-r--r--src/httpfetch.h27
-rw-r--r--src/hud.cpp1
-rw-r--r--src/hud.h6
-rw-r--r--src/inventory.cpp23
-rw-r--r--src/inventory.h4
-rw-r--r--src/inventorymanager.cpp16
-rw-r--r--src/irrlicht_changes/CGUITTFont.cpp117
-rw-r--r--src/irrlicht_changes/CGUITTFont.h12
-rw-r--r--src/irrlicht_changes/CMakeLists.txt7
-rw-r--r--src/irrlicht_changes/irrUString.h3891
-rw-r--r--src/irrlicht_changes/static_text.cpp74
-rw-r--r--src/irrlicht_changes/static_text.h49
-rw-r--r--src/irrlichttypes.h26
-rw-r--r--src/itemdef.cpp14
-rw-r--r--src/itemdef.h1
-rw-r--r--src/itemstackmetadata.cpp2
-rw-r--r--src/main.cpp111
-rw-r--r--src/map.cpp23
-rw-r--r--src/map.h5
-rw-r--r--src/map_settings_manager.cpp40
-rw-r--r--src/map_settings_manager.h6
-rw-r--r--src/mapblock.cpp191
-rw-r--r--src/mapblock.h20
-rw-r--r--src/mapgen/mapgen.cpp11
-rw-r--r--src/mapgen/mapgen_v6.cpp13
-rw-r--r--src/mapgen/mapgen_v6.h4
-rw-r--r--src/mapgen/mapgen_valleys.cpp3
-rw-r--r--src/mapgen/mg_biome.cpp90
-rw-r--r--src/mapgen/mg_biome.h27
-rw-r--r--src/mapgen/mg_ore.h12
-rw-r--r--src/mapgen/mg_schematic.cpp143
-rw-r--r--src/mapgen/mg_schematic.h17
-rw-r--r--src/mapnode.cpp58
-rw-r--r--src/mapnode.h8
-rw-r--r--src/network/address.cpp119
-rw-r--r--src/network/address.h35
-rw-r--r--src/network/clientopcodes.cpp2
-rw-r--r--src/network/clientpackethandler.cpp250
-rw-r--r--src/network/connection.cpp477
-rw-r--r--src/network/connection.h446
-rw-r--r--src/network/connectionthreads.cpp190
-rw-r--r--src/network/connectionthreads.h31
-rw-r--r--src/network/networkpacket.h2
-rw-r--r--src/network/networkprotocol.h17
-rw-r--r--src/network/serveropcodes.cpp4
-rw-r--r--src/network/serverpackethandler.cpp128
-rw-r--r--src/network/socket.cpp74
-rw-r--r--src/network/socket.h14
-rw-r--r--src/nodedef.cpp82
-rw-r--r--src/nodedef.h43
-rw-r--r--src/nodemetadata.cpp6
-rw-r--r--src/nodemetadata.h2
-rw-r--r--src/noise.cpp51
-rw-r--r--src/noise.h15
-rw-r--r--src/object_properties.cpp36
-rw-r--r--src/object_properties.h2
-rw-r--r--src/player.cpp59
-rw-r--r--src/player.h52
-rw-r--r--src/porting.cpp13
-rw-r--r--src/porting.h6
-rw-r--r--src/remoteplayer.cpp30
-rw-r--r--src/remoteplayer.h3
-rw-r--r--src/rollback.cpp6
-rw-r--r--src/rollback.h1
-rw-r--r--src/script/common/c_content.cpp83
-rw-r--r--src/script/common/c_content.h14
-rw-r--r--src/script/common/c_converter.cpp30
-rw-r--r--src/script/common/c_internal.cpp84
-rw-r--r--src/script/common/c_internal.h18
-rw-r--r--src/script/cpp_api/s_async.cpp108
-rw-r--r--src/script/cpp_api/s_async.h39
-rw-r--r--src/script/cpp_api/s_base.cpp11
-rw-r--r--src/script/cpp_api/s_base.h17
-rw-r--r--src/script/cpp_api/s_client.cpp88
-rw-r--r--src/script/cpp_api/s_entity.cpp2
-rw-r--r--src/script/cpp_api/s_entity.h2
-rw-r--r--src/script/cpp_api/s_env.cpp63
-rw-r--r--src/script/cpp_api/s_env.h5
-rw-r--r--src/script/cpp_api/s_item.cpp36
-rw-r--r--src/script/cpp_api/s_item.h14
-rw-r--r--src/script/cpp_api/s_mainmenu.h2
-rw-r--r--src/script/cpp_api/s_node.cpp1
-rw-r--r--src/script/cpp_api/s_node.h1
-rw-r--r--src/script/cpp_api/s_nodemeta.cpp18
-rw-r--r--src/script/cpp_api/s_player.cpp2
-rw-r--r--src/script/cpp_api/s_player.h2
-rw-r--r--src/script/cpp_api/s_security.cpp131
-rw-r--r--src/script/cpp_api/s_security.h15
-rw-r--r--src/script/cpp_api/s_server.cpp64
-rw-r--r--src/script/cpp_api/s_server.h6
-rw-r--r--src/script/lua_api/l_areastore.cpp2
-rw-r--r--src/script/lua_api/l_auth.cpp5
-rw-r--r--src/script/lua_api/l_camera.cpp2
-rw-r--r--src/script/lua_api/l_env.cpp65
-rw-r--r--src/script/lua_api/l_env.h22
-rw-r--r--src/script/lua_api/l_http.cpp66
-rw-r--r--src/script/lua_api/l_http.h3
-rw-r--r--src/script/lua_api/l_inventory.cpp15
-rw-r--r--src/script/lua_api/l_inventory.h2
-rw-r--r--src/script/lua_api/l_item.cpp2
-rw-r--r--src/script/lua_api/l_itemstackmeta.cpp2
-rw-r--r--src/script/lua_api/l_localplayer.cpp36
-rw-r--r--src/script/lua_api/l_localplayer.h3
-rw-r--r--src/script/lua_api/l_mainmenu.cpp247
-rw-r--r--src/script/lua_api/l_mainmenu.h4
-rw-r--r--src/script/lua_api/l_mapgen.cpp125
-rw-r--r--src/script/lua_api/l_metadata.cpp5
-rw-r--r--src/script/lua_api/l_minimap.cpp2
-rw-r--r--src/script/lua_api/l_modchannels.cpp2
-rw-r--r--src/script/lua_api/l_nodemeta.cpp9
-rw-r--r--src/script/lua_api/l_nodetimer.cpp2
-rw-r--r--src/script/lua_api/l_noise.cpp10
-rw-r--r--src/script/lua_api/l_object.cpp207
-rw-r--r--src/script/lua_api/l_playermeta.cpp2
-rw-r--r--src/script/lua_api/l_server.cpp102
-rw-r--r--src/script/lua_api/l_server.h15
-rw-r--r--src/script/lua_api/l_settings.cpp80
-rw-r--r--src/script/lua_api/l_storage.cpp20
-rw-r--r--src/script/lua_api/l_util.cpp209
-rw-r--r--src/script/lua_api/l_util.h28
-rw-r--r--src/script/lua_api/l_vmanip.cpp2
-rw-r--r--src/script/scripting_mainmenu.cpp6
-rw-r--r--src/script/scripting_mainmenu.h5
-rw-r--r--src/serialization.cpp160
-rw-r--r--src/serialization.h14
-rw-r--r--src/server.cpp472
-rw-r--r--src/server.h43
-rw-r--r--src/server/luaentity_sao.cpp13
-rw-r--r--src/server/luaentity_sao.h5
-rw-r--r--src/server/mods.cpp8
-rw-r--r--src/server/player_sao.cpp45
-rw-r--r--src/server/player_sao.h10
-rw-r--r--src/server/serveractiveobject.h7
-rw-r--r--src/server/serverinventorymgr.cpp32
-rw-r--r--src/server/unit_sao.cpp31
-rw-r--r--src/serverenvironment.cpp23
-rw-r--r--src/serverenvironment.h15
-rw-r--r--src/serverlist.cpp1
-rw-r--r--src/settings.cpp133
-rw-r--r--src/settings.h52
-rw-r--r--src/settings_translation_file.cpp119
-rw-r--r--src/skyparams.h45
-rw-r--r--src/staticobject.cpp24
-rw-r--r--src/tool.cpp106
-rw-r--r--src/tool.h20
-rw-r--r--src/translation.cpp8
-rw-r--r--src/unittest/CMakeLists.txt5
-rw-r--r--src/unittest/test.cpp6
-rw-r--r--src/unittest/test_areastore.cpp4
-rw-r--r--src/unittest/test_clientactiveobjectmgr.cpp1
-rw-r--r--src/unittest/test_compression.cpp42
-rw-r--r--src/unittest/test_config.h.in1
-rw-r--r--src/unittest/test_connection.cpp10
-rw-r--r--src/unittest/test_gettext.cpp43
-rw-r--r--src/unittest/test_irrptr.cpp10
-rw-r--r--src/unittest/test_map.cpp68
-rw-r--r--src/unittest/test_map_settings_manager.cpp5
-rw-r--r--src/unittest/test_mod/test_mod/init.lua1
-rw-r--r--src/unittest/test_mod/test_mod/mod.conf2
-rw-r--r--src/unittest/test_modmetadatadatabase.cpp253
-rw-r--r--src/unittest/test_noderesolver.cpp2
-rw-r--r--src/unittest/test_schematic.cpp40
-rw-r--r--src/unittest/test_servermodmanager.cpp21
-rw-r--r--src/unittest/test_socket.cpp18
-rw-r--r--src/unittest/test_utilities.cpp99
-rw-r--r--src/unittest/test_voxelarea.cpp18
-rw-r--r--src/util/CMakeLists.txt1
-rw-r--r--src/util/Optional.h32
-rw-r--r--src/util/base64.cpp29
-rw-r--r--src/util/numeric.cpp19
-rw-r--r--src/util/numeric.h4
-rwxr-xr-xsrc/util/png.cpp68
-rwxr-xr-x[-rw-r--r--]src/util/png.h (renamed from src/cloudparams.h)17
-rw-r--r--src/util/pointer.h58
-rw-r--r--src/util/serialize.cpp5
-rw-r--r--src/util/string.cpp450
-rw-r--r--src/util/string.h43
-rw-r--r--src/version.cpp1
-rw-r--r--textures/base/pack/aux1_btn.pngbin0 -> 1652 bytes
-rw-r--r--textures/base/pack/aux_btn.pngbin1900 -> 0 bytes
-rw-r--r--textures/base/pack/no_texture.pngbin0 -> 281 bytes
-rw-r--r--textures/base/pack/server_favorite.png (renamed from textures/base/pack/server_flags_favorite.png)bin916 -> 916 bytes
-rw-r--r--textures/base/pack/server_incompatible.pngbin0 -> 385 bytes
-rw-r--r--textures/base/pack/server_public.pngbin0 -> 492 bytes
-rwxr-xr-xutil/buildbot/buildwin32.sh182
-rwxr-xr-xutil/buildbot/buildwin64.sh184
-rw-r--r--util/buildbot/toolchain_i686-w64-mingw32-posix.cmake (renamed from util/buildbot/toolchain_i586-mingw32msvc.cmake)10
-rw-r--r--util/buildbot/toolchain_i686-w64-mingw32.cmake (renamed from util/buildbot/toolchain_i646-w64-mingw32.cmake)0
-rw-r--r--util/buildbot/toolchain_x86_64-w64-mingw32-posix.cmake19
-rw-r--r--util/ci/clang-format-whitelist.txt2
-rwxr-xr-xutil/ci/clang-format.sh (renamed from util/ci/lint.sh)25
-rw-r--r--util/ci/common.sh16
-rwxr-xr-xutil/fix_format.sh5
-rwxr-xr-xutil/test_multiplayer.sh105
-rwxr-xr-xutil/updatepo.sh5
-rw-r--r--util/wireshark/minetest.lua4
563 files changed, 89270 insertions, 32675 deletions
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 000000000..bda43ebc0
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,4 @@
+./cmake-build-*
+./build/*
+./cache/*
+Dockerfile
diff --git a/.editorconfig b/.editorconfig
new file mode 100755
index 000000000..ec0645241
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+[*]
+end_of_line = lf
+
+[*.{cpp,h,lua,txt,glsl,md,c,cmake,java,gradle}]
+charset = utf8
+indent_size = 4
+indent_style = tab
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index b01a89509..f60f584f9 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -10,13 +10,31 @@ Contributions are welcome! Here's how you can help:
## Code
-1. [Fork](https://help.github.com/articles/fork-a-repo/) the repository and [clone](https://help.github.com/articles/cloning-a-repository/) your fork.
+1. [Fork](https://help.github.com/articles/fork-a-repo/) the repository and
+ [clone](https://help.github.com/articles/cloning-a-repository/) your fork.
-2. Before you start coding, consider opening an [issue at Github](https://github.com/minetest/minetest/issues) to discuss the suitability and implementation of your intended contribution with the core developers. If you are planning to start some very significant coding, you would benefit from first discussing on our IRC development channel [#minetest-dev](http://www.minetest.net/irc/). Note that a proper IRC client is required to speak on this channel.
+2. Before you start coding, consider opening an
+ [issue at Github](https://github.com/minetest/minetest/issues) to discuss the
+ suitability and implementation of your intended contribution with the core
+ developers.
+
+ Any Pull Request that isn't a bug fix and isn't covered by
+ [the roadmap](../doc/direction.md) will be closed within a week unless it
+ receives a concept approval from a Core Developer. For this reason, it is
+ recommended that you open an issue for any such pull requests before doing
+ the work, to avoid disappointment.
+
+ You may also benefit from discussing on our IRC development channel
+ [#minetest-dev](http://www.minetest.net/irc/). Note that a proper IRC client
+ is required to speak on this channel.
3. Start coding!
- - Refer to the [Lua API](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt), [Developer Wiki](http://dev.minetest.net/Main_Page) and other [documentation](https://github.com/minetest/minetest/tree/master/doc).
- - Follow the [C/C++](http://dev.minetest.net/Code_style_guidelines) and [Lua](http://dev.minetest.net/Lua_code_style_guidelines) code style guidelines.
+ - Refer to the
+ [Lua API](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt),
+ [Developer Wiki](http://dev.minetest.net/Main_Page) and other
+ [documentation](https://github.com/minetest/minetest/tree/master/doc).
+ - Follow the [C/C++](http://dev.minetest.net/Code_style_guidelines) and
+ [Lua](http://dev.minetest.net/Lua_code_style_guidelines) code style guidelines.
- Check your code works as expected and document any changes to the Lua API.
4. Commit & [push](https://help.github.com/articles/pushing-to-a-remote/) your changes to a new branch (not `master`, one change per branch)
@@ -33,67 +51,115 @@ Contributions are welcome! Here's how you can help:
5. Once you are happy with your changes, submit a pull request.
- Open the [pull-request form](https://github.com/minetest/minetest/pull/new/master).
- - Add a description explaining what you've done (or if it's a work-in-progress - what you need to do).
+ - Add a description explaining what you've done (or if it's a
+ work-in-progress - what you need to do).
+ - Make sure to fill out the pull request template.
### A pull-request is considered merge-able when:
-1. It follows the roadmap in some way and fits the whole picture of the project: [roadmap introduction](http://c55.me/blog/?p=1491), [roadmap continued](https://forum.minetest.net/viewtopic.php?t=9177)
+1. It follows [the roadmap](../doc/direction.md) in some way and fits the whole
+ picture of the project.
2. It works.
-3. It follows the code style for [C/C++](http://dev.minetest.net/Code_style_guidelines) or [Lua](http://dev.minetest.net/Lua_code_style_guidelines).
-4. The code's interfaces are well designed, regardless of other aspects that might need more work in the future.
+3. It follows the code style for
+ [C/C++](http://dev.minetest.net/Code_style_guidelines) or
+ [Lua](http://dev.minetest.net/Lua_code_style_guidelines).
+4. The code's interfaces are well designed, regardless of other aspects that
+ might need more work in the future.
5. It uses protocols and formats which include the required compatibility.
### Important note about automated GitHub checks
-When you submit a pull request, GitHub automatically runs checks on the Minetest Engine combined with your changes. One of these checks is called 'cpp lint / clang format', which checks code formatting. Because formatting for readability requires human judgement this check often fails and often makes unsuitable formatting requests which make code readability worse.
+When you submit a pull request, GitHub automatically runs checks on the Minetest
+Engine combined with your changes. One of these checks is called 'cpp lint /
+clang format', which checks code formatting. Because formatting for readability
+requires human judgement this check often fails and often makes unsuitable
+formatting requests which make code readability worse.
-If this check fails, look at the details to check for any clear mistakes and correct those. However, you should not apply everything ClangFormat requests. Ignore requests that make code readability worse and any other clearly unsuitable requests. Discuss in the pull request with a core developer about how to progress.
+If this check fails, look at the details to check for any clear mistakes and
+correct those. However, you should not apply everything ClangFormat requests.
+Ignore requests that make code readability worse and any other clearly
+unsuitable requests. Discuss in the pull request with a core developer about how
+to progress.
## Issues
-If you experience an issue, we would like to know the details - especially when a stable release is on the way.
+If you experience an issue, we would like to know the details - especially when
+a stable release is on the way.
1. Do a quick search on GitHub to check if the issue has already been reported.
-2. Is it an issue with the Minetest *engine*? If not, report it [elsewhere](http://www.minetest.net/development/#reporting-issues).
-3. [Open an issue](https://github.com/minetest/minetest/issues/new) and describe the issue you are having - you could include:
+2. Is it an issue with the Minetest *engine*? If not, report it
+ [elsewhere](http://www.minetest.net/development/#reporting-issues).
+3. [Open an issue](https://github.com/minetest/minetest/issues/new) and describe
+ the issue you are having - you could include:
- Error logs (check the bottom of the `debug.txt` file).
- Screenshots.
- Ways you have tried to solve the issue, and whether they worked or not.
- Your Minetest version and the content (games, mods or texture packs) you have installed.
- Your platform (e.g. Windows 10 or Ubuntu 15.04 x64).
-After reporting you should aim to answer questions or clarifications as this helps pinpoint the cause of the issue (if you don't do this your issue may be closed after 1 month).
+After reporting you should aim to answer questions or clarifications as this
+helps pinpoint the cause of the issue (if you don't do this your issue may be
+closed after 1 month).
## Feature requests
-Feature requests are welcome but take a moment to see if your idea follows the roadmap in some way and fits the whole picture of the project: [roadmap introduction](http://c55.me/blog/?p=1491), [roadmap continued](https://forum.minetest.net/viewtopic.php?t=9177). You should provide a clear explanation with as much detail as possible.
+Feature requests are welcome but take a moment to see if your idea follows
+[the roadmap](../doc/direction.md) in some way and fits the whole picture of
+the project. You should provide a clear explanation with as much detail as
+possible.
## Translations
-Translations of Minetest are performed using Weblate. You can access the project page with a list of current languages [here](https://hosted.weblate.org/projects/minetest/minetest/).
+The core translations of Minetest are performed using Weblate. You can access
+the project page with a list of current languages
+[here](https://hosted.weblate.org/projects/minetest/minetest/).
+
+Builtin (the component which contains things like server messages, chat command
+descriptions, privilege descriptions) is translated separately; it needs to be
+translated by editing a `.tr` text file. See
+[Translation](https://dev.minetest.net/Translation) for more information.
## Donations
-If you'd like to monetarily support Minetest development, you can find donation methods on [our website](http://www.minetest.net/development/#donate).
+If you'd like to monetarily support Minetest development, you can find donation
+methods on [our website](http://www.minetest.net/development/#donate).
# Maintaining
-*This is a concise version of the [Rules & Guidelines](http://dev.minetest.net/Category:Rules_and_Guidelines) on the developer wiki.*
+* This is a concise version of the
+ [Rules & Guidelines](http://dev.minetest.net/Category:Rules_and_Guidelines) on the developer wiki.*
These notes are for those who have push access Minetest (core developers / maintainers).
- See the [project organisation](http://dev.minetest.net/Organisation) for the people involved.
+## Concept approvals and roadmaps
+
+If a Pull Request is not a bug fix:
+
+* If it matches a goal in [the roadmap](../doc/direction.md), then the PR should
+ be labelled as "Roadmap" and the goal stated by number in the description.
+* If it doesn't match a goal, then it needs to receive a concept approval within
+ a week of being opened to remain open. This 1 week deadline does not apply to
+ PRs opened before the roadmap was adopted; instead, they may remain open or be
+ closed as needed. Use the "Concept Approved" label. Issues can be marked as
+ "Concept Approved" to give preapproval to future PRs.
+
## Reviewing pull requests
-Pull requests should be reviewed and, if appropriate, checked if they achieve their intended purpose. You can show that you are in the process of, or will review the pull request by commenting *"Looks good"* or something similar.
+Pull requests should be reviewed and, if appropriate, checked if they achieve
+their intended purpose. You can show that you are in the process of, or will
+review the pull request by commenting *"Looks good"* or something similar.
**If the pull-request is not [merge-able](#a-pull-request-is-considered-merge-able-when):**
-Submit a comment explaining to the author what they need to change to make the pull-request merge-able.
+Submit a comment explaining to the author what they need to change to make the
+pull-request merge-able.
-- If the author comments or makes changes to the pull-request, it can be reviewed again.
-- If no response is made from the author within 1 month (when improvements are suggested or a question is asked), it can be closed.
+- If the author comments or makes changes to the pull-request, it can be
+ reviewed again.
+- If no response is made from the author within 1 month (when improvements are
+ suggested or a question is asked), it can be closed.
**If the pull-request is [merge-able](#a-pull-request-is-considered-merge-able-when):**
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index ccec99bc5..4132826cd 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -3,6 +3,7 @@ Add compact, short information about your PR for easier understanding:
- Goal of the PR
- How does the PR work?
- Does it resolve any reported issue?
+- Does this relate to a goal in [the roadmap](../doc/direction.md)?
- If not a bug fix, why is this PR needed? What usecases does it solve?
## To do
diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index 47ab64d11..cc5fe83ef 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -21,13 +21,13 @@ on:
jobs:
build:
- runs-on: ubuntu-18.04
+ runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- - name: Set up JDK 1.8
- uses: actions/setup-java@v1
- with:
- java-version: 1.8
+ - name: Install deps
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y --no-install-recommends gettext openjdk-11-jdk-headless
- name: Build with Gradle
run: cd android; ./gradlew assemblerelease
- name: Save armeabi artifact
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a3cc92a8e..79f9af5c7 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -13,6 +13,8 @@ on:
- 'util/buildbot/**'
- 'util/ci/**'
- '.github/workflows/**.yml'
+ - 'Dockerfile'
+ - '.dockerignore'
pull_request:
paths:
- 'lib/**.[ch]'
@@ -24,51 +26,53 @@ on:
- 'util/buildbot/**'
- 'util/ci/**'
- '.github/workflows/**.yml'
+ - 'Dockerfile'
+ - '.dockerignore'
jobs:
- # This is our minor gcc compiler
- gcc_6:
+ # Older gcc version (should be close to our minimum supported version)
+ gcc_5:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
source ./util/ci/common.sh
- install_linux_deps g++-6
+ install_linux_deps g++-5
- name: Build
run: |
./util/ci/build.sh
env:
- CC: gcc-6
- CXX: g++-6
+ CC: gcc-5
+ CXX: g++-5
- name: Test
run: |
./bin/minetest --run-unittests
- # This is the current gcc compiler (available in bionic)
- gcc_8:
- runs-on: ubuntu-18.04
+ # Current gcc version
+ gcc_10:
+ runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
source ./util/ci/common.sh
- install_linux_deps g++-8
+ install_linux_deps g++-10
- name: Build
run: |
./util/ci/build.sh
env:
- CC: gcc-8
- CXX: g++-8
+ CC: gcc-10
+ CXX: g++-10
- name: Test
run: |
./bin/minetest --run-unittests
- # This is our minor clang compiler
+ # Older clang version (should be close to our minimum supported version)
clang_3_9:
runs-on: ubuntu-18.04
steps:
@@ -76,7 +80,7 @@ jobs:
- name: Install deps
run: |
source ./util/ci/common.sh
- install_linux_deps clang-3.9
+ install_linux_deps clang-3.9 gdb
- name: Build
run: |
@@ -85,26 +89,30 @@ jobs:
CC: clang-3.9
CXX: clang++-3.9
- - name: Test
+ - name: Unittest
run: |
./bin/minetest --run-unittests
- # This is the current clang version
- clang_9:
- runs-on: ubuntu-18.04
+ - name: Integration test + devtest
+ run: |
+ ./util/test_multiplayer.sh
+
+ # Current clang version
+ clang_10:
+ runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
source ./util/ci/common.sh
- install_linux_deps clang-9 valgrind libluajit-5.1-dev
+ install_linux_deps clang-10 valgrind libluajit-5.1-dev
- name: Build
run: |
./util/ci/build.sh
env:
- CC: clang-9
- CXX: clang++-9
+ CC: clang-10
+ CXX: clang++-10
CMAKE_FLAGS: "-DREQUIRE_LUAJIT=1"
- name: Test
@@ -124,7 +132,7 @@ jobs:
- name: Install deps
run: |
source ./util/ci/common.sh
- install_linux_deps clang-9
+ install_linux_deps --old-irr clang-9
- name: Build prometheus-cpp
run: |
@@ -142,29 +150,6 @@ jobs:
run: |
./bin/minetestserver --run-unittests
- # Build without freetype (client-only)
- clang_9_no_freetype:
- name: "clang_9 (FREETYPE=0)"
- runs-on: ubuntu-18.04
- steps:
- - uses: actions/checkout@v2
- - name: Install deps
- run: |
- source ./util/ci/common.sh
- install_linux_deps clang-9
-
- - name: Build
- run: |
- ./util/ci/build.sh
- env:
- CC: clang-9
- CXX: clang++-9
- CMAKE_FLAGS: "-DENABLE_FREETYPE=0 -DBUILD_SERVER=0"
-
- - name: Test
- run: |
- ./bin/minetest --run-unittests
-
docker:
name: "Docker image"
runs-on: ubuntu-18.04
@@ -172,7 +157,8 @@ jobs:
- uses: actions/checkout@v2
- name: Build docker image
run: |
- docker build .
+ docker build . -t minetest:latest
+ docker run --rm minetest:latest /usr/local/bin/minetestserver --version
win32:
name: "MinGW cross-compiler (32-bit)"
@@ -212,11 +198,14 @@ jobs:
msvc:
name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }}
- runs-on: windows-2019
+ runs-on: windows-2019
+ #### Disabled due to Irrlicht switch
+ if: false
+ #### Disabled due to Irrlicht switch
env:
VCPKG_VERSION: 0bf3923f9fab4001c00f0f429682a0853b5749e0
# 2020.11
- vcpkg_packages: irrlicht zlib curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit
+ vcpkg_packages: irrlicht zlib zstd curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit
strategy:
fail-fast: false
matrix:
diff --git a/.github/workflows/cpp_lint.yml b/.github/workflows/cpp_lint.yml
index 1f97d105a..2bd884c7a 100644
--- a/.github/workflows/cpp_lint.yml
+++ b/.github/workflows/cpp_lint.yml
@@ -24,20 +24,21 @@ on:
- '.github/workflows/**.yml'
jobs:
- clang_format:
- runs-on: ubuntu-18.04
- steps:
- - uses: actions/checkout@v2
- - name: Install clang-format
- run: |
- sudo apt-get install clang-format-9 -qyy
- - name: Run clang-format
- run: |
- source ./util/ci/lint.sh
- perform_lint
- env:
- CLANG_FORMAT: clang-format-9
+# clang_format:
+# runs-on: ubuntu-18.04
+# steps:
+# - uses: actions/checkout@v2
+# - name: Install clang-format
+# run: |
+# sudo apt-get install clang-format-9 -qyy
+#
+# - name: Run clang-format
+# run: |
+# source ./util/ci/clang-format.sh
+# check_format
+# env:
+# CLANG_FORMAT: clang-format-9
clang_tidy:
runs-on: ubuntu-18.04
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
new file mode 100644
index 000000000..69253b70a
--- /dev/null
+++ b/.github/workflows/macos.yml
@@ -0,0 +1,66 @@
+name: macos
+
+# build on c/cpp changes or workflow changes
+on:
+ push:
+ paths:
+ - 'lib/**.[ch]'
+ - 'lib/**.cpp'
+ - 'src/**.[ch]'
+ - 'src/**.cpp'
+ - '**/CMakeLists.txt'
+ - 'cmake/Modules/**'
+ - '.github/workflows/macos.yml'
+ pull_request:
+ paths:
+ - 'lib/**.[ch]'
+ - 'lib/**.cpp'
+ - 'src/**.[ch]'
+ - 'src/**.cpp'
+ - '**/CMakeLists.txt'
+ - 'cmake/Modules/**'
+ - '.github/workflows/macos.yml'
+
+env:
+ IRRLICHT_TAG: 1.9.0mt4
+ MINETEST_GAME_REPO: https://github.com/minetest/minetest_game.git
+ MINETEST_GAME_BRANCH: master
+ MINETEST_GAME_NAME: minetest_game
+
+jobs:
+ build:
+ runs-on: macos-10.15
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install deps
+ run: |
+ pkgs=(cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd)
+ brew update
+ brew install ${pkgs[@]}
+ brew unlink $(brew ls --formula)
+ brew link ${pkgs[@]}
+
+ - name: Build
+ run: |
+ git clone -b $MINETEST_GAME_BRANCH $MINETEST_GAME_REPO games/$MINETEST_GAME_NAME
+ rm -rvf games/$MINETEST_GAME_NAME/.git
+ git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG lib/irrlichtmt
+ mkdir cmakebuild
+ cd cmakebuild
+ cmake .. \
+ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \
+ -DCMAKE_FIND_FRAMEWORK=LAST \
+ -DCMAKE_INSTALL_PREFIX=../build/macos/ \
+ -DRUN_IN_PLACE=FALSE \
+ -DENABLE_FREETYPE=TRUE -DENABLE_GETTEXT=TRUE
+ make -j2
+ make install
+
+ - name: Test
+ run: |
+ ./build/macos/minetest.app/Contents/MacOS/minetest --run-unittests
+
+ - uses: actions/upload-artifact@v2
+ with:
+ name: minetest-macos
+ path: ./build/macos/
diff --git a/.gitignore b/.gitignore
index 31e938ce4..bb5e0a0cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -87,8 +87,8 @@ src/test_config.h
src/cmake_config.h
src/cmake_config_githash.h
src/unittest/test_world/world.mt
-src/lua/build/
-locale/
+games/devtest/mods/testnodes/textures/testnodes_generated_*.png
+/locale/
.directory
*.cbp
*.layout
@@ -107,3 +107,13 @@ CMakeDoxy*
compile_commands.json
*.apk
*.zip
+# Visual Studio
+*.vcxproj*
+*.sln
+.vs/
+
+# Optional user provided library folder
+lib/irrlichtmt
+
+# Generated mod storage database
+client/mod_storage.sqlite
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0441aeaa1..5d2600364 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,16 +9,20 @@ stages:
- deploy
variables:
+ IRRLICHT_TAG: "1.9.0mt4"
MINETEST_GAME_REPO: "https://github.com/minetest/minetest_game.git"
CONTAINER_IMAGE: registry.gitlab.com/$CI_PROJECT_PATH
.build_template:
stage: build
+ before_script:
+ - apt-get update
+ - DEBIAN_FRONTEND=noninteractive apt-get -y install build-essential git cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libleveldb-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev
script:
+ - git clone https://github.com/minetest/irrlicht -b $IRRLICHT_TAG lib/irrlichtmt
- mkdir cmakebuild
- - mkdir -p artifact/minetest/usr/
- cd cmakebuild
- - cmake -DCMAKE_INSTALL_PREFIX=../artifact/minetest/usr/ -DCMAKE_BUILD_TYPE=Release -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE -DENABLE_SYSTEM_JSONCPP=TRUE -DBUILD_SERVER=TRUE ..
+ - cmake -DCMAKE_INSTALL_PREFIX=../artifact/minetest/usr/ -DCMAKE_BUILD_TYPE=Release -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE -DBUILD_SERVER=TRUE ..
- make -j2
- make install
artifacts:
@@ -30,7 +34,7 @@ variables:
.debpkg_template:
stage: package
before_script:
- - apt-get update -y
+ - apt-get update
- apt-get install -y git
- mkdir -p build/deb/minetest/DEBIAN/
- cp misc/debpkg-control build/deb/minetest/DEBIAN/control
@@ -39,7 +43,9 @@ variables:
- git clone $MINETEST_GAME_REPO build/deb/minetest/usr/share/minetest/games/minetest_game
- rm -rf build/deb/minetest/usr/share/minetest/games/minetest/.git
- sed -i 's/DATEPLACEHOLDER/'$(date +%y.%m.%d)'/g' build/deb/minetest/DEBIAN/control
+ - sed -i 's/JPEG_PLACEHOLDER/'$JPEG_PKG'/g' build/deb/minetest/DEBIAN/control
- sed -i 's/LEVELDB_PLACEHOLDER/'$LEVELDB_PKG'/g' build/deb/minetest/DEBIAN/control
+ - sed -i 's/JSONCPP_PLACEHOLDER/'$JSONCPP_PKG'/g' build/deb/minetest/DEBIAN/control
- cd build/deb/ && dpkg-deb -b minetest/ && mv minetest.deb ../../
artifacts:
expire_in: 90 day
@@ -49,7 +55,7 @@ variables:
.debpkg_install:
stage: deploy
before_script:
- - apt-get update -y
+ - apt-get update -qy
script:
- apt-get install -y ./*.deb
- minetest --version
@@ -63,9 +69,6 @@ variables:
build:debian-9:
extends: .build_template
image: debian:9
- before_script:
- - apt-get update -y
- - apt-get -y install build-essential libirrlicht-dev cmake libbz2-dev libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev
package:debian-9:
extends: .debpkg_template
@@ -73,7 +76,9 @@ package:debian-9:
needs:
- build:debian-9
variables:
+ JSONCPP_PKG: libjsoncpp1
LEVELDB_PKG: libleveldb1v5
+ JPEG_PKG: libjpeg62-turbo
deploy:debian-9:
extends: .debpkg_install
@@ -86,9 +91,6 @@ deploy:debian-9:
build:debian-10:
extends: .build_template
image: debian:10
- before_script:
- - apt-get update -y
- - apt-get -y install build-essential libirrlicht-dev cmake libbz2-dev libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev
package:debian-10:
extends: .debpkg_template
@@ -96,7 +98,9 @@ package:debian-10:
needs:
- build:debian-10
variables:
+ JSONCPP_PKG: libjsoncpp1
LEVELDB_PKG: libleveldb1d
+ JPEG_PKG: libjpeg62-turbo
deploy:debian-10:
extends: .debpkg_install
@@ -104,41 +108,37 @@ deploy:debian-10:
needs:
- package:debian-10
-##
-## Ubuntu
-##
-
-# Xenial
+# Bullseye
-build:ubuntu-16.04:
- extends: .build_template
- image: ubuntu:xenial
- before_script:
- - apt-get update -y
- - apt-get -y install build-essential libirrlicht-dev cmake libbz2-dev libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev
+build:debian-11:
+ extends: .build_template
+ image: debian:11
-package:ubuntu-16.04:
+package:debian-11:
extends: .debpkg_template
- image: ubuntu:xenial
+ image: debian:11
needs:
- - build:ubuntu-16.04
+ - build:debian-11
variables:
- LEVELDB_PKG: libleveldb1v5
+ JSONCPP_PKG: libjsoncpp24
+ LEVELDB_PKG: libleveldb1d
+ JPEG_PKG: libjpeg62-turbo
-deploy:ubuntu-16.04:
+deploy:debian-11:
extends: .debpkg_install
- image: ubuntu:xenial
+ image: debian:11
needs:
- - package:ubuntu-16.04
+ - package:debian-11
+
+##
+## Ubuntu
+##
# Bionic
build:ubuntu-18.04:
extends: .build_template
image: ubuntu:bionic
- before_script:
- - apt-get update -y
- - apt-get -y install build-essential libirrlicht-dev cmake libbz2-dev libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev
package:ubuntu-18.04:
extends: .debpkg_template
@@ -146,7 +146,9 @@ package:ubuntu-18.04:
needs:
- build:ubuntu-18.04
variables:
+ JSONCPP_PKG: libjsoncpp1
LEVELDB_PKG: libleveldb1v5
+ JPEG_PKG: libjpeg-turbo8
deploy:ubuntu-18.04:
extends: .debpkg_install
@@ -154,6 +156,28 @@ deploy:ubuntu-18.04:
needs:
- package:ubuntu-18.04
+# Focal
+
+build:ubuntu-20.04:
+ extends: .build_template
+ image: ubuntu:focal
+
+package:ubuntu-20.04:
+ extends: .debpkg_template
+ image: ubuntu:focal
+ needs:
+ - build:ubuntu-20.04
+ variables:
+ JSONCPP_PKG: libjsoncpp1
+ LEVELDB_PKG: libleveldb1d
+ JPEG_PKG: libjpeg-turbo8
+
+deploy:ubuntu-20.04:
+ extends: .debpkg_install
+ image: ubuntu:focal
+ needs:
+ - package:ubuntu-20.04
+
##
## Fedora
##
@@ -163,17 +187,17 @@ build:fedora-28:
extends: .build_template
image: fedora:28
before_script:
- - dnf -y install make automake gcc gcc-c++ kernel-devel cmake libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel irrlicht-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel
+ - dnf -y install make git gcc gcc-c++ kernel-devel cmake libjpeg-devel libpng-devel libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel
##
## MinGW for Windows
##
.generic_win_template:
- image: ubuntu:bionic
+ image: ubuntu:focal
before_script:
- - apt-get update -y
- - apt-get install -y wget xz-utils unzip git cmake gettext
+ - apt-get update
+ - DEBIAN_FRONTEND=noninteractive apt-get install -y wget xz-utils unzip git cmake gettext
- wget -nv http://minetest.kitsunemimi.pw/mingw-w64-${WIN_ARCH}_9.2.0_ubuntu18.04.tar.xz -O mingw.tar.xz
- tar -xaf mingw.tar.xz -C /usr
@@ -181,19 +205,6 @@ build:fedora-28:
extends: .generic_win_template
stage: build
artifacts:
- expire_in: 1h
- paths:
- - build/minetest/_build/*
-
-.package_win_template:
- extends: .generic_win_template
- stage: package
- script:
- - unzip build/minetest/_build/minetest-*.zip
- - cp -p /usr/${WIN_ARCH}-w64-mingw32/bin/libgcc*.dll minetest-*-win*/bin/
- - cp -p /usr/${WIN_ARCH}-w64-mingw32/bin/libstdc++*.dll minetest-*-win*/bin/
- - cp -p /usr/${WIN_ARCH}-w64-mingw32/bin/libwinpthread*.dll minetest-*-win*/bin/
- artifacts:
expire_in: 90 day
paths:
- minetest-*-win*/*
@@ -201,29 +212,16 @@ build:fedora-28:
build:win32:
extends: .build_win_template
script:
- - ./util/buildbot/buildwin32.sh build
- variables:
- WIN_ARCH: "i686"
-
-package:win32:
- extends: .package_win_template
- needs:
- - build:win32
+ - EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh build
+ - unzip -q build/build/*.zip
variables:
WIN_ARCH: "i686"
-
build:win64:
extends: .build_win_template
script:
- - ./util/buildbot/buildwin64.sh build
- variables:
- WIN_ARCH: "x86_64"
-
-package:win64:
- extends: .package_win_template
- needs:
- - build:win64
+ - EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh build
+ - unzip -q build/build/*.zip
variables:
WIN_ARCH: "x86_64"
@@ -274,7 +272,7 @@ package:appimage-client:
- build:ubuntu-18.04
before_script:
- apt-get update -y
- - apt-get install -y git wget
+ - apt-get install -y git
# Collect files
- mkdir AppDir
- cp -a artifact/minetest/usr/ AppDir/usr/
diff --git a/.luacheckrc b/.luacheckrc
index e010ab95c..a922bdea9 100644
--- a/.luacheckrc
+++ b/.luacheckrc
@@ -20,7 +20,7 @@ read_globals = {
string = {fields = {"split", "trim"}},
table = {fields = {"copy", "getn", "indexof", "insert_all"}},
- math = {fields = {"hypot"}},
+ math = {fields = {"hypot", "round"}},
}
globals = {
diff --git a/AppImageBuilder.yml b/AppImageBuilder.yml
index 9ecad5d8e..5788e246b 100644
--- a/AppImageBuilder.yml
+++ b/AppImageBuilder.yml
@@ -24,18 +24,21 @@ AppDir:
- sourceline: deb http://archive.ubuntu.com/ubuntu/ bionic-security main universe
include:
- - libirrlicht1.8
- - libxxf86vm1
- - libgl1-mesa-glx
- - libsqlite3-0
- - libogg0
- - libvorbis0a
- - libopenal1
+ - libc6
- libcurl3-gnutls
- libfreetype6
- - zlib1g
- - libgmp10
+ - libgl1
+ - libjpeg-turbo8
- libjsoncpp1
+ - libleveldb1v5
+ - libopenal1
+ - libpng16-16
+ - libsqlite3-0
+ - libstdc++6
+ - libvorbisfile3
+ - libx11-6
+ - libxxf86vm1
+ - zlib1g
files:
exclude:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5629cbe70..40a9ce15f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,19 +1,24 @@
cmake_minimum_required(VERSION 3.5)
-cmake_policy(SET CMP0025 OLD)
+# Set policies up to 3.9 since we want to enable the IPO option
+if(${CMAKE_VERSION} VERSION_LESS 3.9)
+ cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
+else()
+ cmake_policy(VERSION 3.9)
+endif()
# This can be read from ${PROJECT_NAME} after project() is called
project(minetest)
set(PROJECT_NAME_CAPITALIZED "Minetest")
set(CMAKE_CXX_STANDARD 11)
-set(GCC_MINIMUM_VERSION "4.8")
-set(CLANG_MINIMUM_VERSION "3.4")
+set(GCC_MINIMUM_VERSION "5.1")
+set(CLANG_MINIMUM_VERSION "3.5")
# Also remember to set PROTOCOL_VERSION in network/networkprotocol.h when releasing
set(VERSION_MAJOR 5)
-set(VERSION_MINOR 4)
-set(VERSION_PATCH 2)
+set(VERSION_MINOR 5)
+set(VERSION_PATCH 0)
set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")
# Change to false for releases
@@ -21,7 +26,7 @@ set(DEVELOPMENT_BUILD FALSE)
set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
if(VERSION_EXTRA)
- set(VERSION_STRING ${VERSION_STRING}-${VERSION_EXTRA})
+ set(VERSION_STRING "${VERSION_STRING}-${VERSION_EXTRA}")
elseif(DEVELOPMENT_BUILD)
set(VERSION_STRING "${VERSION_STRING}-dev")
endif()
@@ -47,7 +52,6 @@ set(BUILD_CLIENT TRUE CACHE BOOL "Build client")
set(BUILD_SERVER FALSE CACHE BOOL "Build server")
set(BUILD_UNITTESTS TRUE CACHE BOOL "Build unittests")
-
set(WARN_ALL TRUE CACHE BOOL "Enable -Wall for Release build")
if(NOT CMAKE_BUILD_TYPE)
@@ -59,8 +63,62 @@ endif()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
+set(IRRLICHTMT_BUILD_DIR "" CACHE PATH "Path to IrrlichtMt build directory.")
+if(NOT "${IRRLICHTMT_BUILD_DIR}" STREQUAL "")
+ find_package(IrrlichtMt QUIET
+ PATHS "${IRRLICHTMT_BUILD_DIR}"
+ NO_DEFAULT_PATH
+)
+
+ if(NOT TARGET IrrlichtMt::IrrlichtMt)
+ # find_package() searches certain subdirectories. ${PATH}/cmake is not
+ # the only one, but it is the one where IrrlichtMt is supposed to export
+ # IrrlichtMtConfig.cmake
+ message(FATAL_ERROR "Could not find IrrlichtMtConfig.cmake in ${IRRLICHTMT_BUILD_DIR}/cmake.")
+ endif()
# This is done here so that relative search paths are more reasonable
-find_package(Irrlicht)
+elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt")
+ message(STATUS "Using user-provided IrrlichtMt at subdirectory 'lib/irrlichtmt'")
+ if(BUILD_CLIENT)
+ # tell IrrlichtMt to create a static library
+ set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared library" FORCE)
+ add_subdirectory(lib/irrlichtmt EXCLUDE_FROM_ALL)
+ unset(BUILD_SHARED_LIBS CACHE)
+
+ if(NOT TARGET IrrlichtMt)
+ message(FATAL_ERROR "IrrlichtMt project is missing a CMake target?!")
+ endif()
+ else()
+ add_library(IrrlichtMt::IrrlichtMt INTERFACE IMPORTED)
+ set_target_properties(IrrlichtMt::IrrlichtMt PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt/include")
+ endif()
+else()
+ find_package(IrrlichtMt QUIET)
+ if(NOT TARGET IrrlichtMt::IrrlichtMt)
+ string(CONCAT explanation_msg
+ "The Minetest team has forked Irrlicht to make their own customizations. "
+ "It can be found here: https://github.com/minetest/irrlicht\n"
+ "For example use: git clone --depth=1 https://github.com/minetest/irrlicht lib/irrlichtmt\n")
+ if(BUILD_CLIENT)
+ message(FATAL_ERROR "IrrlichtMt is required to build the client, but it was not found.\n${explanation_msg}")
+ endif()
+
+ include(MinetestFindIrrlichtHeaders)
+ if(NOT IRRLICHT_INCLUDE_DIR)
+ message(FATAL_ERROR "Irrlicht or IrrlichtMt headers are required to build the server, but none found.\n${explanation_msg}")
+ endif()
+ message(STATUS "Found Irrlicht headers: ${IRRLICHT_INCLUDE_DIR}")
+ add_library(IrrlichtMt::IrrlichtMt INTERFACE IMPORTED)
+ # Note that we can't use target_include_directories() since that doesn't work for IMPORTED targets before CMake 3.11
+ set_target_properties(IrrlichtMt::IrrlichtMt PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES "${IRRLICHT_INCLUDE_DIR}")
+ endif()
+endif()
+
+if(TARGET IrrlichtMt::IrrlichtMt)
+ message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}")
+endif()
# Installation
@@ -91,15 +149,16 @@ elseif(UNIX) # Linux, BSD etc
set(ICONDIR "unix/icons")
set(LOCALEDIR "locale")
else()
- set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}")
- set(BINDIR "${CMAKE_INSTALL_PREFIX}/bin")
- set(DOCDIR "${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME}")
- set(MANDIR "${CMAKE_INSTALL_PREFIX}/share/man")
+ include(GNUInstallDirs)
+ set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}")
+ set(BINDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}")
+ set(DOCDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DOCDIR}")
+ set(MANDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}")
set(EXAMPLE_CONF_DIR ${DOCDIR})
- set(XDG_APPS_DIR "${CMAKE_INSTALL_PREFIX}/share/applications")
- set(APPDATADIR "${CMAKE_INSTALL_PREFIX}/share/metainfo")
- set(ICONDIR "${CMAKE_INSTALL_PREFIX}/share/icons")
- set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/share/locale")
+ set(XDG_APPS_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/applications")
+ set(APPDATADIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/metainfo")
+ set(ICONDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/icons")
+ set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LOCALEDIR}")
endif()
endif()
@@ -169,7 +228,6 @@ install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/games/devtest" DESTINATION "${SHA
if(BUILD_CLIENT)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/client/shaders" DESTINATION "${SHAREDIR}/client")
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/textures/base/pack" DESTINATION "${SHAREDIR}/textures/base")
- install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/fonts" DESTINATION "${SHAREDIR}")
if(RUN_IN_PLACE)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/clientmods" DESTINATION "${SHAREDIR}")
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/client/serverlist" DESTINATION "${SHAREDIR}/client")
@@ -203,10 +261,10 @@ endif()
find_package(GMP REQUIRED)
find_package(Json REQUIRED)
find_package(Lua REQUIRED)
-
-# JsonCPP doesn't compile well on GCC 4.8
-if(NOT ENABLE_SYSTEM_JSONCPP)
- set(GCC_MINIMUM_VERSION "4.9")
+if(NOT USE_LUAJIT)
+ set(LUA_BIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/bitop)
+ set(LUA_BIT_LIBRARY bitop)
+ add_subdirectory(lib/bitop)
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
@@ -214,7 +272,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
message(FATAL_ERROR "Insufficient gcc version, found ${CMAKE_CXX_COMPILER_VERSION}. "
"Version ${GCC_MINIMUM_VERSION} or higher is required.")
endif()
-elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+elseif(CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?Clang")
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${CLANG_MINIMUM_VERSION}")
message(FATAL_ERROR "Insufficient clang version, found ${CMAKE_CXX_COMPILER_VERSION}. "
"Version ${CLANG_MINIMUM_VERSION} or higher is required.")
@@ -223,7 +281,6 @@ endif()
# Subdirectories
# Be sure to add all relevant definitions above this
-
add_subdirectory(src)
diff --git a/Dockerfile b/Dockerfile
index 871ca9825..8d1008fa2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,8 @@
-FROM alpine:3.11
+ARG DOCKER_IMAGE=alpine:3.14
+FROM $DOCKER_IMAGE AS builder
ENV MINETEST_GAME_VERSION master
+ENV IRRLICHT_VERSION master
COPY .git /usr/src/minetest/.git
COPY CMakeLists.txt /usr/src/minetest/CMakeLists.txt
@@ -18,10 +20,8 @@ COPY textures /usr/src/minetest/textures
WORKDIR /usr/src/minetest
-RUN apk add --no-cache git build-base irrlicht-dev cmake bzip2-dev libpng-dev \
- jpeg-dev libxxf86vm-dev mesa-dev sqlite-dev libogg-dev \
- libvorbis-dev openal-soft-dev curl-dev freetype-dev zlib-dev \
- gmp-dev jsoncpp-dev postgresql-dev luajit-dev ca-certificates && \
+RUN apk add --no-cache git build-base cmake sqlite-dev curl-dev zlib-dev zstd-dev \
+ gmp-dev jsoncpp-dev postgresql-dev ninja luajit-dev ca-certificates && \
git clone --depth=1 -b ${MINETEST_GAME_VERSION} https://github.com/minetest/minetest_game.git ./games/minetest_game && \
rm -fr ./games/minetest_game/.git
@@ -32,9 +32,13 @@ RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \
cmake .. \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DCMAKE_BUILD_TYPE=Release \
- -DENABLE_TESTING=0 && \
- make -j2 && \
- make install
+ -DENABLE_TESTING=0 \
+ -GNinja && \
+ ninja && \
+ ninja install
+
+RUN git clone --depth=1 https://github.com/minetest/irrlicht/ -b ${IRRLICHT_VERSION} && \
+ cp -r irrlicht/include /usr/include/irrlichtmt
WORKDIR /usr/src/minetest
RUN mkdir build && \
@@ -45,21 +49,23 @@ RUN mkdir build && \
-DBUILD_SERVER=TRUE \
-DENABLE_PROMETHEUS=TRUE \
-DBUILD_UNITTESTS=FALSE \
- -DBUILD_CLIENT=FALSE && \
- make -j2 && \
- make install
+ -DBUILD_CLIENT=FALSE \
+ -GNinja && \
+ ninja && \
+ ninja install
-FROM alpine:3.11
+ARG DOCKER_IMAGE=alpine:3.14
+FROM $DOCKER_IMAGE AS runtime
-RUN apk add --no-cache sqlite-libs curl gmp libstdc++ libgcc libpq luajit && \
+RUN apk add --no-cache sqlite-libs curl gmp libstdc++ libgcc libpq luajit jsoncpp zstd-libs && \
adduser -D minetest --uid 30000 -h /var/lib/minetest && \
chown -R minetest:minetest /var/lib/minetest
WORKDIR /var/lib/minetest
-COPY --from=0 /usr/local/share/minetest /usr/local/share/minetest
-COPY --from=0 /usr/local/bin/minetestserver /usr/local/bin/minetestserver
-COPY --from=0 /usr/local/share/doc/minetest/minetest.conf.example /etc/minetest/minetest.conf
+COPY --from=builder /usr/local/share/minetest /usr/local/share/minetest
+COPY --from=builder /usr/local/bin/minetestserver /usr/local/bin/minetestserver
+COPY --from=builder /usr/local/share/doc/minetest/minetest.conf.example /etc/minetest/minetest.conf
USER minetest:minetest
diff --git a/LICENSE.txt b/LICENSE.txt
index 9b8ee851a..ab44488a7 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -14,6 +14,9 @@ https://www.apache.org/licenses/LICENSE-2.0.html
Textures by Zughy are under CC BY-SA 4.0
https://creativecommons.org/licenses/by-sa/4.0/
+textures/base/pack/server_public.png is under CC-BY 4.0, taken from Twitter's Twemoji set
+https://creativecommons.org/licenses/by/4.0/
+
Authors of media files
-----------------------
Everything not listed in here:
@@ -39,10 +42,10 @@ erlehmann:
misc/minetest.svg
textures/base/pack/logo.png
-JRottm
+JRottm:
textures/base/pack/player_marker.png
-srifqi
+srifqi:
textures/base/pack/chat_hide_btn.png
textures/base/pack/chat_show_btn.png
textures/base/pack/joystick_bg.png
@@ -58,6 +61,9 @@ Zughy:
textures/base/pack/cdb_update.png
textures/base/pack/cdb_viewonline.png
+appgurueu:
+ textures/base/pack/server_incompatible.png
+
License of Minetest source code
-------------------------------
@@ -81,7 +87,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Irrlicht
---------------
-This program uses the Irrlicht Engine. http://irrlicht.sourceforge.net/
+This program uses IrrlichtMt, Minetest's fork of
+the Irrlicht Engine. http://irrlicht.sourceforge.net/
The Irrlicht Engine License
diff --git a/README.md b/README.md
index 58ec0c821..b3d2981f6 100644
--- a/README.md
+++ b/README.md
@@ -68,7 +68,7 @@ Some can be changed in the key config dialog in the settings tab.
| P | Enable/disable pitch move mode |
| J | Enable/disable fast mode (needs fast privilege) |
| H | Enable/disable noclip mode (needs noclip privilege) |
-| E | Move fast in fast mode |
+| E | Aux1 (Move fast in fast mode. Games may add special features) |
| C | Cycle through camera modes |
| V | Cycle through minimap modes |
| Shift + V | Change minimap orientation |
@@ -132,29 +132,31 @@ Compiling
| Dependency | Version | Commentary |
|------------|---------|------------|
-| GCC | 4.9+ | Can be replaced with Clang 3.4+ |
-| CMake | 2.6+ | |
-| Irrlicht | 1.7.3+ | |
-| SQLite3 | 3.0+ | |
+| GCC | 5.1+ | or Clang 3.5+ |
+| CMake | 3.5+ | |
+| IrrlichtMt | - | Custom version of Irrlicht, see https://github.com/minetest/irrlicht |
+| Freetype | 2.0+ | |
+| SQLite3 | 3+ | |
+| Zstd | 1.0+ | |
| LuaJIT | 2.0+ | Bundled Lua 5.1 is used if not present |
| GMP | 5.0.0+ | Bundled mini-GMP is used if not present |
| JsonCPP | 1.0.0+ | Bundled JsonCPP is used if not present |
For Debian/Ubuntu users:
- sudo apt install g++ make libc6-dev libirrlicht-dev cmake libbz2-dev libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev
+ sudo apt install g++ make libc6-dev cmake libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev libluajit-5.1-dev
For Fedora users:
- sudo dnf install make automake gcc gcc-c++ kernel-devel cmake libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel irrlicht-devel bzip2-libs gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel doxygen spatialindex-devel bzip2-devel
-
+ sudo dnf install make automake gcc gcc-c++ kernel-devel cmake libcurl-devel openal-soft-devel libvorbis-devel libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel
+
For Arch users:
- sudo pacman -S base-devel libcurl-gnutls cmake libxxf86vm irrlicht libpng sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses
+ sudo pacman -S base-devel libcurl-gnutls cmake libxxf86vm libpng sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses zstd
For Alpine users:
- sudo apk add build-base irrlicht-dev cmake bzip2-dev libpng-dev jpeg-dev libxxf86vm-dev mesa-dev sqlite-dev libogg-dev libvorbis-dev openal-soft-dev curl-dev freetype-dev zlib-dev gmp-dev jsoncpp-dev luajit-dev
+ sudo apk add build-base cmake libpng-dev jpeg-dev libxxf86vm-dev mesa-dev sqlite-dev libogg-dev libvorbis-dev openal-soft-dev curl-dev freetype-dev zlib-dev gmp-dev jsoncpp-dev luajit-dev zstd-dev
#### Download
@@ -177,6 +179,10 @@ Download minetest_game (otherwise only the "Development Test" game is available)
git clone --depth 1 https://github.com/minetest/minetest_game.git games/minetest_game
+Download IrrlichtMt to `lib/irrlichtmt`, it will be used to satisfy the IrrlichtMt dependency that way:
+
+ git clone --depth 1 https://github.com/minetest/irrlicht.git lib/irrlichtmt
+
Download source, without using Git:
wget https://github.com/minetest/minetest/archive/master.tar.gz
@@ -191,6 +197,14 @@ Download minetest_game, without using Git:
mv minetest_game-master minetest_game
cd ..
+Download IrrlichtMt, without using Git:
+
+ cd lib/
+ wget https://github.com/minetest/irrlicht/archive/master.tar.gz
+ tar xf master.tar.gz
+ mv irrlicht-master irrlichtmt
+ cd ..
+
#### Build
Build a version that runs directly from the source directory:
@@ -209,8 +223,15 @@ Run it:
- 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`.
+- If you build a bare server you don't need to have the Irrlicht or IrrlichtMt library installed.
+ - In that case use `-DIRRLICHT_INCLUDE_DIR=/some/where/irrlicht/include`.
+
+- Minetest will use the IrrlichtMt package that is found first, given by the following order:
+ 1. Specified `IRRLICHTMT_BUILD_DIR` CMake variable
+ 2. `${PROJECT_SOURCE_DIR}/lib/irrlichtmt` (if existent)
+ 3. Installation of IrrlichtMt in the system-specific library paths
+ 4. For server builds with disabled `BUILD_CLIENT` variable, the headers from `IRRLICHT_INCLUDE_DIR` will be used.
+ - NOTE: Changing the IrrlichtMt build directory (includes system installs) requires regenerating the CMake cache (`rm CMakeCache.txt`)
### CMake options
@@ -227,9 +248,8 @@ General options and their default values:
MinSizeRel - Release build with -Os passed to compiler to make executable as small as possible
ENABLE_CURL=ON - Build with cURL; Enables use of online mod repo, public serverlist and remote media fetching via http
ENABLE_CURSES=ON - Build with (n)curses; Enables a server side terminal (command line option: --terminal)
- ENABLE_FREETYPE=ON - Build with FreeType2; Allows using TTF fonts
ENABLE_GETTEXT=ON - Build with Gettext; Allows using translations
- ENABLE_GLES=OFF - Build for OpenGL ES instead of OpenGL (requires support by Irrlicht)
+ ENABLE_GLES=OFF - Build for OpenGL ES instead of OpenGL (requires support by IrrlichtMt)
ENABLE_LEVELDB=ON - Build with LevelDB; Enables use of LevelDB map backend
ENABLE_POSTGRESQL=ON - Build with libpq; Enables use of PostgreSQL map backend (PostgreSQL 9.5 or greater recommended)
ENABLE_REDIS=ON - Build with libhiredis; Enables use of Redis map backend
@@ -238,33 +258,31 @@ General options and their default values:
ENABLE_LUAJIT=ON - Build with LuaJIT (much faster than non-JIT Lua)
ENABLE_PROMETHEUS=OFF - Build with Prometheus metrics exporter (listens on tcp/30000 by default)
ENABLE_SYSTEM_GMP=ON - Use GMP from system (much faster than bundled mini-gmp)
- ENABLE_SYSTEM_JSONCPP=OFF - Use JsonCPP from system
+ ENABLE_SYSTEM_JSONCPP=ON - Use JsonCPP from system
OPENGL_GL_PREFERENCE=LEGACY - Linux client build only; See CMake Policy CMP0072 for reference
RUN_IN_PLACE=FALSE - Create a portable install (worlds, settings etc. in current directory)
USE_GPROF=FALSE - Enable profiling using GProf
VERSION_EXTRA= - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar)
+ ENABLE_TOUCH=FALSE - Enable Touchscreen support (requires support by IrrlichtMt)
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 FreeType 2; directory that contains an freetype directory with files such as ftimage.h in it
- FREETYPE_INCLUDE_DIR_ft2build - Only if building with FreeType 2; directory that contains ft2build.h
- FREETYPE_LIBRARY - Only if building with FreeType 2; path to libfreetype.a/libfreetype.so/freetype.lib
- FREETYPE_DLL - Only if building with FreeType 2 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
+ EXTRA_DLL - Only on Windows; optional paths to additional DLLs that should be packaged
+ FREETYPE_INCLUDE_DIR_freetype2 - Directory that contains files such as ftimage.h
+ FREETYPE_INCLUDE_DIR_ft2build - Directory that contains ft2build.h
+ FREETYPE_LIBRARY - Path to libfreetype.a/libfreetype.so/freetype.lib
+ FREETYPE_DLL - Only on Windows; path to libfreetype-6.dll
+ GETTEXT_DLL - Only when building with gettext on Windows; paths to libintl + libiconv DLLs
GETTEXT_INCLUDE_DIR - Only when building with gettext; directory that contains iconv.h
GETTEXT_LIBRARY - Only when building with gettext on Windows; path to libintl.dll.a
GETTEXT_MSGFMT - Only when building with gettext; path to msgfmt/msgfmt.exe
- IRRLICHT_DLL - Only on Windows; path to Irrlicht.dll
- IRRLICHT_INCLUDE_DIR - Directory that contains IrrCompileConfig.h
- IRRLICHT_LIBRARY - Path to libIrrlicht.a/libIrrlicht.so/libIrrlicht.dll.a/Irrlicht.lib
+ IRRLICHT_DLL - Only on Windows; path to IrrlichtMt.dll
+ IRRLICHT_INCLUDE_DIR - Directory that contains IrrCompileConfig.h (usable for server build only)
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
@@ -276,7 +294,6 @@ Library specific options:
SPATIAL_LIBRARY - Only when building with LibSpatial; path to libspatialindex_c.so/spatialindex-32.lib
LUA_INCLUDE_DIR - Only if you want to use LuaJIT; directory where luajit.h is located
LUA_LIBRARY - Only if you want to use LuaJIT; path to libluajit.a/libluajit.so
- MINGWM10_DLL - Only if compiling with MinGW; path to mingwm10.dll
OGG_DLL - Only if building with sound on Windows; path to libogg.dll
OGG_INCLUDE_DIR - Only if building with sound; directory that contains an ogg directory which contains ogg.h
OGG_LIBRARY - Only if building with sound; path to libogg.a/libogg.so/libogg.dll.a
@@ -287,17 +304,19 @@ Library specific options:
OPENGLES2_LIBRARY - Only if building with GLES; path to libGLESv2.a/libGLESv2.so
SQLITE3_INCLUDE_DIR - Directory that contains sqlite3.h
SQLITE3_LIBRARY - Path to libsqlite3.a/libsqlite3.so/sqlite3.lib
- VORBISFILE_DLL - Only if building with sound on Windows; path to libvorbisfile-3.dll
VORBISFILE_LIBRARY - Only if building with sound; path to libvorbisfile.a/libvorbisfile.so/libvorbisfile.dll.a
- VORBIS_DLL - Only if building with sound on Windows; path to libvorbis-0.dll
+ VORBIS_DLL - Only if building with sound on Windows; paths to vorbis DLLs
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
ZLIB_INCLUDE_DIR - Directory that contains zlib.h
ZLIB_LIBRARY - Path to libz.a/libz.so/zlib.lib
+ ZSTD_DLL - Only on Windows; path to libzstd.dll
+ ZSTD_INCLUDE_DIR - Directory that contains zstd.h
+ ZSTD_LIBRARY - Path to libzstd.a/libzstd.so/ztd.lib
-### Compiling on Windows
+### Compiling on Windows using MSVC
### Requirements
@@ -310,16 +329,14 @@ Library specific options:
It is highly recommended to use vcpkg as package manager.
-#### a) Using vcpkg to install dependencies
-
After you successfully built vcpkg you can easily install the required libraries:
```powershell
-vcpkg install irrlicht zlib curl[winssl] openal-soft libvorbis libogg sqlite3 freetype luajit gmp jsoncpp --triplet x64-windows
+vcpkg install zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry --triplet x64-windows
```
+- **Don't forget about IrrlichtMt.** The easiest way is to clone it to `lib/irrlichtmt` as described in the Linux section.
- `curl` is optional, but required to read the serverlist, `curl[winssl]` is required to use the content store.
- `openal-soft`, `libvorbis` and `libogg` are optional, but required to use sound.
-- `freetype` is optional, it allows true-type font rendering.
- `luajit` is optional, it replaces the integrated Lua interpreter with a faster just-in-time interpreter.
- `gmp` and `jsoncpp` are optional, otherwise the bundled versions will be compiled
@@ -327,10 +344,6 @@ There are other optional libraries, but they are not tested if they can build an
Use `--triplet` to specify the target triplet, e.g. `x64-windows` or `x86-windows`.
-#### b) Compile the dependencies on your own
-
-This is outdated and not recommended. Follow the instructions on https://dev.minetest.net/Build_Win32_Minetest_including_all_required_libraries#VS2012_Build
-
### Compile Minetest
#### a) Using the vcpkg toolchain and CMake GUI
@@ -354,17 +367,11 @@ This is outdated and not recommended. Follow the instructions on https://dev.min
Run the following script in PowerShell:
```powershell
-cmake . -G"Visual Studio 15 2017 Win64" -DCMAKE_TOOLCHAIN_FILE=D:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_GETTEXT=OFF -DENABLE_CURSES=OFF -DENABLE_SYSTEM_JSONCPP=ON
+cmake . -G"Visual Studio 15 2017 Win64" -DCMAKE_TOOLCHAIN_FILE=D:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_GETTEXT=OFF -DENABLE_CURSES=OFF
cmake --build . --config Release
```
Make sure that the right compiler is selected and the path to the vcpkg toolchain is correct.
-#### c) Using your own compiled libraries
-
-**This is outdated and not recommended**
-
-Follow the instructions on https://dev.minetest.net/Build_Win32_Minetest_including_all_required_libraries#VS2012_Build
-
### Windows Installer using WiX Toolset
Requirements:
@@ -378,6 +385,60 @@ Build the binaries as described above, but make sure you unselect `RUN_IN_PLACE`
Open the generated project file with Visual Studio. Right-click **Package** and choose **Generate**.
It may take some minutes to generate the installer.
+### Compiling on MacOS
+
+#### Requirements
+- [Homebrew](https://brew.sh/)
+- [Git](https://git-scm.com/downloads)
+
+Install dependencies with homebrew:
+
+```
+brew install cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd
+```
+
+#### Download
+
+Download source (this is the URL to the latest of source repository, which might not work at all times) using Git:
+
+```bash
+git clone --depth 1 https://github.com/minetest/minetest.git
+cd minetest
+```
+
+Download minetest_game (otherwise only the "Development Test" game is available) using Git:
+
+```
+git clone --depth 1 https://github.com/minetest/minetest_game.git games/minetest_game
+```
+
+Download Minetest's fork of Irrlicht:
+
+```
+git clone --depth 1 https://github.com/minetest/irrlicht.git lib/irrlichtmt
+```
+
+#### Build
+
+```bash
+mkdir cmakebuild
+cd cmakebuild
+
+cmake .. \
+ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \
+ -DCMAKE_FIND_FRAMEWORK=LAST \
+ -DCMAKE_INSTALL_PREFIX=../build/macos/ \
+ -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE
+
+make -j$(nproc)
+make install
+```
+
+#### Run
+
+```
+open ./build/macos/minetest.app
+```
Docker
------
diff --git a/android/app/build.gradle b/android/app/build.gradle
index b7d93ef0f..e8ba95722 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -1,12 +1,12 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 29
+ compileSdkVersion 30
buildToolsVersion '30.0.3'
- ndkVersion '22.0.7026061'
+ ndkVersion "$ndk_version"
defaultConfig {
applicationId 'net.minetest.minetest'
minSdkVersion 16
- targetSdkVersion 29
+ targetSdkVersion 30
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
versionCode project.versionCode
}
@@ -68,7 +68,7 @@ task prepareAssets() {
from "${projRoot}/client/shaders" into "${assetsFolder}/client/shaders"
}
copy {
- from "../native/deps/Android/Irrlicht/shaders" into "${assetsFolder}/client/shaders/Irrlicht"
+ from "../native/deps/armeabi-v7a/Irrlicht/Shaders" into "${assetsFolder}/client/shaders/Irrlicht"
}
copy {
from "${projRoot}/fonts" include "*.ttf" into "${assetsFolder}/fonts"
@@ -76,10 +76,13 @@ task prepareAssets() {
copy {
from "${projRoot}/games/${gameToCopy}" into "${assetsFolder}/games/${gameToCopy}"
}
- /*copy {
- // ToDo: fix broken locales
- from "${projRoot}/po" into "${assetsFolder}/po"
- }*/
+ fileTree("${projRoot}/po").include("**/*.po").forEach { poFile ->
+ def moPath = "${assetsFolder}/locale/${poFile.parentFile.name}/LC_MESSAGES/"
+ file(moPath).mkdirs()
+ exec {
+ commandLine 'msgfmt', '-o', "${moPath}/minetest.mo", poFile
+ }
+ }
copy {
from "${projRoot}/textures" into "${assetsFolder}/textures"
}
@@ -109,5 +112,5 @@ android.applicationVariants.all { variant ->
dependencies {
implementation project(':native')
- implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation 'androidx.appcompat:appcompat:1.3.1'
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 314a38b5c..6ea677cb9 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -19,8 +19,6 @@
android:label="@string/label"
android:requestLegacyExternalStorage="true"
android:resizeableActivity="false"
- android:hasFragileUserData="true"
- android:isGame="true"
tools:ignore="UnusedAttribute">
<meta-data
@@ -32,7 +30,8 @@
android:configChanges="orientation|keyboardHidden|navigation|screenSize"
android:maxAspectRatio="3.0"
android:screenOrientation="sensorLandscape"
- android:theme="@style/AppTheme">
+ android:theme="@style/AppTheme"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -46,7 +45,8 @@
android:launchMode="singleTask"
android:maxAspectRatio="3.0"
android:screenOrientation="sensorLandscape"
- android:theme="@style/AppTheme">
+ android:theme="@style/AppTheme"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
diff --git a/android/app/src/main/java/net/minetest/minetest/GameActivity.java b/android/app/src/main/java/net/minetest/minetest/GameActivity.java
index dc2e564be..46fc9b1de 100644
--- a/android/app/src/main/java/net/minetest/minetest/GameActivity.java
+++ b/android/app/src/main/java/net/minetest/minetest/GameActivity.java
@@ -30,7 +30,9 @@ import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
import android.widget.EditText;
+import android.widget.LinearLayout;
import androidx.appcompat.app.AlertDialog;
@@ -85,9 +87,19 @@ public class GameActivity extends NativeActivity {
private void showDialogUI(String hint, String current, int editType) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
- EditText editText = new CustomEditText(this);
- builder.setView(editText);
+ LinearLayout container = new LinearLayout(this);
+ container.setOrientation(LinearLayout.VERTICAL);
+ builder.setView(container);
AlertDialog alertDialog = builder.create();
+ EditText editText;
+ // For multi-line, do not close the dialog after pressing back button
+ if (editType == 1) {
+ editText = new EditText(this);
+ } else {
+ editText = new CustomEditText(this);
+ }
+ container.addView(editText);
+ editText.setMaxLines(8);
editText.requestFocus();
editText.setHint(hint);
editText.setText(current);
@@ -103,8 +115,9 @@ public class GameActivity extends NativeActivity {
else
editText.setInputType(InputType.TYPE_CLASS_TEXT);
editText.setSelection(editText.getText().length());
- editText.setOnKeyListener((view, KeyCode, event) -> {
- if (KeyCode == KeyEvent.KEYCODE_ENTER) {
+ editText.setOnKeyListener((view, keyCode, event) -> {
+ // For multi-line, do not submit the text after pressing Enter key
+ if (keyCode == KeyEvent.KEYCODE_ENTER && editType != 1) {
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
messageReturnCode = 0;
messageReturnValue = editText.getText().toString();
@@ -113,6 +126,18 @@ public class GameActivity extends NativeActivity {
}
return false;
});
+ // For multi-line, add Done button since Enter key does not submit text
+ if (editType == 1) {
+ Button doneButton = new Button(this);
+ container.addView(doneButton);
+ doneButton.setText(R.string.ime_dialog_done);
+ doneButton.setOnClickListener((view -> {
+ imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
+ messageReturnCode = 0;
+ messageReturnValue = editText.getText().toString();
+ alertDialog.dismiss();
+ }));
+ }
alertDialog.show();
alertDialog.setOnCancelListener(dialog -> {
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
diff --git a/android/app/src/main/java/net/minetest/minetest/MainActivity.java b/android/app/src/main/java/net/minetest/minetest/MainActivity.java
index 56615fca7..b6567b4b7 100644
--- a/android/app/src/main/java/net/minetest/minetest/MainActivity.java
+++ b/android/app/src/main/java/net/minetest/minetest/MainActivity.java
@@ -101,7 +101,8 @@ public class MainActivity extends AppCompatActivity {
mTextView = findViewById(R.id.textView);
sharedPreferences = getSharedPreferences(SETTINGS, Context.MODE_PRIVATE);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
+ Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
checkPermission();
else
checkAppVersion();
diff --git a/android/app/src/main/java/net/minetest/minetest/UnzipService.java b/android/app/src/main/java/net/minetest/minetest/UnzipService.java
index b513a7fe0..a61a49139 100644
--- a/android/app/src/main/java/net/minetest/minetest/UnzipService.java
+++ b/android/app/src/main/java/net/minetest/minetest/UnzipService.java
@@ -32,6 +32,7 @@ import android.os.Environment;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import java.io.File;
@@ -200,6 +201,10 @@ public class UnzipService extends IntentService {
* Migrates user data from deprecated external storage to app scoped storage
*/
private void migrate(Notification.Builder notificationBuilder, File newLocation) throws IOException {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ return;
+ }
+
File oldLocation = new File(Environment.getExternalStorageDirectory(), "Minetest");
if (!oldLocation.isDirectory())
return;
diff --git a/android/build.gradle b/android/build.gradle
index b3118af21..71e995e48 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,21 +1,22 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
project.ext.set("versionMajor", 5) // Version Major
-project.ext.set("versionMinor", 4) // Version Minor
-project.ext.set("versionPatch", 2) // Version Patch
+project.ext.set("versionMinor", 5) // Version Minor
+project.ext.set("versionPatch", 0) // Version Patch
project.ext.set("versionExtra", "") // Version Extra
-project.ext.set("versionCode", 36) // Android Version Code
+project.ext.set("versionCode", 38) // Android Version Code
// NOTE: +2 after each release!
// +1 for ARM and +1 for ARM64 APK's, because
// each APK must have a larger `versionCode` than the previous
buildscript {
+ ext.ndk_version = '23.0.7599858'
repositories {
google()
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.1.1'
+ classpath 'com.android.tools.build:gradle:7.0.3'
classpath 'de.undercouch:gradle-download-task:4.1.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index 7fd9307d7..8ad73a75c 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,5 @@
-#Fri Jan 08 17:52:00 UTC 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
diff --git a/android/gradlew b/android/gradlew
index 83f2acfdc..25e0c1148 100755
--- a/android/gradlew
+++ b/android/gradlew
@@ -98,7 +98,7 @@ location of your Java installation."
fi
else
JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ command -v java >/dev/null || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
diff --git a/android/icons/aux_btn.svg b/android/icons/aux_btn.svg
deleted file mode 100644
index 6bbefff67..000000000
--- a/android/icons/aux_btn.svg
+++ /dev/null
@@ -1,411 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="512"
- height="512"
- viewBox="0 0 135.46666 135.46667"
- version="1.1"
- id="svg8"
- inkscape:version="0.92.1 r15371"
- sodipodi:docname="aux_btn.svg"
- inkscape:export-filename="/home/stu/Desktop/icons/png/aux_btn.png"
- inkscape:export-xdpi="24.000002"
- inkscape:export-ydpi="24.000002">
- <defs
- id="defs2">
- <filter
- style="color-interpolation-filters:sRGB;"
- inkscape:label="Colorize"
- id="filter4628">
- <feComposite
- in2="SourceGraphic"
- operator="arithmetic"
- k1="0"
- k2="1"
- result="composite1"
- id="feComposite4614" />
- <feColorMatrix
- in="composite1"
- values="1"
- type="saturate"
- result="colormatrix1"
- id="feColorMatrix4616" />
- <feFlood
- flood-opacity="1"
- flood-color="rgb(158,0,0)"
- result="flood1"
- id="feFlood4618" />
- <feBlend
- in="flood1"
- in2="colormatrix1"
- mode="multiply"
- result="blend1"
- id="feBlend4620" />
- <feBlend
- in2="blend1"
- mode="screen"
- result="blend2"
- id="feBlend4622" />
- <feColorMatrix
- in="blend2"
- values="1"
- type="saturate"
- result="colormatrix2"
- id="feColorMatrix4624" />
- <feComposite
- in="colormatrix2"
- in2="SourceGraphic"
- operator="in"
- k2="1"
- result="composite2"
- id="feComposite4626" />
- </filter>
- <filter
- style="color-interpolation-filters:sRGB;"
- inkscape:label="Sharpen More"
- id="filter5109"
- inkscape:menu="Image Effects"
- inkscape:menu-tooltip="Sharpen edges and boundaries within the object, force=0.3">
- <feComposite
- in2="SourceGraphic"
- operator="arithmetic"
- k1="0"
- k2="1"
- result="composite1"
- id="feComposite5095" />
- <feColorMatrix
- in="composite1"
- values="1"
- type="saturate"
- result="colormatrix1"
- id="feColorMatrix5097" />
- <feFlood
- flood-opacity="1"
- flood-color="rgb(158,67,0)"
- result="flood1"
- id="feFlood5099" />
- <feBlend
- in="flood1"
- in2="colormatrix1"
- mode="multiply"
- result="blend1"
- id="feBlend5101" />
- <feBlend
- in2="blend1"
- mode="screen"
- result="blend2"
- id="feBlend5103" />
- <feColorMatrix
- in="blend2"
- values="1"
- type="saturate"
- result="colormatrix2"
- id="feColorMatrix5105" />
- <feComposite
- in="colormatrix2"
- in2="SourceGraphic"
- operator="in"
- k2="1"
- result="fbSourceGraphic"
- id="feComposite5107" />
- <feColorMatrix
- result="fbSourceGraphicAlpha"
- in="fbSourceGraphic"
- values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
- id="feColorMatrix5111" />
- <feComposite
- in2="fbSourceGraphic"
- id="feComposite5113"
- operator="arithmetic"
- k1="0"
- k2="1"
- result="composite1"
- in="fbSourceGraphic" />
- <feColorMatrix
- id="feColorMatrix5115"
- in="composite1"
- values="1"
- type="saturate"
- result="colormatrix1" />
- <feFlood
- id="feFlood5117"
- flood-opacity="1"
- flood-color="rgb(158,0,0)"
- result="flood1" />
- <feBlend
- in2="colormatrix1"
- id="feBlend5119"
- in="flood1"
- mode="multiply"
- result="blend1" />
- <feBlend
- in2="blend1"
- id="feBlend5121"
- mode="screen"
- result="blend2" />
- <feColorMatrix
- id="feColorMatrix5123"
- in="blend2"
- values="1"
- type="saturate"
- result="colormatrix2" />
- <feComposite
- in2="fbSourceGraphic"
- id="feComposite5125"
- in="colormatrix2"
- operator="in"
- k2="1"
- result="fbSourceGraphic" />
- <feColorMatrix
- result="fbSourceGraphicAlpha"
- in="fbSourceGraphic"
- values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
- id="feColorMatrix7007" />
- <feConvolveMatrix
- id="feConvolveMatrix7009"
- order="3 3"
- kernelMatrix="0 -0.15 0 -0.15 1.6 -0.15 0 -0.15 0"
- divisor="1"
- in="fbSourceGraphic"
- targetX="1"
- targetY="1"
- result="fbSourceGraphic" />
- <feColorMatrix
- result="fbSourceGraphicAlpha"
- in="fbSourceGraphic"
- values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
- id="feColorMatrix7011" />
- <feConvolveMatrix
- id="feConvolveMatrix7013"
- targetY="1"
- targetX="1"
- in="fbSourceGraphic"
- divisor="1"
- kernelMatrix="0 -0.3 0 -0.3 2.2 -0.3 0 -0.3 0"
- order="3 3"
- result="result1" />
- <feBlend
- in2="fbSourceGraphic"
- id="feBlend7015"
- mode="normal"
- result="result2" />
- </filter>
- <marker
- style="overflow:visible"
- refY="0.0"
- refX="0.0"
- orient="auto"
- id="DistanceX">
- <path
- id="path7410"
- style="stroke:#000000; stroke-width:0.5"
- d="M 3,-3 L -3,3 M 0,-5 L 0,5" />
- </marker>
- <pattern
- y="0"
- x="0"
- width="8"
- patternUnits="userSpaceOnUse"
- id="Hatch"
- height="8">
- <path
- id="path7413"
- stroke-width="0.25"
- stroke="#000000"
- linecap="square"
- d="M8 4 l-4,4" />
- <path
- id="path7415"
- stroke-width="0.25"
- stroke="#000000"
- linecap="square"
- d="M6 2 l-4,4" />
- <path
- id="path7417"
- stroke-width="0.25"
- stroke="#000000"
- linecap="square"
- d="M4 0 l-4,4" />
- </pattern>
- <symbol
- id="*Model_Space" />
- <symbol
- id="*Paper_Space" />
- <symbol
- id="*Paper_Space0" />
- <filter
- style="color-interpolation-filters:sRGB;"
- inkscape:label="Colorize"
- id="filter4883">
- <feComposite
- in2="SourceGraphic"
- operator="arithmetic"
- k1="0"
- k2="1"
- result="composite1"
- id="feComposite4869" />
- <feColorMatrix
- in="composite1"
- values="1"
- type="saturate"
- result="colormatrix1"
- id="feColorMatrix4871" />
- <feFlood
- flood-opacity="1"
- flood-color="rgb(158,21,0)"
- result="flood1"
- id="feFlood4873" />
- <feBlend
- in="flood1"
- in2="colormatrix1"
- mode="multiply"
- result="blend1"
- id="feBlend4875" />
- <feBlend
- in2="blend1"
- mode="screen"
- result="blend2"
- id="feBlend4877" />
- <feColorMatrix
- in="blend2"
- values="1"
- type="saturate"
- result="colormatrix2"
- id="feColorMatrix4879" />
- <feComposite
- in="colormatrix2"
- in2="SourceGraphic"
- operator="in"
- k2="1"
- result="composite2"
- id="feComposite4881" />
- </filter>
- </defs>
- <sodipodi:namedview
- id="base"
- pagecolor="#404040"
- bordercolor="#666666"
- borderopacity="1.0"
- inkscape:pageopacity="0"
- inkscape:pageshadow="2"
- inkscape:zoom="0.98994949"
- inkscape:cx="-341.34157"
- inkscape:cy="210.02973"
- inkscape:document-units="mm"
- inkscape:current-layer="layer2"
- showgrid="true"
- units="px"
- inkscape:window-width="1920"
- inkscape:window-height="1023"
- inkscape:window-x="0"
- inkscape:window-y="34"
- inkscape:window-maximized="1"
- inkscape:pagecheckerboard="false"
- inkscape:snap-grids="false"
- inkscape:snap-page="true"
- showguides="true"
- inkscape:snap-bbox="true"
- inkscape:snap-to-guides="true"
- inkscape:snap-object-midpoints="false"
- inkscape:snap-others="true"
- inkscape:snap-bbox-midpoints="true">
- <inkscape:grid
- type="xygrid"
- id="grid16"
- spacingx="0.26458333"
- spacingy="0.26458333"
- empspacing="4"
- color="#40ff40"
- opacity="0.1254902"
- empcolor="#40ff40"
- empopacity="0.25098039" />
- </sodipodi:namedview>
- <metadata
- id="metadata5">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- <cc:license
- rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
- </cc:Work>
- <cc:License
- rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
- <cc:permits
- rdf:resource="http://creativecommons.org/ns#Reproduction" />
- <cc:permits
- rdf:resource="http://creativecommons.org/ns#Distribution" />
- <cc:requires
- rdf:resource="http://creativecommons.org/ns#Notice" />
- <cc:requires
- rdf:resource="http://creativecommons.org/ns#Attribution" />
- <cc:permits
- rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
- <cc:requires
- rdf:resource="http://creativecommons.org/ns#ShareAlike" />
- </cc:License>
- </rdf:RDF>
- </metadata>
- <g
- inkscape:groupmode="layer"
- id="layer2"
- inkscape:label="Layer 2"
- style="display:inline">
- <path
- style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d=""
- id="path7055"
- inkscape:connector-curvature="0" />
- <path
- style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d=""
- id="path7035"
- inkscape:connector-curvature="0" />
- <path
- style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d=""
- id="path7005"
- inkscape:connector-curvature="0" />
- <path
- style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- d=""
- id="path5127"
- inkscape:connector-curvature="0" />
- <text
- xml:space="preserve"
- style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:48.47851181px;line-height:1.25;font-family:'Bitstream Vera Sans';-inkscape-font-specification:'Bitstream Vera Sans';text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#d9d9d9;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
- x="67.78315"
- y="85.59491"
- id="text4716"
- transform="scale(1.0078883,0.99217343)"><tspan
- sodipodi:role="line"
- id="tspan4714"
- x="67.78315"
- y="85.59491"
- style="fill:#d9d9d9;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">AUX</tspan></text>
- <flowRoot
- xml:space="preserve"
- id="flowRoot4718"
- style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;line-height:1.25;font-family:'Bitstream Vera Sans';-inkscape-font-specification:'Bitstream Vera Sans';text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:none;fill-opacity:1;stroke:#ffffff;stroke-opacity:1"
- transform="scale(0.26458333)"><flowRegion
- id="flowRegion4720"
- style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-opacity:1"><rect
- id="rect4722"
- width="157.5838"
- height="136.37059"
- x="264.65997"
- y="124.10143"
- style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-opacity:1" /></flowRegion><flowPara
- id="flowPara4724" /></flowRoot> </g>
-</svg>
diff --git a/android/native/build.gradle b/android/native/build.gradle
index d58a2dcc5..2ddc77135 100644
--- a/android/native/build.gradle
+++ b/android/native/build.gradle
@@ -2,12 +2,12 @@ apply plugin: 'com.android.library'
apply plugin: 'de.undercouch.download'
android {
- compileSdkVersion 29
+ compileSdkVersion 30
buildToolsVersion '30.0.3'
- ndkVersion '22.0.7026061'
+ ndkVersion "$ndk_version"
defaultConfig {
minSdkVersion 16
- targetSdkVersion 29
+ targetSdkVersion 30
externalNativeBuild {
ndkBuild {
arguments '-j' + Runtime.getRuntime().availableProcessors(),
@@ -50,54 +50,19 @@ android {
}
// get precompiled deps
-def folder = 'minetest_android_deps_binaries'
-def deps_ref = "342eb18b7512462585a33bc9eef0b68298087151"
-
task downloadDeps(type: Download) {
- src 'https://github.com/minetest/' + folder + '/archive/' + deps_ref + '.zip'
+ src 'https://github.com/minetest/minetest_android_deps/releases/download/latest/deps.zip'
dest new File(buildDir, 'deps.zip')
overwrite false
}
task getDeps(dependsOn: downloadDeps, type: Copy) {
- def deps = file('deps')
- def f = file("$buildDir/" + folder + "-" + deps_ref)
-
- if (!deps.exists() && !f.exists()) {
+ def deps = new File(buildDir.parent, 'deps')
+ if (!deps.exists()) {
+ deps.mkdir()
from zipTree(downloadDeps.dest)
- into buildDir
- }
-
- doLast {
- if (!deps.exists()) {
- file(f).renameTo(file(deps))
- }
- }
-}
-
-// get sqlite
-def sqlite_ver = '3340000'
-task downloadSqlite(dependsOn: getDeps, type: Download) {
- src 'https://www.sqlite.org/2020/sqlite-amalgamation-' + sqlite_ver + '.zip'
- dest new File(buildDir, 'sqlite.zip')
- overwrite false
-}
-
-task getSqlite(dependsOn: downloadSqlite, type: Copy) {
- def sqlite = file('deps/Android/sqlite')
- def f = file("$buildDir/sqlite-amalgamation-" + sqlite_ver)
-
- if (!sqlite.exists() && !f.exists()) {
- from zipTree(downloadSqlite.dest)
- into buildDir
- }
-
- doLast {
- if (!sqlite.exists()) {
- file(f).renameTo(file(sqlite))
- }
+ into deps
}
}
preBuild.dependsOn getDeps
-preBuild.dependsOn getSqlite
diff --git a/android/native/jni/Android.mk b/android/native/jni/Android.mk
index 73df04945..f8ca74d3c 100644
--- a/android/native/jni/Android.mk
+++ b/android/native/jni/Android.mk
@@ -4,64 +4,82 @@ LOCAL_PATH := $(call my-dir)/..
include $(CLEAR_VARS)
LOCAL_MODULE := Curl
-LOCAL_SRC_FILES := deps/Android/Curl/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libcurl.a
+LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libcurl.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
-LOCAL_MODULE := Freetype
-LOCAL_SRC_FILES := deps/Android/Freetype/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libfreetype.a
+LOCAL_MODULE := libmbedcrypto
+LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedcrypto.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
-LOCAL_MODULE := Irrlicht
-LOCAL_SRC_FILES := deps/Android/Irrlicht/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libIrrlicht.a
+LOCAL_MODULE := libmbedtls
+LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedtls.a
include $(PREBUILT_STATIC_LIBRARY)
-#include $(CLEAR_VARS)
-#LOCAL_MODULE := LevelDB
-#LOCAL_SRC_FILES := deps/Android/LevelDB/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libleveldb.a
-#include $(PREBUILT_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+LOCAL_MODULE := libmbedx509
+LOCAL_SRC_FILES := deps/$(APP_ABI)/Curl/libmbedx509.a
+include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
-LOCAL_MODULE := LuaJIT
-LOCAL_SRC_FILES := deps/Android/LuaJIT/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libluajit.a
+LOCAL_MODULE := Freetype
+LOCAL_SRC_FILES := deps/$(APP_ABI)/Freetype/libfreetype.a
+include $(PREBUILT_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := Iconv
+LOCAL_SRC_FILES := deps/$(APP_ABI)/Iconv/libiconv.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
-LOCAL_MODULE := mbedTLS
-LOCAL_SRC_FILES := deps/Android/mbedTLS/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libmbedtls.a
+LOCAL_MODULE := libcharset
+LOCAL_SRC_FILES := deps/$(APP_ABI)/Iconv/libcharset.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
-LOCAL_MODULE := mbedx509
-LOCAL_SRC_FILES := deps/Android/mbedTLS/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libmbedx509.a
+LOCAL_MODULE := Irrlicht
+LOCAL_SRC_FILES := deps/$(APP_ABI)/Irrlicht/libIrrlichtMt.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
-LOCAL_MODULE := mbedcrypto
-LOCAL_SRC_FILES := deps/Android/mbedTLS/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libmbedcrypto.a
+LOCAL_MODULE := LuaJIT
+LOCAL_SRC_FILES := deps/$(APP_ABI)/LuaJIT/libluajit.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := OpenAL
-LOCAL_SRC_FILES := deps/Android/OpenAL-Soft/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libopenal.a
+LOCAL_SRC_FILES := deps/$(APP_ABI)/OpenAL-Soft/libopenal.a
include $(PREBUILT_STATIC_LIBRARY)
-# You can use `OpenSSL and Crypto` instead `mbedTLS mbedx509 mbedcrypto`,
-#but it increase APK size on ~0.7MB
-#include $(CLEAR_VARS)
-#LOCAL_MODULE := OpenSSL
-#LOCAL_SRC_FILES := deps/Android/OpenSSL/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libssl.a
-#include $(PREBUILT_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+LOCAL_MODULE := Gettext
+LOCAL_SRC_FILES := deps/$(APP_ABI)/Gettext/libintl.a
+include $(PREBUILT_STATIC_LIBRARY)
-#include $(CLEAR_VARS)
-#LOCAL_MODULE := Crypto
-#LOCAL_SRC_FILES := deps/Android/OpenSSL/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libcrypto.a
-#include $(PREBUILT_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+LOCAL_MODULE := SQLite3
+LOCAL_SRC_FILES := deps/$(APP_ABI)/SQLite/libsqlite3.a
+include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := Vorbis
-LOCAL_SRC_FILES := deps/Android/Vorbis/${NDK_TOOLCHAIN_VERSION}/$(APP_ABI)/libvorbis.a
+LOCAL_SRC_FILES := deps/$(APP_ABI)/Vorbis/libvorbis.a
+include $(PREBUILT_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libvorbisfile
+LOCAL_SRC_FILES := deps/$(APP_ABI)/Vorbis/libvorbisfile.a
+include $(PREBUILT_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libogg
+LOCAL_SRC_FILES := deps/$(APP_ABI)/Vorbis/libogg.a
+include $(PREBUILT_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := Zstd
+LOCAL_SRC_FILES := deps/$(APP_ABI)/Zstd/libzstd.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
@@ -73,9 +91,9 @@ LOCAL_CFLAGS += \
-DENABLE_GLES=1 \
-DUSE_CURL=1 \
-DUSE_SOUND=1 \
- -DUSE_FREETYPE=1 \
-DUSE_LEVELDB=0 \
-DUSE_LUAJIT=1 \
+ -DUSE_GETTEXT=1 \
-DVERSION_MAJOR=${versionMajor} \
-DVERSION_MINOR=${versionMinor} \
-DVERSION_PATCH=${versionPatch} \
@@ -97,16 +115,16 @@ LOCAL_C_INCLUDES := \
../../src/script \
../../lib/gmp \
../../lib/jsoncpp \
- deps/Android/Curl/include \
- deps/Android/Freetype/include \
- deps/Android/Irrlicht/include \
- deps/Android/LevelDB/include \
- deps/Android/libiconv/include \
- deps/Android/libiconv/libcharset/include \
- deps/Android/LuaJIT/src \
- deps/Android/OpenAL-Soft/include \
- deps/Android/sqlite \
- deps/Android/Vorbis/include
+ deps/$(APP_ABI)/Curl/include \
+ deps/$(APP_ABI)/Freetype/include/freetype2 \
+ deps/$(APP_ABI)/Irrlicht/include \
+ deps/$(APP_ABI)/Gettext/include \
+ deps/$(APP_ABI)/Iconv/include \
+ deps/$(APP_ABI)/LuaJIT/include \
+ deps/$(APP_ABI)/OpenAL-Soft/include \
+ deps/$(APP_ABI)/SQLite/include \
+ deps/$(APP_ABI)/Vorbis/include \
+ deps/$(APP_ABI)/Zstd/include
LOCAL_SRC_FILES := \
$(wildcard ../../src/client/*.cpp) \
@@ -189,25 +207,24 @@ LOCAL_SRC_FILES := \
../../src/voxel.cpp \
../../src/voxelalgorithms.cpp
-# LevelDB backend is disabled
-# ../../src/database/database-leveldb.cpp
-
# GMP
LOCAL_SRC_FILES += ../../lib/gmp/mini-gmp.c
# JSONCPP
LOCAL_SRC_FILES += ../../lib/jsoncpp/jsoncpp.cpp
-# iconv
-LOCAL_SRC_FILES += \
- deps/Android/libiconv/lib/iconv.c \
- deps/Android/libiconv/libcharset/lib/localcharset.c
-
-# SQLite3
-LOCAL_SRC_FILES += deps/Android/sqlite/sqlite3.c
-
-LOCAL_STATIC_LIBRARIES += Curl Freetype Irrlicht OpenAL mbedTLS mbedx509 mbedcrypto Vorbis LuaJIT android_native_app_glue $(PROFILER_LIBS) #LevelDB
-#OpenSSL Crypto
+LOCAL_STATIC_LIBRARIES += \
+ Curl libmbedcrypto libmbedtls libmbedx509 \
+ Freetype \
+ Iconv libcharset \
+ Irrlicht \
+ LuaJIT \
+ OpenAL \
+ Gettext \
+ SQLite3 \
+ Vorbis libvorbisfile libogg \
+ Zstd
+LOCAL_STATIC_LIBRARIES += android_native_app_glue $(PROFILER_LIBS)
LOCAL_LDLIBS := -lEGL -lGLESv1_CM -lGLESv2 -landroid -lOpenSLES
diff --git a/android/native/jni/Application.mk b/android/native/jni/Application.mk
index 82f0148f0..e21bca61c 100644
--- a/android/native/jni/Application.mk
+++ b/android/native/jni/Application.mk
@@ -5,22 +5,22 @@ NDK_TOOLCHAIN_VERSION := clang
APP_SHORT_COMMANDS := true
APP_MODULES := Minetest
-APP_CPPFLAGS := -Ofast -fvisibility=hidden -fexceptions -Wno-deprecated-declarations -Wno-extra-tokens
+APP_CPPFLAGS := -O2 -fvisibility=hidden
ifeq ($(APP_ABI),armeabi-v7a)
-APP_CPPFLAGS += -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb
+APP_CPPFLAGS += -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb
endif
-#ifeq ($(APP_ABI),x86)
-#APP_CPPFLAGS += -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32 -funroll-loops
-#endif
+ifeq ($(APP_ABI),x86)
+APP_CPPFLAGS += -mssse3 -mfpmath=sse -funroll-loops
+endif
ifndef NDEBUG
-APP_CPPFLAGS := -g -D_DEBUG -O0 -fno-omit-frame-pointer -fexceptions
+APP_CPPFLAGS := -g -Og -fno-omit-frame-pointer
endif
-APP_CFLAGS := $(APP_CPPFLAGS) -Wno-parentheses-equality #-Werror=shorten-64-to-32
-APP_CXXFLAGS := $(APP_CPPFLAGS) -frtti -std=gnu++17
+APP_CFLAGS := $(APP_CPPFLAGS) -Wno-inconsistent-missing-override -Wno-parentheses-equality
+APP_CXXFLAGS := $(APP_CPPFLAGS) -fexceptions -frtti -std=gnu++17
APP_LDFLAGS := -Wl,--no-warn-mismatch,--gc-sections,--icf=safe
ifeq ($(APP_ABI),arm64-v8a)
diff --git a/builtin/async/init.lua b/builtin/async/init.lua
index 1b2549685..3803994d6 100644
--- a/builtin/async/init.lua
+++ b/builtin/async/init.lua
@@ -1,16 +1,10 @@
core.log("info", "Initializing Asynchronous environment")
-function core.job_processor(serialized_func, serialized_param)
- local func = loadstring(serialized_func)
+function core.job_processor(func, serialized_param)
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
+ local retval = core.serialize(func(param))
return retval or core.serialize(nil)
end
diff --git a/builtin/client/chatcommands.lua b/builtin/client/chatcommands.lua
index 0e8d4dd03..a563a6627 100644
--- a/builtin/client/chatcommands.lua
+++ b/builtin/client/chatcommands.lua
@@ -1,6 +1,5 @@
-- Minetest: builtin/client/chatcommands.lua
-
core.register_on_sending_chat_message(function(message)
if message:sub(1,2) == ".." then
return false
@@ -8,7 +7,7 @@ core.register_on_sending_chat_message(function(message)
local first_char = message:sub(1,1)
if first_char == "/" or first_char == "." then
- core.display_chat_message(core.gettext("issued command: ") .. message)
+ core.display_chat_message(core.gettext("Issued command: ") .. message)
end
if first_char ~= "." then
@@ -19,7 +18,7 @@ core.register_on_sending_chat_message(function(message)
param = param or ""
if not cmd then
- core.display_chat_message(core.gettext("-!- Empty command"))
+ core.display_chat_message("-!- " .. core.gettext("Empty command."))
return true
end
@@ -36,7 +35,7 @@ core.register_on_sending_chat_message(function(message)
core.display_chat_message(result)
end
else
- core.display_chat_message(core.gettext("-!- Invalid command: ") .. cmd)
+ core.display_chat_message("-!- " .. core.gettext("Invalid command: ") .. cmd)
end
return true
@@ -66,7 +65,7 @@ core.register_chatcommand("clear_chat_queue", {
description = core.gettext("Clear the out chat queue"),
func = function(param)
core.clear_out_chat_queue()
- return true, core.gettext("The out chat queue is now empty")
+ return true, core.gettext("The out chat queue is now empty.")
end,
})
diff --git a/builtin/client/death_formspec.lua b/builtin/client/death_formspec.lua
index e755ac5c1..c25c799ab 100644
--- a/builtin/client/death_formspec.lua
+++ b/builtin/client/death_formspec.lua
@@ -2,7 +2,6 @@
-- handled by the engine.
core.register_on_death(function()
- core.display_chat_message("You died.")
local formspec = "size[11,5.5]bgcolor[#320000b4;true]" ..
"label[4.85,1.35;" .. fgettext("You died") ..
"]button_exit[4,3;3,0.5;btn_respawn;".. fgettext("Respawn") .."]"
diff --git a/builtin/client/init.lua b/builtin/client/init.lua
index 9633a7c71..589fe8f24 100644
--- a/builtin/client/init.lua
+++ b/builtin/client/init.lua
@@ -7,5 +7,4 @@ dofile(clientpath .. "register.lua")
dofile(commonpath .. "after.lua")
dofile(commonpath .. "chatcommands.lua")
dofile(clientpath .. "chatcommands.lua")
-dofile(commonpath .. "vector.lua")
dofile(clientpath .. "death_formspec.lua")
diff --git a/builtin/common/after.lua b/builtin/common/after.lua
index e20f292f0..bce262537 100644
--- a/builtin/common/after.lua
+++ b/builtin/common/after.lua
@@ -37,7 +37,14 @@ function core.after(after, func, ...)
arg = {...},
mod_origin = core.get_last_run_mod(),
}
+
jobs[#jobs + 1] = new_job
time_next = math.min(time_next, expire)
- return { cancel = function() new_job.func = function() end end }
+
+ return {
+ cancel = function()
+ new_job.func = function() end
+ new_job.args = {}
+ end
+ }
end
diff --git a/builtin/common/chatcommands.lua b/builtin/common/chatcommands.lua
index 52edda659..7c3da0601 100644
--- a/builtin/common/chatcommands.lua
+++ b/builtin/common/chatcommands.lua
@@ -1,7 +1,47 @@
-- Minetest: builtin/common/chatcommands.lua
+-- For server-side translations (if INIT == "game")
+-- Otherwise, use core.gettext
+local S = core.get_translator("__builtin")
+
core.registered_chatcommands = {}
+-- Interpret the parameters of a command, separating options and arguments.
+-- Input: command, param
+-- command: name of command
+-- param: parameters of command
+-- Returns: opts, args
+-- opts is a string of option letters, or false on error
+-- args is an array with the non-option arguments in order, or an error message
+-- Example: for this command line:
+-- /command a b -cd e f -g
+-- the function would receive:
+-- a b -cd e f -g
+-- and it would return:
+-- "cdg", {"a", "b", "e", "f"}
+-- Negative numbers are taken as arguments. Long options (--option) are
+-- currently rejected as reserved.
+local function getopts(command, param)
+ local opts = ""
+ local args = {}
+ for match in param:gmatch("%S+") do
+ if match:byte(1) == 45 then -- 45 = '-'
+ local second = match:byte(2)
+ if second == 45 then
+ return false, S("Invalid parameters (see /help @1).", command)
+ elseif second and (second < 48 or second > 57) then -- 48 = '0', 57 = '9'
+ opts = opts .. match:sub(2)
+ else
+ -- numeric, add it to args
+ args[#args + 1] = match
+ end
+ else
+ args[#args + 1] = match
+ end
+ end
+ return opts, args
+end
+
function core.register_chatcommand(cmd, def)
def = def or {}
def.params = def.params or ""
@@ -29,35 +69,30 @@ function core.override_chatcommand(name, redefinition)
core.registered_chatcommands[name] = chatcommand
end
-local cmd_marker = "/"
-
-local function gettext(...)
- return ...
-end
-
-local function gettext_replace(text, replace)
- return text:gsub("$1", replace)
-end
-
-
-if INIT == "client" then
- cmd_marker = "."
- gettext = core.gettext
- gettext_replace = fgettext_ne
+local function format_help_line(cmd, def)
+ local cmd_marker = INIT == "client" and "." or "/"
+ local msg = core.colorize("#00ffff", cmd_marker .. 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
local function do_help_cmd(name, param)
- local function format_help_line(cmd, def)
- local msg = core.colorize("#00ffff", cmd_marker .. 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
+ local opts, args = getopts("help", param)
+ if not opts then
+ return false, args
+ end
+ if #args > 1 then
+ return false, S("Too many arguments, try using just /help <command>")
end
- if param == "" then
+ local use_gui = INIT ~= "client" and core.get_player_by_name(name)
+ use_gui = use_gui and not opts:find("t")
+
+ if #args == 0 and not use_gui then
local cmds = {}
for cmd, def in pairs(core.registered_chatcommands) do
if INIT == "client" or core.check_player_privs(name, def.privs) then
@@ -65,10 +100,25 @@ local function do_help_cmd(name, param)
end
end
table.sort(cmds)
- return true, gettext("Available commands: ") .. table.concat(cmds, " ") .. "\n"
- .. gettext_replace("Use '$1help <cmd>' to get more information,"
- .. " or '$1help all' to list everything.", cmd_marker)
- elseif param == "all" then
+ local msg
+ if INIT == "game" then
+ msg = S("Available commands: @1",
+ table.concat(cmds, " ")) .. "\n"
+ .. S("Use '/help <cmd>' to get more "
+ .. "information, or '/help all' to list "
+ .. "everything.")
+ else
+ msg = core.gettext("Available commands: ")
+ .. table.concat(cmds, " ") .. "\n"
+ .. core.gettext("Use '.help <cmd>' to get more "
+ .. "information, or '.help all' to list "
+ .. "everything.")
+ end
+ return true, msg
+ elseif #args == 0 or (args[1] == "all" and use_gui) then
+ core.show_general_help_formspec(name)
+ return true
+ elseif args[1] == "all" then
local cmds = {}
for cmd, def in pairs(core.registered_chatcommands) do
if INIT == "client" or core.check_player_privs(name, def.privs) then
@@ -76,19 +126,35 @@ local function do_help_cmd(name, param)
end
end
table.sort(cmds)
- return true, gettext("Available commands:").."\n"..table.concat(cmds, "\n")
- elseif INIT == "game" and param == "privs" then
+ local msg
+ if INIT == "game" then
+ msg = S("Available commands:")
+ else
+ msg = core.gettext("Available commands:")
+ end
+ return true, msg.."\n"..table.concat(cmds, "\n")
+ elseif INIT == "game" and args[1] == "privs" then
+ if use_gui then
+ core.show_privs_help_formspec(name)
+ return true
+ end
local privs = {}
for priv, def in pairs(core.registered_privileges) do
privs[#privs + 1] = priv .. ": " .. def.description
end
table.sort(privs)
- return true, "Available privileges:\n"..table.concat(privs, "\n")
+ return true, S("Available privileges:").."\n"..table.concat(privs, "\n")
else
- local cmd = param
+ local cmd = args[1]
local def = core.registered_chatcommands[cmd]
if not def then
- return false, gettext("Command not available: ")..cmd
+ local msg
+ if INIT == "game" then
+ msg = S("Command not available: @1", cmd)
+ else
+ msg = core.gettext("Command not available: ") .. cmd
+ end
+ return false, msg
else
return true, format_help_line(cmd, def)
end
@@ -97,16 +163,16 @@ end
if INIT == "client" then
core.register_chatcommand("help", {
- params = gettext("[all | <cmd>]"),
- description = gettext("Get help for commands"),
+ params = core.gettext("[all | <cmd>]"),
+ description = core.gettext("Get help for commands"),
func = function(param)
return do_help_cmd(nil, param)
end,
})
else
core.register_chatcommand("help", {
- params = "[all | privs | <cmd>]",
- description = "Get help for commands or list privileges",
+ params = S("[all | privs | <cmd>] [-t]"),
+ description = S("Get help for commands or list privileges (-t: output in chat)"),
func = do_help_cmd,
})
end
diff --git a/builtin/common/information_formspecs.lua b/builtin/common/information_formspecs.lua
index 3e2f1f079..3405263bf 100644
--- a/builtin/common/information_formspecs.lua
+++ b/builtin/common/information_formspecs.lua
@@ -20,7 +20,8 @@ local LIST_FORMSPEC_DESCRIPTION = [[
button_exit[5,7;3,1;quit;%s]
]]
-local formspec_escape = core.formspec_escape
+local F = core.formspec_escape
+local S = core.get_translator("__builtin")
local check_player_privs = core.check_player_privs
@@ -51,22 +52,23 @@ core.after(0, load_mod_command_tree)
local function build_chatcommands_formspec(name, sel, copy)
local rows = {}
- rows[1] = "#FFF,0,Command,Parameters"
+ rows[1] = "#FFF,0,"..F(S("Command"))..","..F(S("Parameters"))
- local description = "For more information, click on any entry in the list.\n" ..
- "Double-click to copy the entry to the chat history."
+ local description = S("For more information, click on "
+ .. "any entry in the list.").. "\n" ..
+ S("Double-click to copy the entry to the chat history.")
for i, data in ipairs(mod_cmds) do
- rows[#rows + 1] = COLOR_BLUE .. ",0," .. formspec_escape(data[1]) .. ","
+ rows[#rows + 1] = COLOR_BLUE .. ",0," .. F(data[1]) .. ","
for j, cmds in ipairs(data[2]) do
local has_priv = check_player_privs(name, cmds[2].privs)
rows[#rows + 1] = ("%s,1,%s,%s"):format(
has_priv and COLOR_GREEN or COLOR_GRAY,
- cmds[1], formspec_escape(cmds[2].params))
+ cmds[1], F(cmds[2].params))
if sel == #rows then
description = cmds[2].description
if copy then
- core.chat_send_player(name, ("Command: %s %s"):format(
+ core.chat_send_player(name, S("Command: @1 @2",
core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params))
end
end
@@ -74,9 +76,9 @@ local function build_chatcommands_formspec(name, sel, copy)
end
return LIST_FORMSPEC_DESCRIPTION:format(
- "Available commands: (see also: /help <cmd>)",
+ F(S("Available commands: (see also: /help <cmd>)")),
table.concat(rows, ","), sel or 0,
- description, "Close"
+ F(description), F(S("Close"))
)
end
@@ -91,19 +93,19 @@ local function build_privs_formspec(name)
table.sort(privs, function(a, b) return a[1] < b[1] end)
local rows = {}
- rows[1] = "#FFF,0,Privilege,Description"
+ rows[1] = "#FFF,0,"..F(S("Privilege"))..","..F(S("Description"))
local player_privs = core.get_player_privs(name)
for i, data in ipairs(privs) do
rows[#rows + 1] = ("%s,0,%s,%s"):format(
player_privs[data[1]] and COLOR_GREEN or COLOR_GRAY,
- data[1], formspec_escape(data[2].description))
+ data[1], F(data[2].description))
end
return LIST_FORMSPEC:format(
- "Available privileges:",
+ F(S("Available privileges:")),
table.concat(rows, ","),
- "Close"
+ F(S("Close"))
)
end
@@ -123,30 +125,12 @@ core.register_on_player_receive_fields(function(player, formname, fields)
end
end)
-
-local help_command = core.registered_chatcommands["help"]
-local old_help_func = help_command.func
-
-help_command.func = function(name, param)
- local admin = core.settings:get("name")
-
- -- If the admin ran help, put the output in the chat buffer as well to
- -- work with the server terminal
- if param == "privs" then
- core.show_formspec(name, "__builtin:help_privs",
- build_privs_formspec(name))
- if name ~= admin then
- return true
- end
- end
- if param == "" or param == "all" then
- core.show_formspec(name, "__builtin:help_cmds",
- build_chatcommands_formspec(name))
- if name ~= admin then
- return true
- end
- end
-
- return old_help_func(name, param)
+function core.show_general_help_formspec(name)
+ core.show_formspec(name, "__builtin:help_cmds",
+ build_chatcommands_formspec(name))
end
+function core.show_privs_help_formspec(name)
+ core.show_formspec(name, "__builtin:help_privs",
+ build_privs_formspec(name))
+end
diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua
index 0f3897f47..f5f89acd7 100644
--- a/builtin/common/misc_helpers.lua
+++ b/builtin/common/misc_helpers.lua
@@ -209,14 +209,7 @@ end
--------------------------------------------------------------------------------
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)
+ return math.sqrt(x * x + y * y)
end
--------------------------------------------------------------------------------
@@ -244,6 +237,15 @@ function math.factorial(x)
return v
end
+
+function math.round(x)
+ if x >= 0 then
+ return math.floor(x + 0.5)
+ end
+ return math.ceil(x - 0.5)
+end
+
+
function core.formspec_escape(text)
if text ~= nil then
text = string.gsub(text,"\\","\\\\")
@@ -423,21 +425,19 @@ function core.string_to_pos(value)
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
+ local x, y, z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
+ if x and y and z then
+ x = tonumber(x)
+ y = tonumber(y)
+ z = tonumber(z)
+ return vector.new(x, y, z)
end
- 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
+ x, y, z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$")
+ if x and y and z then
+ x = tonumber(x)
+ y = tonumber(y)
+ z = tonumber(z)
+ return vector.new(x, y, z)
end
return nil
end
@@ -532,7 +532,7 @@ if INIT == "mainmenu" then
end
end
-if INIT == "client" or INIT == "mainmenu" then
+if core.gettext then -- for client and mainmenu
function fgettext_ne(text, ...)
text = core.gettext(text)
local arg = {n=select('#', ...), ...}
diff --git a/builtin/common/tests/misc_helpers_spec.lua b/builtin/common/tests/misc_helpers_spec.lua
index bb9d13e7f..b16987f0b 100644
--- a/builtin/common/tests/misc_helpers_spec.lua
+++ b/builtin/common/tests/misc_helpers_spec.lua
@@ -1,4 +1,5 @@
_G.core = {}
+dofile("builtin/common/vector.lua")
dofile("builtin/common/misc_helpers.lua")
describe("string", function()
@@ -55,8 +56,8 @@ end)
describe("pos", function()
it("from string", function()
- assert.same({ x = 10, y = 5.1, z = -2}, core.string_to_pos("10.0, 5.1, -2"))
- assert.same({ x = 10, y = 5.1, z = -2}, core.string_to_pos("( 10.0, 5.1, -2)"))
+ assert.equal(vector.new(10, 5.1, -2), core.string_to_pos("10.0, 5.1, -2"))
+ assert.equal(vector.new(10, 5.1, -2), core.string_to_pos("( 10.0, 5.1, -2)"))
assert.is_nil(core.string_to_pos("asd, 5, -2)"))
end)
diff --git a/builtin/common/tests/serialize_spec.lua b/builtin/common/tests/serialize_spec.lua
index 17c6a60f7..e46b7dcc5 100644
--- a/builtin/common/tests/serialize_spec.lua
+++ b/builtin/common/tests/serialize_spec.lua
@@ -3,6 +3,7 @@ _G.core = {}
_G.setfenv = require 'busted.compatibility'.setfenv
dofile("builtin/common/serialize.lua")
+dofile("builtin/common/vector.lua")
describe("serialize", function()
it("works", function()
@@ -53,4 +54,16 @@ describe("serialize", function()
assert.is_nil(test_out.func)
assert.equals(test_out.foo, "bar")
end)
+
+ it("vectors work", function()
+ local v = vector.new(1, 2, 3)
+ assert.same({{x = 1, y = 2, z = 3}}, core.deserialize(core.serialize({v})))
+ assert.same({x = 1, y = 2, z = 3}, core.deserialize(core.serialize(v)))
+
+ -- abuse
+ v = vector.new(1, 2, 3)
+ v.a = "bla"
+ assert.same({x = 1, y = 2, z = 3, a = "bla"},
+ core.deserialize(core.serialize(v)))
+ end)
end)
diff --git a/builtin/common/tests/vector_spec.lua b/builtin/common/tests/vector_spec.lua
index 0f287363a..2f72f3383 100644
--- a/builtin/common/tests/vector_spec.lua
+++ b/builtin/common/tests/vector_spec.lua
@@ -4,14 +4,20 @@ dofile("builtin/common/vector.lua")
describe("vector", function()
describe("new()", function()
it("constructs", function()
- assert.same({ x = 0, y = 0, z = 0 }, vector.new())
- assert.same({ x = 1, y = 2, z = 3 }, vector.new(1, 2, 3))
- assert.same({ x = 3, y = 2, z = 1 }, vector.new({ x = 3, y = 2, z = 1 }))
+ assert.same({x = 0, y = 0, z = 0}, vector.new())
+ assert.same({x = 1, y = 2, z = 3}, vector.new(1, 2, 3))
+ assert.same({x = 3, y = 2, z = 1}, vector.new({x = 3, y = 2, z = 1}))
+
+ assert.is_true(vector.check(vector.new()))
+ assert.is_true(vector.check(vector.new(1, 2, 3)))
+ assert.is_true(vector.check(vector.new({x = 3, y = 2, z = 1})))
local input = vector.new({ x = 3, y = 2, z = 1 })
local output = vector.new(input)
assert.same(input, output)
- assert.are_not.equal(input, output)
+ assert.equal(input, output)
+ assert.is_false(rawequal(input, output))
+ assert.equal(input, input:new())
end)
it("throws on invalid input", function()
@@ -25,27 +31,286 @@ describe("vector", function()
end)
end)
- it("equal()", function()
- local function assertE(a, b)
- assert.is_true(vector.equals(a, b))
- end
- local function assertNE(a, b)
- assert.is_false(vector.equals(a, b))
- end
+ it("zero()", function()
+ assert.same({x = 0, y = 0, z = 0}, vector.zero())
+ assert.same(vector.new(), vector.zero())
+ assert.equal(vector.new(), vector.zero())
+ assert.is_true(vector.check(vector.zero()))
+ end)
- assertE({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0})
- assertE({x = -1, y = 0, z = 1}, {x = -1, y = 0, z = 1})
- local a = { x = 2, y = 4, z = -10 }
- assertE(a, a)
- assertNE({x = -1, y = 0, z = 1}, a)
+ it("copy()", function()
+ local v = vector.new(1, 2, 3)
+ assert.same(v, vector.copy(v))
+ assert.same(vector.new(v), vector.copy(v))
+ assert.equal(vector.new(v), vector.copy(v))
+ assert.is_true(vector.check(vector.copy(v)))
end)
- it("add()", function()
- assert.same({ x = 2, y = 4, z = 6 }, vector.add(vector.new(1, 2, 3), { x = 1, y = 2, z = 3 }))
+ it("indexes", function()
+ local some_vector = vector.new(24, 42, 13)
+ assert.equal(24, some_vector[1])
+ assert.equal(24, some_vector.x)
+ assert.equal(42, some_vector[2])
+ assert.equal(42, some_vector.y)
+ assert.equal(13, some_vector[3])
+ assert.equal(13, some_vector.z)
+
+ some_vector[1] = 100
+ assert.equal(100, some_vector.x)
+ some_vector.x = 101
+ assert.equal(101, some_vector[1])
+
+ some_vector[2] = 100
+ assert.equal(100, some_vector.y)
+ some_vector.y = 102
+ assert.equal(102, some_vector[2])
+
+ some_vector[3] = 100
+ assert.equal(100, some_vector.z)
+ some_vector.z = 103
+ assert.equal(103, some_vector[3])
+ end)
+
+ it("direction()", function()
+ local a = vector.new(1, 0, 0)
+ local b = vector.new(1, 42, 0)
+ assert.equal(vector.new(0, 1, 0), vector.direction(a, b))
+ assert.equal(vector.new(0, 1, 0), a:direction(b))
+ end)
+
+ it("distance()", function()
+ local a = vector.new(1, 0, 0)
+ local b = vector.new(3, 42, 9)
+ assert.is_true(math.abs(43 - vector.distance(a, b)) < 1.0e-12)
+ assert.is_true(math.abs(43 - a:distance(b)) < 1.0e-12)
+ assert.equal(0, vector.distance(a, a))
+ assert.equal(0, b:distance(b))
+ end)
+
+ it("length()", function()
+ local a = vector.new(0, 0, -23)
+ assert.equal(0, vector.length(vector.new()))
+ assert.equal(23, vector.length(a))
+ assert.equal(23, a:length())
+ end)
+
+ it("normalize()", function()
+ local a = vector.new(0, 0, -23)
+ assert.equal(vector.new(0, 0, -1), vector.normalize(a))
+ assert.equal(vector.new(0, 0, -1), a:normalize())
+ assert.equal(vector.new(), vector.normalize(vector.new()))
+ end)
+
+ it("floor()", function()
+ local a = vector.new(0.1, 0.9, -0.5)
+ assert.equal(vector.new(0, 0, -1), vector.floor(a))
+ assert.equal(vector.new(0, 0, -1), a:floor())
+ end)
+
+ it("round()", function()
+ local a = vector.new(0.1, 0.9, -0.5)
+ assert.equal(vector.new(0, 1, -1), vector.round(a))
+ assert.equal(vector.new(0, 1, -1), a:round())
+ end)
+
+ it("apply()", function()
+ local i = 0
+ local f = function(x)
+ i = i + 1
+ return x + i
+ end
+ local a = vector.new(0.1, 0.9, -0.5)
+ assert.equal(vector.new(1, 1, 0), vector.apply(a, math.ceil))
+ assert.equal(vector.new(1, 1, 0), a:apply(math.ceil))
+ assert.equal(vector.new(0.1, 0.9, 0.5), vector.apply(a, math.abs))
+ assert.equal(vector.new(0.1, 0.9, 0.5), a:apply(math.abs))
+ assert.equal(vector.new(1.1, 2.9, 2.5), vector.apply(a, f))
+ assert.equal(vector.new(4.1, 5.9, 5.5), a:apply(f))
+ end)
+
+ it("equals()", function()
+ local function assertE(a, b)
+ assert.is_true(vector.equals(a, b))
+ end
+ local function assertNE(a, b)
+ assert.is_false(vector.equals(a, b))
+ end
+
+ assertE({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0})
+ assertE({x = -1, y = 0, z = 1}, {x = -1, y = 0, z = 1})
+ assertE({x = -1, y = 0, z = 1}, vector.new(-1, 0, 1))
+ local a = {x = 2, y = 4, z = -10}
+ assertE(a, a)
+ assertNE({x = -1, y = 0, z = 1}, a)
+
+ assert.equal(vector.new(1, 2, 3), vector.new(1, 2, 3))
+ assert.is_true(vector.new(1, 2, 3):equals(vector.new(1, 2, 3)))
+ assert.not_equal(vector.new(1, 2, 3), vector.new(1, 2, 4))
+ assert.is_true(vector.new(1, 2, 3) == vector.new(1, 2, 3))
+ assert.is_false(vector.new(1, 2, 3) == vector.new(1, 3, 3))
+ end)
+
+ it("metatable is same", function()
+ local a = vector.new()
+ local b = vector.new(1, 2, 3)
+
+ assert.equal(true, vector.check(a))
+ assert.equal(true, vector.check(b))
+
+ assert.equal(vector.metatable, getmetatable(a))
+ assert.equal(vector.metatable, getmetatable(b))
+ assert.equal(vector.metatable, a.metatable)
+ end)
+
+ it("sort()", function()
+ local a = vector.new(1, 2, 3)
+ local b = vector.new(0.5, 232, -2)
+ local sorted = {vector.new(0.5, 2, -2), vector.new(1, 232, 3)}
+ assert.same(sorted, {vector.sort(a, b)})
+ assert.same(sorted, {a:sort(b)})
+ end)
+
+ it("angle()", function()
+ assert.equal(math.pi, vector.angle(vector.new(-1, -2, -3), vector.new(1, 2, 3)))
+ assert.equal(math.pi/2, vector.new(0, 1, 0):angle(vector.new(1, 0, 0)))
+ end)
+
+ it("dot()", function()
+ assert.equal(-14, vector.dot(vector.new(-1, -2, -3), vector.new(1, 2, 3)))
+ assert.equal(0, vector.new():dot(vector.new(1, 2, 3)))
+ end)
+
+ it("cross()", function()
+ local a = vector.new(-1, -2, 0)
+ local b = vector.new(1, 2, 3)
+ assert.equal(vector.new(-6, 3, 0), vector.cross(a, b))
+ assert.equal(vector.new(-6, 3, 0), a:cross(b))
end)
it("offset()", function()
- assert.same({ x = 41, y = 52, z = 63 }, vector.offset(vector.new(1, 2, 3), 40, 50, 60))
+ assert.same({x = 41, y = 52, z = 63}, vector.offset(vector.new(1, 2, 3), 40, 50, 60))
+ assert.equal(vector.new(41, 52, 63), vector.offset(vector.new(1, 2, 3), 40, 50, 60))
+ assert.equal(vector.new(41, 52, 63), vector.new(1, 2, 3):offset(40, 50, 60))
+ end)
+
+ it("is()", function()
+ local some_table1 = {foo = 13, [42] = 1, "bar", 2}
+ local some_table2 = {1, 2, 3}
+ local some_table3 = {x = 1, 2, 3}
+ local some_table4 = {1, 2, z = 3}
+ local old = {x = 1, y = 2, z = 3}
+ local real = vector.new(1, 2, 3)
+
+ assert.is_false(vector.check(nil))
+ assert.is_false(vector.check(1))
+ assert.is_false(vector.check(true))
+ assert.is_false(vector.check("foo"))
+ assert.is_false(vector.check(some_table1))
+ assert.is_false(vector.check(some_table2))
+ assert.is_false(vector.check(some_table3))
+ assert.is_false(vector.check(some_table4))
+ assert.is_false(vector.check(old))
+ assert.is_true(vector.check(real))
+ assert.is_true(real:check())
+ end)
+
+ it("global pairs", function()
+ local out = {}
+ local vec = vector.new(10, 20, 30)
+ for k, v in pairs(vec) do
+ out[k] = v
+ end
+ assert.same({x = 10, y = 20, z = 30}, out)
+ end)
+
+ it("abusing works", function()
+ local v = vector.new(1, 2, 3)
+ v.a = 1
+ assert.equal(1, v.a)
+
+ local a_is_there = false
+ for key, value in pairs(v) do
+ if key == "a" then
+ a_is_there = true
+ assert.equal(value, 1)
+ break
+ end
+ end
+ assert.is_true(a_is_there)
+ end)
+
+ it("add()", function()
+ local a = vector.new(1, 2, 3)
+ local b = vector.new(1, 4, 3)
+ local c = vector.new(2, 6, 6)
+ assert.equal(c, vector.add(a, {x = 1, y = 4, z = 3}))
+ assert.equal(c, vector.add(a, b))
+ assert.equal(c, a:add(b))
+ assert.equal(c, a + b)
+ assert.equal(c, b + a)
+ end)
+
+ it("subtract()", function()
+ local a = vector.new(1, 2, 3)
+ local b = vector.new(2, 4, 3)
+ local c = vector.new(-1, -2, 0)
+ assert.equal(c, vector.subtract(a, {x = 2, y = 4, z = 3}))
+ assert.equal(c, vector.subtract(a, b))
+ assert.equal(c, a:subtract(b))
+ assert.equal(c, a - b)
+ assert.equal(c, -b + a)
+ end)
+
+ it("multiply()", function()
+ local a = vector.new(1, 2, 3)
+ local b = vector.new(2, 4, 3)
+ local c = vector.new(2, 8, 9)
+ local s = 2
+ local d = vector.new(2, 4, 6)
+ assert.equal(c, vector.multiply(a, {x = 2, y = 4, z = 3}))
+ assert.equal(c, vector.multiply(a, b))
+ assert.equal(d, vector.multiply(a, s))
+ assert.equal(d, a:multiply(s))
+ assert.equal(d, a * s)
+ assert.equal(d, s * a)
+ assert.equal(-a, -1 * a)
+ end)
+
+ it("divide()", function()
+ local a = vector.new(1, 2, 3)
+ local b = vector.new(2, 4, 3)
+ local c = vector.new(0.5, 0.5, 1)
+ local s = 2
+ local d = vector.new(0.5, 1, 1.5)
+ assert.equal(c, vector.divide(a, {x = 2, y = 4, z = 3}))
+ assert.equal(c, vector.divide(a, b))
+ assert.equal(d, vector.divide(a, s))
+ assert.equal(d, a:divide(s))
+ assert.equal(d, a / s)
+ assert.equal(d, 1/s * a)
+ assert.equal(-a, a / -1)
+ end)
+
+ it("to_string()", function()
+ local v = vector.new(1, 2, 3.14)
+ assert.same("(1, 2, 3.14)", vector.to_string(v))
+ assert.same("(1, 2, 3.14)", v:to_string())
+ assert.same("(1, 2, 3.14)", tostring(v))
+ end)
+
+ it("from_string()", function()
+ local v = vector.new(1, 2, 3.14)
+ assert.is_true(vector.check(vector.from_string("(1, 2, 3.14)")))
+ assert.same({v, 13}, {vector.from_string("(1, 2, 3.14)")})
+ assert.same({v, 12}, {vector.from_string("(1,2 ,3.14)")})
+ assert.same({v, 12}, {vector.from_string("(1,2,3.14,)")})
+ assert.same({v, 11}, {vector.from_string("(1 2 3.14)")})
+ assert.same({v, 15}, {vector.from_string("( 1, 2, 3.14 )")})
+ assert.same({v, 15}, {vector.from_string(" ( 1, 2, 3.14) ")})
+ assert.same({vector.new(), 8}, {vector.from_string("(0,0,0) ( 1, 2, 3.14) ")})
+ assert.same({v, 22}, {vector.from_string("(0,0,0) ( 1, 2, 3.14) ", 8)})
+ assert.same({v, 22}, {vector.from_string("(0,0,0) ( 1, 2, 3.14) ", 9)})
+ assert.same(nil, vector.from_string("nothing"))
end)
-- This function is needed because of floating point imprecision.
diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua
index d6437deda..581d014e0 100644
--- a/builtin/common/vector.lua
+++ b/builtin/common/vector.lua
@@ -1,73 +1,126 @@
+--[[
+Vector helpers
+Note: The vector.*-functions must be able to accept old vectors that had no metatables
+]]
+
+-- localize functions
+local setmetatable = setmetatable
vector = {}
+local metatable = {}
+vector.metatable = metatable
+
+local xyz = {"x", "y", "z"}
+
+-- only called when rawget(v, key) returns nil
+function metatable.__index(v, key)
+ return rawget(v, xyz[key]) or vector[key]
+end
+
+-- only called when rawget(v, key) returns nil
+function metatable.__newindex(v, key, value)
+ rawset(v, xyz[key] or key, value)
+end
+
+-- constructors
+
+local function fast_new(x, y, z)
+ return setmetatable({x = x, y = y, z = z}, metatable)
+end
+
function vector.new(a, b, c)
+ if a and b and c then
+ return fast_new(a, b, c)
+ end
+
+ -- deprecated, use vector.copy and vector.zero directly
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}
+ return vector.copy(a)
+ else
+ assert(not a, "Invalid arguments for vector.new()")
+ return vector.zero()
+ end
+end
+
+function vector.zero()
+ return fast_new(0, 0, 0)
+end
+
+function vector.copy(v)
+ assert(v.x and v.y and v.z, "Invalid vector passed to vector.copy()")
+ return fast_new(v.x, v.y, v.z)
+end
+
+function vector.from_string(s, init)
+ local x, y, z, np = string.match(s, "^%s*%(%s*([^%s,]+)%s*[,%s]%s*([^%s,]+)%s*[,%s]" ..
+ "%s*([^%s,]+)%s*[,%s]?%s*%)()", init)
+ x = tonumber(x)
+ y = tonumber(y)
+ z = tonumber(z)
+ if not (x and y and z) then
+ return nil
end
- return {x=0, y=0, z=0}
+ return fast_new(x, y, z), np
+end
+
+function vector.to_string(v)
+ return string.format("(%g, %g, %g)", v.x, v.y, v.z)
end
+metatable.__tostring = vector.to_string
function vector.equals(a, b)
return a.x == b.x and
a.y == b.y and
a.z == b.z
end
+metatable.__eq = vector.equals
+
+-- unary operations
function vector.length(v)
- return math.hypot(v.x, math.hypot(v.y, v.z))
+ return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
end
+-- Note: we can not use __len because it is already used for primitive table length
function vector.normalize(v)
local len = vector.length(v)
if len == 0 then
- return {x=0, y=0, z=0}
+ return fast_new(0, 0, 0)
else
return vector.divide(v, len)
end
end
function vector.floor(v)
- return {
- x = math.floor(v.x),
- y = math.floor(v.y),
- z = math.floor(v.z)
- }
+ return vector.apply(v, math.floor)
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)
- }
+ return fast_new(
+ math.round(v.x),
+ math.round(v.y),
+ math.round(v.z)
+ )
end
function vector.apply(v, func)
- return {
- x = func(v.x),
- y = func(v.y),
- z = func(v.z)
- }
+ return fast_new(
+ func(v.x),
+ func(v.y),
+ 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))
+ return math.sqrt(x * x + y * y + z * z)
end
function vector.direction(pos1, pos2)
- return vector.normalize({
- x = pos2.x - pos1.x,
- y = pos2.y - pos1.y,
- z = pos2.z - pos1.z
- })
+ return vector.subtract(pos2, pos1):normalize()
end
function vector.angle(a, b)
@@ -82,70 +135,137 @@ function vector.dot(a, b)
end
function vector.cross(a, b)
- return {
- x = a.y * b.z - a.z * b.y,
- y = a.z * b.x - a.x * b.z,
- z = a.x * b.y - a.y * b.x
- }
+ return fast_new(
+ a.y * b.z - a.z * b.y,
+ a.z * b.x - a.x * b.z,
+ a.x * b.y - a.y * b.x
+ )
end
+function metatable.__unm(v)
+ return fast_new(-v.x, -v.y, -v.z)
+end
+
+-- add, sub, mul, div operations
+
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}
+ return fast_new(
+ a.x + b.x,
+ a.y + b.y,
+ a.z + b.z
+ )
else
- return {x = a.x + b,
- y = a.y + b,
- z = a.z + b}
+ return fast_new(
+ a.x + b,
+ a.y + b,
+ a.z + b
+ )
end
end
+function metatable.__add(a, b)
+ return fast_new(
+ a.x + b.x,
+ a.y + b.y,
+ a.z + b.z
+ )
+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}
+ return fast_new(
+ a.x - b.x,
+ a.y - b.y,
+ a.z - b.z
+ )
else
- return {x = a.x - b,
- y = a.y - b,
- z = a.z - b}
+ return fast_new(
+ a.x - b,
+ a.y - b,
+ a.z - b
+ )
end
end
+function metatable.__sub(a, b)
+ return fast_new(
+ a.x - b.x,
+ a.y - b.y,
+ a.z - b.z
+ )
+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}
+ return fast_new(
+ a.x * b.x,
+ a.y * b.y,
+ a.z * b.z
+ )
+ else
+ return fast_new(
+ a.x * b,
+ a.y * b,
+ a.z * b
+ )
+ end
+end
+function metatable.__mul(a, b)
+ if type(a) == "table" then
+ return fast_new(
+ a.x * b,
+ a.y * b,
+ a.z * b
+ )
else
- return {x = a.x * b,
- y = a.y * b,
- z = a.z * b}
+ return fast_new(
+ a * b.x,
+ a * b.y,
+ a * b.z
+ )
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}
+ return fast_new(
+ a.x / b.x,
+ a.y / b.y,
+ a.z / b.z
+ )
else
- return {x = a.x / b,
- y = a.y / b,
- z = a.z / b}
+ return fast_new(
+ a.x / b,
+ a.y / b,
+ a.z / b
+ )
end
end
+function metatable.__div(a, b)
+ -- scalar/vector makes no sense
+ return fast_new(
+ a.x / b,
+ a.y / b,
+ a.z / b
+ )
+end
+
+-- misc stuff
function vector.offset(v, x, y, z)
- return {x = v.x + x,
- y = v.y + y,
- z = v.z + z}
+ return fast_new(
+ v.x + x,
+ v.y + y,
+ v.z + z
+ )
end
function vector.sort(a, b)
- return {x = math.min(a.x, b.x), y = math.min(a.y, b.y), z = math.min(a.z, b.z)},
- {x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z)}
+ return fast_new(math.min(a.x, b.x), math.min(a.y, b.y), math.min(a.z, b.z)),
+ fast_new(math.max(a.x, b.x), math.max(a.y, b.y), math.max(a.z, b.z))
+end
+
+function vector.check(v)
+ return getmetatable(v) == metatable
end
local function sin(x)
@@ -213,7 +333,7 @@ end
function vector.dir_to_rotation(forward, up)
forward = vector.normalize(forward)
- local rot = {x = math.asin(forward.y), y = -math.atan2(forward.x, forward.z), z = 0}
+ local rot = vector.new(math.asin(forward.y), -math.atan2(forward.x, forward.z), 0)
if not up then
return rot
end
@@ -221,7 +341,7 @@ function vector.dir_to_rotation(forward, up)
"Invalid vectors passed to vector.dir_to_rotation().")
up = vector.normalize(up)
-- Calculate vector pointing up with roll = 0, just based on forward vector.
- local forwup = vector.rotate({x = 0, y = 1, z = 0}, rot)
+ local forwup = vector.rotate(vector.new(0, 1, 0), rot)
-- 'forwup' and 'up' are now in a plane with 'forward' as normal.
-- The angle between them is the absolute of the roll value we're looking for.
rot.z = vector.angle(forwup, up)
diff --git a/builtin/fstk/tabview.lua b/builtin/fstk/tabview.lua
index 3715e231b..424d329fb 100644
--- a/builtin/fstk/tabview.lua
+++ b/builtin/fstk/tabview.lua
@@ -58,26 +58,20 @@ end
--------------------------------------------------------------------------------
local function get_formspec(self)
- local formspec = ""
+ if self.hidden or (self.parent ~= nil and self.parent.hidden) then
+ return ""
+ end
+ local tab = self.tablist[self.last_tab_index]
- if not self.hidden and (self.parent == nil or not self.parent.hidden) then
+ local content, prepend = tab.get_formspec(self, tab.name, tab.tabdata, tab.tabsize)
- 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
- )
+ if self.parent == nil and not prepend then
+ local tsize = tab.tabsize or {width=self.width, height=self.height}
+ prepend = string.format("size[%f,%f,%s]", tsize.width, tsize.height,
+ dump(self.fixed_size))
end
+
+ local formspec = (prepend or "") .. self:tab_header() .. content
return formspec
end
@@ -97,14 +91,9 @@ local function handle_buttons(self,fields)
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
- )
+ local tab = self.tablist[self.last_tab_index]
+ if tab.button_handler ~= nil then
+ return tab.button_handler(self, fields, tab.name, tab.tabdata)
end
return false
@@ -122,14 +111,9 @@ local function handle_events(self,event)
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
- )
+ local tab = self.tablist[self.last_tab_index]
+ if tab.evt_handler ~= nil then
+ return tab.evt_handler(self, event, tab.name, tab.tabdata)
end
return false
diff --git a/builtin/fstk/ui.lua b/builtin/fstk/ui.lua
index 976659ed3..13f9cbec2 100644
--- a/builtin/fstk/ui.lua
+++ b/builtin/fstk/ui.lua
@@ -63,7 +63,7 @@ function ui.update()
-- handle errors
if gamedata ~= nil and gamedata.reconnect_requested then
local error_message = core.formspec_escape(
- gamedata.errormessage or "<none available>")
+ gamedata.errormessage or fgettext("<none available>"))
formspec = {
"size[14,8]",
"real_coordinates[true]",
diff --git a/builtin/game/auth.lua b/builtin/game/auth.lua
index fc061666c..e7d502bb3 100644
--- a/builtin/game/auth.lua
+++ b/builtin/game/auth.lua
@@ -87,6 +87,10 @@ core.builtin_auth_handler = {
core.settings:get("default_password")))
end
+ auth_entry.privileges = privileges
+
+ core_auth.save(auth_entry)
+
-- Run grant callbacks
for priv, _ in pairs(privileges) do
if not auth_entry.privileges[priv] then
@@ -100,9 +104,6 @@ core.builtin_auth_handler = {
core.run_priv_callbacks(name, priv, nil, "revoke")
end
end
-
- auth_entry.privileges = privileges
- core_auth.save(auth_entry)
core.notify_authentication_modified(name)
end,
reload = function()
diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua
index 945707623..b73e32876 100644
--- a/builtin/game/chat.lua
+++ b/builtin/game/chat.lua
@@ -1,5 +1,7 @@
-- Minetest: builtin/game/chat.lua
+local S = core.get_translator("__builtin")
+
-- Helper function that implements search and replace without pattern matching
-- Returns the string and a boolean indicating whether or not the string was modified
local function safe_gsub(s, replace, with)
@@ -45,6 +47,8 @@ end
core.chatcommands = core.registered_chatcommands -- BACKWARDS COMPATIBILITY
+local msg_time_threshold =
+ tonumber(core.settings:get("chatcommand_msg_time_threshold")) or 0.1
core.register_on_chat_message(function(name, message)
if message:sub(1,1) ~= "/" then
return
@@ -52,7 +56,7 @@ core.register_on_chat_message(function(name, message)
local cmd, param = string.match(message, "^/([^ ]+) *(.*)")
if not cmd then
- core.chat_send_player(name, "-!- Empty command")
+ core.chat_send_player(name, "-!- "..S("Empty command."))
return true
end
@@ -65,15 +69,17 @@ core.register_on_chat_message(function(name, message)
local cmd_def = core.registered_chatcommands[cmd]
if not cmd_def then
- core.chat_send_player(name, "-!- Invalid command: " .. cmd)
+ core.chat_send_player(name, "-!- "..S("Invalid command: @1", cmd))
return true
end
local has_privs, missing_privs = core.check_player_privs(name, cmd_def.privs)
if has_privs then
core.set_last_run_mod(cmd_def.mod_origin)
+ local t_before = core.get_us_time()
local success, result = cmd_def.func(name, param)
+ local delay = (core.get_us_time() - t_before) / 1000000
if success == false and result == nil then
- core.chat_send_player(name, "-!- Invalid command usage")
+ core.chat_send_player(name, "-!- "..S("Invalid command usage."))
local help_def = core.registered_chatcommands["help"]
if help_def then
local _, helpmsg = help_def.func(name, cmd)
@@ -81,13 +87,27 @@ core.register_on_chat_message(function(name, message)
core.chat_send_player(name, helpmsg)
end
end
- elseif result then
- core.chat_send_player(name, result)
+ else
+ if delay > msg_time_threshold then
+ -- Show how much time it took to execute the command
+ if result then
+ result = result .. core.colorize("#f3d2ff", S(" (@1 s)",
+ string.format("%.5f", delay)))
+ else
+ result = core.colorize("#f3d2ff", S(
+ "Command execution took @1 s",
+ string.format("%.5f", delay)))
+ end
+ end
+ if result then
+ core.chat_send_player(name, result)
+ end
end
else
- core.chat_send_player(name, "You don't have permission"
- .. " to run this command (missing privileges: "
- .. table.concat(missing_privs, ", ") .. ")")
+ core.chat_send_player(name,
+ S("You don't have permission to run this command "
+ .. "(missing privileges: @1).",
+ table.concat(missing_privs, ", ")))
end
return true -- Handled chat message
end)
@@ -107,12 +127,13 @@ local function parse_range_str(player_name, str)
if args[1] == "here" then
p1, p2 = core.get_player_radius_area(player_name, tonumber(args[2]))
if p1 == nil then
- return false, "Unable to get player " .. player_name .. " position"
+ return false, S("Unable to get position of player @1.", player_name)
end
else
p1, p2 = core.string_to_area(str)
if p1 == nil then
- return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)"
+ return false, S("Incorrect area format. "
+ .. "Expected: (x1,y1,z1) (x2,y2,z2)")
end
end
@@ -123,9 +144,9 @@ end
-- Chat commands
--
core.register_chatcommand("me", {
- params = "<action>",
- description = "Show chat action (e.g., '/me orders a pizza' displays"
- .. " '<player name> orders a pizza')",
+ params = S("<action>"),
+ description = S("Show chat action (e.g., '/me orders a pizza' "
+ .. "displays '<player name> orders a pizza')"),
privs = {shout=true},
func = function(name, param)
core.chat_send_all("* " .. name .. " " .. param)
@@ -134,43 +155,54 @@ core.register_chatcommand("me", {
})
core.register_chatcommand("admin", {
- description = "Show the name of the server owner",
+ description = S("Show the name of the server owner"),
func = function(name)
local admin = core.settings:get("name")
if admin then
- return true, "The administrator of this server is " .. admin .. "."
+ return true, S("The administrator of this server is @1.", admin)
else
- return false, "There's no administrator named in the config file."
+ return false, S("There's no administrator named "
+ .. "in the config file.")
end
end,
})
+local function privileges_of(name, privs)
+ if not privs then
+ privs = core.get_player_privs(name)
+ end
+ local privstr = core.privs_to_string(privs, ", ")
+ if privstr == "" then
+ return S("@1 does not have any privileges.", name)
+ else
+ return S("Privileges of @1: @2", name, privstr)
+ end
+end
+
core.register_chatcommand("privs", {
- params = "[<name>]",
- description = "Show privileges of yourself or another player",
+ params = S("[<name>]"),
+ description = S("Show privileges of yourself or another player"),
func = function(caller, param)
param = param:trim()
local name = (param ~= "" and param or caller)
if not core.player_exists(name) then
- return false, "Player " .. name .. " does not exist."
+ return false, S("Player @1 does not exist.", name)
end
- return true, "Privileges of " .. name .. ": "
- .. core.privs_to_string(
- core.get_player_privs(name), ", ")
+ return true, privileges_of(name)
end,
})
core.register_chatcommand("haspriv", {
- params = "<privilege>",
- description = "Return list of all online players with privilege.",
+ params = S("<privilege>"),
+ description = S("Return list of all online players with privilege"),
privs = {basic_privs = true},
func = function(caller, param)
param = param:trim()
if param == "" then
- return false, "Invalid parameters (see /help haspriv)"
+ return false, S("Invalid parameters (see /help haspriv).")
end
if not core.registered_privileges[param] then
- return false, "Unknown privilege!"
+ return false, S("Unknown privilege!")
end
local privs = core.string_to_privs(param)
local players_with_priv = {}
@@ -180,19 +212,25 @@ core.register_chatcommand("haspriv", {
table.insert(players_with_priv, player_name)
end
end
- return true, "Players online with the \"" .. param .. "\" privilege: " ..
- table.concat(players_with_priv, ", ")
+ if #players_with_priv == 0 then
+ return true, S("No online player has the \"@1\" privilege.",
+ param)
+ else
+ return true, S("Players online with the \"@1\" privilege: @2",
+ param,
+ table.concat(players_with_priv, ", "))
+ end
end
})
local function handle_grant_command(caller, grantname, grantprivstr)
local caller_privs = core.get_player_privs(caller)
if not (caller_privs.privs or caller_privs.basic_privs) then
- return false, "Your privileges are insufficient."
+ return false, S("Your privileges are insufficient.")
end
if not core.get_auth_handler().get_auth(grantname) then
- return false, "Player " .. grantname .. " does not exist."
+ return false, S("Player @1 does not exist.", grantname)
end
local grantprivs = core.string_to_privs(grantprivstr)
if grantprivstr == "all" then
@@ -204,50 +242,51 @@ local function handle_grant_command(caller, grantname, grantprivstr)
core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
for priv, _ in pairs(grantprivs) do
if not basic_privs[priv] and not caller_privs.privs then
- return false, "Your privileges are insufficient."
+ return false, S("Your privileges are insufficient. "..
+ "'@1' only allows you to grant: @2",
+ "basic_privs",
+ core.privs_to_string(basic_privs, ', '))
end
if not core.registered_privileges[priv] then
- privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
+ privs_unknown = privs_unknown .. S("Unknown privilege: @1", priv) .. "\n"
end
privs[priv] = true
end
if privs_unknown ~= "" then
return false, privs_unknown
end
+ core.set_player_privs(grantname, privs)
for priv, _ in pairs(grantprivs) do
-- call the on_grant callbacks
core.run_priv_callbacks(grantname, priv, caller, "grant")
end
- core.set_player_privs(grantname, privs)
core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname)
if grantname ~= caller then
- core.chat_send_player(grantname, caller
- .. " granted you privileges: "
- .. core.privs_to_string(grantprivs, ' '))
+ core.chat_send_player(grantname,
+ S("@1 granted you privileges: @2", caller,
+ core.privs_to_string(grantprivs, ', ')))
end
- return true, "Privileges of " .. grantname .. ": "
- .. core.privs_to_string(
- core.get_player_privs(grantname), ' ')
+ return true, privileges_of(grantname)
end
core.register_chatcommand("grant", {
- params = "<name> (<privilege> | all)",
- description = "Give privileges to player",
+ params = S("<name> (<privilege> [, <privilege2> [<...>]] | all)"),
+ description = S("Give privileges to player"),
func = function(name, param)
local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)")
if not grantname or not grantprivstr then
- return false, "Invalid parameters (see /help grant)"
+ return false, S("Invalid parameters (see /help grant).")
end
return handle_grant_command(name, grantname, grantprivstr)
end,
})
core.register_chatcommand("grantme", {
- params = "<privilege> | all",
- description = "Grant privileges to yourself",
+ params = S("<privilege> [, <privilege2> [<...>]] | all"),
+ description = S("Grant privileges to yourself"),
func = function(name, param)
if param == "" then
- return false, "Invalid parameters (see /help grantme)"
+ return false, S("Invalid parameters (see /help grantme).")
end
return handle_grant_command(name, name, param)
end,
@@ -256,23 +295,20 @@ core.register_chatcommand("grantme", {
local function handle_revoke_command(caller, revokename, revokeprivstr)
local caller_privs = core.get_player_privs(caller)
if not (caller_privs.privs or caller_privs.basic_privs) then
- return false, "Your privileges are insufficient."
+ return false, S("Your privileges are insufficient.")
end
if not core.get_auth_handler().get_auth(revokename) then
- return false, "Player " .. revokename .. " does not exist."
+ return false, S("Player @1 does not exist.", revokename)
end
- local revokeprivs = core.string_to_privs(revokeprivstr)
local privs = core.get_player_privs(revokename)
- local basic_privs =
- core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
- for priv, _ in pairs(revokeprivs) do
- if not basic_privs[priv] and not caller_privs.privs then
- return false, "Your privileges are insufficient."
- end
- end
+ local revokeprivs = core.string_to_privs(revokeprivstr)
+ local is_singleplayer = core.is_singleplayer()
+ local is_admin = not is_singleplayer
+ and revokename == core.settings:get("name")
+ and revokename ~= ""
if revokeprivstr == "all" then
revokeprivs = privs
privs = {}
@@ -282,53 +318,99 @@ local function handle_revoke_command(caller, revokename, revokeprivstr)
end
end
+ local privs_unknown = ""
+ local basic_privs =
+ core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
+ local irrevokable = {}
+ local has_irrevokable_priv = false
+ for priv, _ in pairs(revokeprivs) do
+ if not basic_privs[priv] and not caller_privs.privs then
+ return false, S("Your privileges are insufficient. "..
+ "'@1' only allows you to revoke: @2",
+ "basic_privs",
+ core.privs_to_string(basic_privs, ', '))
+ end
+ local def = core.registered_privileges[priv]
+ if not def then
+ privs_unknown = privs_unknown .. S("Unknown privilege: @1", priv) .. "\n"
+ elseif is_singleplayer and def.give_to_singleplayer then
+ irrevokable[priv] = true
+ elseif is_admin and def.give_to_admin then
+ irrevokable[priv] = true
+ end
+ end
+ for priv, _ in pairs(irrevokable) do
+ revokeprivs[priv] = nil
+ has_irrevokable_priv = true
+ end
+ if privs_unknown ~= "" then
+ return false, privs_unknown
+ end
+ if has_irrevokable_priv then
+ if is_singleplayer then
+ core.chat_send_player(caller,
+ S("Note: Cannot revoke in singleplayer: @1",
+ core.privs_to_string(irrevokable, ', ')))
+ elseif is_admin then
+ core.chat_send_player(caller,
+ S("Note: Cannot revoke from admin: @1",
+ core.privs_to_string(irrevokable, ', ')))
+ end
+ end
+
+ local revokecount = 0
+
+ core.set_player_privs(revokename, privs)
for priv, _ in pairs(revokeprivs) do
-- call the on_revoke callbacks
core.run_priv_callbacks(revokename, priv, caller, "revoke")
+ revokecount = revokecount + 1
+ end
+ local new_privs = core.get_player_privs(revokename)
+
+ if revokecount == 0 then
+ return false, S("No privileges were revoked.")
end
- core.set_player_privs(revokename, privs)
core.log("action", caller..' revoked ('
..core.privs_to_string(revokeprivs, ', ')
..') privileges from '..revokename)
if revokename ~= caller then
- core.chat_send_player(revokename, caller
- .. " revoked privileges from you: "
- .. core.privs_to_string(revokeprivs, ' '))
+ core.chat_send_player(revokename,
+ S("@1 revoked privileges from you: @2", caller,
+ core.privs_to_string(revokeprivs, ', ')))
end
- return true, "Privileges of " .. revokename .. ": "
- .. core.privs_to_string(
- core.get_player_privs(revokename), ' ')
+ return true, privileges_of(revokename, new_privs)
end
core.register_chatcommand("revoke", {
- params = "<name> (<privilege> | all)",
- description = "Remove privileges from player",
+ params = S("<name> (<privilege> [, <privilege2> [<...>]] | all)"),
+ description = S("Remove privileges from player"),
privs = {},
func = function(name, param)
local revokename, revokeprivstr = string.match(param, "([^ ]+) (.+)")
if not revokename or not revokeprivstr then
- return false, "Invalid parameters (see /help revoke)"
+ return false, S("Invalid parameters (see /help revoke).")
end
return handle_revoke_command(name, revokename, revokeprivstr)
end,
})
core.register_chatcommand("revokeme", {
- params = "<privilege> | all",
- description = "Revoke privileges from yourself",
+ params = S("<privilege> [, <privilege2> [<...>]] | all"),
+ description = S("Revoke privileges from yourself"),
privs = {},
func = function(name, param)
if param == "" then
- return false, "Invalid parameters (see /help revokeme)"
+ return false, S("Invalid parameters (see /help revokeme).")
end
return handle_revoke_command(name, name, param)
end,
})
core.register_chatcommand("setpassword", {
- params = "<name> <password>",
- description = "Set player's password",
+ params = S("<name> <password>"),
+ description = S("Set player's password"),
privs = {password=true},
func = function(name, param)
local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
@@ -338,207 +420,197 @@ core.register_chatcommand("setpassword", {
end
if not toname then
- return false, "Name field required"
+ return false, S("Name field required.")
end
- local act_str_past, act_str_pres
+ local msg_chat, msg_log, msg_ret
if not raw_password then
core.set_player_password(toname, "")
- act_str_past = "cleared"
- act_str_pres = "clears"
+ msg_chat = S("Your password was cleared by @1.", name)
+ msg_log = name .. " clears password of " .. toname .. "."
+ msg_ret = S("Password of player \"@1\" cleared.", toname)
else
core.set_player_password(toname,
core.get_password_hash(toname,
raw_password))
- act_str_past = "set"
- act_str_pres = "sets"
+ msg_chat = S("Your password was set by @1.", name)
+ msg_log = name .. " sets password of " .. toname .. "."
+ msg_ret = S("Password of player \"@1\" set.", toname)
end
if toname ~= name then
- core.chat_send_player(toname, "Your password was "
- .. act_str_past .. " by " .. name)
+ core.chat_send_player(toname, msg_chat)
end
- core.log("action", name .. " " .. act_str_pres ..
- " password of " .. toname .. ".")
+ core.log("action", msg_log)
- return true, "Password of player \"" .. toname .. "\" " .. act_str_past
+ return true, msg_ret
end,
})
core.register_chatcommand("clearpassword", {
- params = "<name>",
- description = "Set empty password for a player",
+ params = S("<name>"),
+ description = S("Set empty password for a player"),
privs = {password=true},
func = function(name, param)
local toname = param
if toname == "" then
- return false, "Name field required"
+ return false, S("Name field required.")
end
core.set_player_password(toname, '')
core.log("action", name .. " clears password of " .. toname .. ".")
- return true, "Password of player \"" .. toname .. "\" cleared"
+ return true, S("Password of player \"@1\" cleared.", toname)
end,
})
core.register_chatcommand("auth_reload", {
params = "",
- description = "Reload authentication data",
+ description = S("Reload authentication data"),
privs = {server=true},
func = function(name, param)
local done = core.auth_reload()
- return done, (done and "Done." or "Failed.")
+ return done, (done and S("Done.") or S("Failed."))
end,
})
core.register_chatcommand("remove_player", {
- params = "<name>",
- description = "Remove a player's data",
+ params = S("<name>"),
+ description = S("Remove a player's data"),
privs = {server=true},
func = function(name, param)
local toname = param
if toname == "" then
- return false, "Name field required"
+ return false, S("Name field required.")
end
local rc = core.remove_player(toname)
if rc == 0 then
core.log("action", name .. " removed player data of " .. toname .. ".")
- return true, "Player \"" .. toname .. "\" removed."
+ return true, S("Player \"@1\" removed.", toname)
elseif rc == 1 then
- return true, "No such player \"" .. toname .. "\" to remove."
+ return true, S("No such player \"@1\" to remove.", toname)
elseif rc == 2 then
- return true, "Player \"" .. toname .. "\" is connected, cannot remove."
+ return true, S("Player \"@1\" is connected, cannot remove.", toname)
end
- return false, "Unhandled remove_player return code " .. rc .. ""
+ return false, S("Unhandled remove_player return code @1.", tostring(rc))
end,
})
-core.register_chatcommand("teleport", {
- params = "<X>,<Y>,<Z> | <to_name> | (<name> <X>,<Y>,<Z>) | (<name> <to_name>)",
- description = "Teleport to position or player",
- 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
+
+-- pos may be a non-integer position
+local function find_free_position_near(pos)
+ local tries = {
+ vector.new( 1, 0, 0),
+ vector.new(-1, 0, 0),
+ vector.new( 0, 0, 1),
+ vector.new( 0, 0, -1),
+ }
+ for _, d in ipairs(tries) do
+ local p = vector.add(pos, d)
+ local n = core.get_node_or_nil(p)
+ if n then
+ local def = core.registered_nodes[n.name]
+ if def and not def.walkable then
+ return p
end
- return pos, false
end
+ end
+ return pos
+end
+
+-- Teleports player <name> to <p> if possible
+local function teleport_to_pos(name, p)
+ local lm = 31007 -- equals MAX_MAP_GENERATION_LIMIT in C++
+ if p.x < -lm or p.x > lm or p.y < -lm or p.y > lm
+ or p.z < -lm or p.z > lm then
+ return false, S("Cannot teleport out of map bounds!")
+ end
+ local teleportee = core.get_player_by_name(name)
+ if not teleportee then
+ return false, S("Cannot get player with name @1.", name)
+ end
+ if teleportee:get_attach() then
+ return false, S("Cannot teleport, @1 " ..
+ "is attached to an object!", name)
+ end
+ teleportee:set_pos(p)
+ return true, S("Teleporting @1 to @2.", name, core.pos_to_string(p, 1))
+end
+-- Teleports player <name> next to player <target_name> if possible
+local function teleport_to_player(name, target_name)
+ if name == target_name then
+ return false, S("One does not teleport to oneself.")
+ end
+ local teleportee = core.get_player_by_name(name)
+ if not teleportee then
+ return false, S("Cannot get teleportee with name @1.", name)
+ end
+ if teleportee:get_attach() then
+ return false, S("Cannot teleport, @1 " ..
+ "is attached to an object!", name)
+ end
+ local target = core.get_player_by_name(target_name)
+ if not target then
+ return false, S("Cannot get target player with name @1.", target_name)
+ end
+ local p = find_free_position_near(target:get_pos())
+ teleportee:set_pos(p)
+ return true, S("Teleporting @1 to @2 at @3.", name, target_name,
+ core.pos_to_string(p, 1))
+end
+
+core.register_chatcommand("teleport", {
+ params = S("<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>"),
+ description = S("Teleport to position or player"),
+ privs = {teleport=true},
+ func = function(name, param)
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)
+ p.x, p.y, p.z = param:match("^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
+ p = vector.apply(p, tonumber)
if p.x and p.y and p.z then
-
- local lm = 31000
- if p.x < -lm or p.x > lm or p.y < -lm or p.y > lm or p.z < -lm or p.z > lm then
- return false, "Cannot teleport out of map bounds!"
- end
- local teleportee = core.get_player_by_name(name)
- if teleportee then
- if teleportee:get_attach() then
- return false, "Can't teleport, you're attached to an object!"
- end
- teleportee:set_pos(p)
- return true, "Teleporting to "..core.pos_to_string(p)
- end
+ return teleport_to_pos(name, p)
end
local target_name = param:match("^([^ ]+)$")
- local teleportee = core.get_player_by_name(name)
-
- p = nil
if target_name then
- local target = core.get_player_by_name(target_name)
- if target then
- p = target:get_pos()
- end
+ return teleport_to_player(name, target_name)
end
- if teleportee and p then
- if teleportee:get_attach() then
- return false, "Can't teleport, you're attached to an object!"
- end
- p = find_free_position_near(p)
- teleportee:set_pos(p)
- return true, "Teleporting to " .. target_name
- .. " at "..core.pos_to_string(p)
- end
+ local has_bring_priv = core.check_player_privs(name, {bring=true})
+ local missing_bring_msg = S("You don't have permission to teleport " ..
+ "other players (missing privilege: @1).", "bring")
- 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
-
- teleportee = nil
- p = {}
local teleportee_name
teleportee_name, p.x, p.y, p.z = param:match(
"^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
- p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
- 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
- if teleportee:get_attach() then
- return false, "Can't teleport, player is attached to an object!"
+ p = vector.apply(p, tonumber)
+ if teleportee_name and p.x and p.y and p.z then
+ if not has_bring_priv then
+ return false, missing_bring_msg
end
- teleportee:set_pos(p)
- return true, "Teleporting " .. teleportee_name
- .. " to " .. core.pos_to_string(p)
+ return teleport_to_pos(teleportee_name, p)
end
- teleportee = nil
- p = 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:get_pos()
- end
- end
- if teleportee and p then
- if teleportee:get_attach() then
- return false, "Can't teleport, player is attached to an object!"
+ if teleportee_name and target_name then
+ if not has_bring_priv then
+ return false, missing_bring_msg
end
- p = find_free_position_near(p)
- teleportee:set_pos(p)
- return true, "Teleporting " .. teleportee_name
- .. " to " .. target_name
- .. " at " .. core.pos_to_string(p)
+ return teleport_to_player(teleportee_name, target_name)
end
- return false, 'Invalid parameters ("' .. param
- .. '") or player not found (see /help teleport)'
+ return false
end,
})
core.register_chatcommand("set", {
- params = "([-n] <name> <value>) | <name>",
- description = "Set or read server configuration setting",
+ params = S("([-n] <name> <value>) | <name>"),
+ description = S("Set or read server configuration setting"),
privs = {server=true},
func = function(name, param)
local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
@@ -550,22 +622,23 @@ core.register_chatcommand("set", {
setname, setvalue = string.match(param, "([^ ]+) (.+)")
if setname and setvalue then
if not core.settings:get(setname) then
- return false, "Failed. Use '/set -n <name> <value>' to create a new setting."
+ return false, S("Failed. Use '/set -n <name> <value>' "
+ .. "to create a new setting.")
end
core.settings:set(setname, setvalue)
- return true, setname .. " = " .. setvalue
+ return true, S("@1 = @2", setname, setvalue)
end
setname = string.match(param, "([^ ]+)")
if setname then
setvalue = core.settings:get(setname)
if not setvalue then
- setvalue = "<not set>"
+ setvalue = S("<not set>")
end
- return true, setname .. " = " .. setvalue
+ return true, S("@1 = @2", setname, setvalue)
end
- return false, "Invalid parameters (see /help set)."
+ return false, S("Invalid parameters (see /help set).")
end,
})
@@ -578,26 +651,27 @@ local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
if ctx.current_blocks == ctx.total_blocks then
core.chat_send_player(ctx.requestor_name,
- string.format("Finished emerging %d blocks in %.2fms.",
- ctx.total_blocks, (os.clock() - ctx.start_time) * 1000))
+ S("Finished emerging @1 blocks in @2ms.",
+ ctx.total_blocks,
+ string.format("%.2f", (os.clock() - ctx.start_time) * 1000)))
end
end
local function emergeblocks_progress_update(ctx)
if ctx.current_blocks ~= ctx.total_blocks then
core.chat_send_player(ctx.requestor_name,
- string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)",
+ S("emergeblocks update: @1/@2 blocks emerged (@3%)",
ctx.current_blocks, ctx.total_blocks,
- (ctx.current_blocks / ctx.total_blocks) * 100))
+ string.format("%.1f", (ctx.current_blocks / ctx.total_blocks) * 100)))
core.after(2, emergeblocks_progress_update, ctx)
end
end
core.register_chatcommand("emergeblocks", {
- params = "(here [<radius>]) | (<pos1> <pos2>)",
- description = "Load (or, if nonexistent, generate) map blocks "
- .. "contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)",
+ params = S("(here [<radius>]) | (<pos1> <pos2>)"),
+ description = S("Load (or, if nonexistent, generate) map blocks contained in "
+ .. "area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)"),
privs = {server=true},
func = function(name, param)
local p1, p2 = parse_range_str(name, param)
@@ -615,15 +689,15 @@ core.register_chatcommand("emergeblocks", {
core.emerge_area(p1, p2, emergeblocks_callback, context)
core.after(2, emergeblocks_progress_update, context)
- return true, "Started emerge of area ranging from " ..
- core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
+ return true, S("Started emerge of area ranging from @1 to @2.",
+ core.pos_to_string(p1, 1), core.pos_to_string(p2, 1))
end,
})
core.register_chatcommand("deleteblocks", {
- params = "(here [<radius>]) | (<pos1> <pos2>)",
- description = "Delete map blocks contained in area pos1 to pos2 "
- .. "(<pos1> and <pos2> must be in parentheses)",
+ params = S("(here [<radius>]) | (<pos1> <pos2>)"),
+ description = S("Delete map blocks contained in area pos1 to pos2 "
+ .. "(<pos1> and <pos2> must be in parentheses)"),
privs = {server=true},
func = function(name, param)
local p1, p2 = parse_range_str(name, param)
@@ -632,18 +706,20 @@ core.register_chatcommand("deleteblocks", {
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)
+ return true, S("Successfully cleared area "
+ .. "ranging from @1 to @2.",
+ core.pos_to_string(p1, 1), core.pos_to_string(p2, 1))
else
- return false, "Failed to clear one or more blocks in area"
+ return false, S("Failed to clear one or more "
+ .. "blocks in area.")
end
end,
})
core.register_chatcommand("fixlight", {
- params = "(here [<radius>]) | (<pos1> <pos2>)",
- description = "Resets lighting in the area between pos1 and pos2 "
- .. "(<pos1> and <pos2> must be in parentheses)",
+ params = S("(here [<radius>]) | (<pos1> <pos2>)"),
+ description = S("Resets lighting in the area between pos1 and pos2 "
+ .. "(<pos1> and <pos2> must be in parentheses)"),
privs = {server = true},
func = function(name, param)
local p1, p2 = parse_range_str(name, param)
@@ -652,20 +728,26 @@ core.register_chatcommand("fixlight", {
end
if core.fix_light(p1, p2) then
- return true, "Successfully reset light in the area ranging from " ..
- core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
+ return true, S("Successfully reset light in the area "
+ .. "ranging from @1 to @2.",
+ core.pos_to_string(p1, 1), core.pos_to_string(p2, 1))
else
- return false, "Failed to load one or more blocks in area"
+ return false, S("Failed to load one or more blocks in area.")
end
end,
})
core.register_chatcommand("mods", {
params = "",
- description = "List mods installed on the server",
+ description = S("List mods installed on the server"),
privs = {},
func = function(name, param)
- return true, table.concat(core.get_modnames(), ", ")
+ local mods = core.get_modnames()
+ if #mods == 0 then
+ return true, S("No mods installed.")
+ else
+ return true, table.concat(core.get_modnames(), ", ")
+ end
end,
})
@@ -674,117 +756,136 @@ local function handle_give_command(cmd, giver, receiver, stackstring)
.. ', stackstring="' .. stackstring .. '"')
local itemstack = ItemStack(stackstring)
if itemstack:is_empty() then
- return false, "Cannot give an empty item"
+ return false, S("Cannot give an empty item.")
elseif (not itemstack:is_known()) or (itemstack:get_name() == "unknown") then
- return false, "Cannot give an unknown item"
+ return false, S("Cannot give an unknown item.")
-- Forbid giving 'ignore' due to unwanted side effects
elseif itemstack:get_name() == "ignore" then
- return false, "Giving 'ignore' is not allowed"
+ return false, S("Giving 'ignore' is not allowed.")
end
local receiverref = core.get_player_by_name(receiver)
if receiverref == nil then
- return false, receiver .. " is not a known player"
+ return false, S("@1 is not a known player.", receiver)
end
local leftover = receiverref:get_inventory():add_item("main", itemstack)
local partiality
if leftover:is_empty() then
- partiality = ""
+ partiality = nil
elseif leftover:get_count() == itemstack:get_count() then
- partiality = "could not be "
+ partiality = false
else
- partiality = "partially "
+ partiality = true
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()
+ local msg
+ if partiality == true then
+ msg = S("@1 partially added to inventory.", stackstring)
+ elseif partiality == false then
+ msg = S("@1 could not be added to inventory.", stackstring)
+ else
+ msg = S("@1 added to inventory.", stackstring)
+ end
if giver == receiver then
- local msg = "%q %sadded to inventory."
- return true, msg:format(stackstring, partiality)
+ return true, msg
else
- core.chat_send_player(receiver, ("%q %sadded to inventory.")
- :format(stackstring, partiality))
- local msg = "%q %sadded to %s's inventory."
- return true, msg:format(stackstring, partiality, receiver)
+ core.chat_send_player(receiver, msg)
+ local msg_other
+ if partiality == true then
+ msg_other = S("@1 partially added to inventory of @2.",
+ stackstring, receiver)
+ elseif partiality == false then
+ msg_other = S("@1 could not be added to inventory of @2.",
+ stackstring, receiver)
+ else
+ msg_other = S("@1 added to inventory of @2.",
+ stackstring, receiver)
+ end
+ return true, msg_other
end
end
core.register_chatcommand("give", {
- params = "<name> <ItemString> [<count> [<wear>]]",
- description = "Give item to player",
+ params = S("<name> <ItemString> [<count> [<wear>]]"),
+ description = S("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"
+ return false, S("Name and ItemString required.")
end
return handle_give_command("/give", name, toname, itemstring)
end,
})
core.register_chatcommand("giveme", {
- params = "<ItemString> [<count> [<wear>]]",
- description = "Give item to yourself",
+ params = S("<ItemString> [<count> [<wear>]]"),
+ description = S("Give item to yourself"),
privs = {give=true},
func = function(name, param)
local itemstring = string.match(param, "(.+)$")
if not itemstring then
- return false, "ItemString required"
+ return false, S("ItemString required.")
end
return handle_give_command("/giveme", name, name, itemstring)
end,
})
core.register_chatcommand("spawnentity", {
- params = "<EntityName> [<X>,<Y>,<Z>]",
- description = "Spawn entity at given (or your) position",
+ params = S("<EntityName> [<X>,<Y>,<Z>]"),
+ description = S("Spawn entity at given (or your) position"),
privs = {give=true, interact=true},
func = function(name, param)
local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
if not entityname then
- return false, "EntityName required"
+ return false, S("EntityName required.")
end
core.log("action", ("%s invokes /spawnentity, entityname=%q")
:format(name, entityname))
local player = core.get_player_by_name(name)
if player == nil then
core.log("error", "Unable to spawn entity, player is nil")
- return false, "Unable to spawn entity, player is nil"
+ return false, S("Unable to spawn entity, player is nil.")
end
if not core.registered_entities[entityname] then
- return false, "Cannot spawn an unknown entity"
+ return false, S("Cannot spawn an unknown entity.")
end
if p == "" then
p = player:get_pos()
else
p = core.string_to_pos(p)
if p == nil then
- return false, "Invalid parameters ('" .. param .. "')"
+ return false, S("Invalid parameters (@1).", param)
end
end
p.y = p.y + 1
local obj = core.add_entity(p, entityname)
- local msg = obj and "%q spawned." or "%q failed to spawn."
- return true, msg:format(entityname)
+ if obj then
+ return true, S("@1 spawned.", entityname)
+ else
+ return true, S("@1 failed to spawn.", entityname)
+ end
end,
})
core.register_chatcommand("pulverize", {
params = "",
- description = "Destroy item in hand",
+ description = S("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."
+ return false, S("Unable to pulverize, no player.")
end
local wielded_item = player:get_wielded_item()
if wielded_item:is_empty() then
- return false, "Unable to pulverize, no item in hand."
+ return false, S("Unable to pulverize, no item in hand.")
end
core.log("action", name .. " pulverized \"" ..
wielded_item:get_name() .. " " .. wielded_item:get_count() .. "\"")
player:set_wielded_item(nil)
- return true, "An item was pulverized."
+ return true, S("An item was pulverized.")
end,
})
@@ -800,14 +901,15 @@ core.register_on_punchnode(function(pos, node, puncher)
end)
core.register_chatcommand("rollback_check", {
- params = "[<range>] [<seconds>] [<limit>]",
- description = "Check who last touched a node or a node near it"
- .. " within the time specified by <seconds>. Default: range = 0,"
- .. " seconds = 86400 = 24h, limit = 5. Set <seconds> to inf for no time limit",
+ params = S("[<range>] [<seconds>] [<limit>]"),
+ description = S("Check who last touched a node or a node near it "
+ .. "within the time specified by <seconds>. "
+ .. "Default: range = 0, seconds = 86400 = 24h, limit = 5. "
+ .. "Set <seconds> to inf for no time limit"),
privs = {rollback=true},
func = function(name, param)
if not core.settings:get_bool("enable_rollback_recording") then
- return false, "Rollback functions are disabled."
+ return false, S("Rollback functions are disabled.")
end
local range, seconds, limit =
param:match("(%d+) *(%d*) *(%d*)")
@@ -815,30 +917,30 @@ core.register_chatcommand("rollback_check", {
seconds = tonumber(seconds) or 86400
limit = tonumber(limit) or 5
if limit > 100 then
- return false, "That limit is too high!"
+ return false, S("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) .. "...")
+ core.chat_send_player(name, S("Checking @1 ...", 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")
+ core.chat_send_player(name, S("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")
+ core.chat_send_player(name,
+ S("Nobody has touched the specified "
+ .. "location in @1 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(
+ S("@1 @2 @3 -> @4 @5 seconds ago.",
core.pos_to_string(action.pos),
action.actor,
action.oldnode.name,
@@ -847,189 +949,235 @@ core.register_chatcommand("rollback_check", {
end
end
- return true, "Punch a node (range=" .. range .. ", seconds="
- .. seconds .. "s, limit=" .. limit .. ")"
+ return true, S("Punch a node (range=@1, seconds=@2, limit=@3).",
+ range, seconds, limit)
end,
})
core.register_chatcommand("rollback", {
- params = "(<name> [<seconds>]) | (:<actor> [<seconds>])",
- description = "Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit",
+ params = S("(<name> [<seconds>]) | (:<actor> [<seconds>])"),
+ description = S("Revert actions of a player. "
+ .. "Default for <seconds> is 60. "
+ .. "Set <seconds> to inf for no time limit"),
privs = {rollback=true},
func = function(name, param)
if not core.settings:get_bool("enable_rollback_recording") then
- return false, "Rollback functions are disabled."
+ return false, S("Rollback functions are disabled.")
end
local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
+ local rev_msg
if not target_name then
local player_name
player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
if not player_name then
- return false, "Invalid parameters. See /help rollback"
- .. " and /help rollback_check."
+ return false, S("Invalid parameters. "
+ .. "See /help rollback and "
+ .. "/help rollback_check.")
end
+ seconds = tonumber(seconds) or 60
target_name = "player:"..player_name
+ rev_msg = S("Reverting actions of player '@1' since @2 seconds.",
+ player_name, seconds)
+ else
+ seconds = tonumber(seconds) or 60
+ rev_msg = S("Reverting actions of @1 since @2 seconds.",
+ target_name, seconds)
end
- seconds = tonumber(seconds) or 60
- core.chat_send_player(name, "Reverting actions of "
- .. target_name .. " since "
- .. seconds .. " seconds.")
+ core.chat_send_player(name, rev_msg)
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"
+ response = S("(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.")
+ if success then
+ response = response .. S("Reverting actions succeeded.")
+ else
+ response = response .. S("Reverting actions FAILED.")
+ end
return success, response
end,
})
core.register_chatcommand("status", {
- description = "Show server status",
+ description = S("Show server status"),
func = function(name, param)
local status = core.get_server_status(name, false)
if status and status ~= "" then
return true, status
end
- return false, "This command was disabled by a mod or game"
+ return false, S("This command was disabled by a mod or game.")
end,
})
core.register_chatcommand("time", {
- params = "[<0..23>:<0..59> | <0..24000>]",
- description = "Show or set time of day",
+ params = S("[<0..23>:<0..59> | <0..24000>]"),
+ description = S("Show or set time of day"),
privs = {},
func = function(name, param)
if param == "" then
local current_time = math.floor(core.get_timeofday() * 1440)
local minutes = current_time % 60
local hour = (current_time - minutes) / 60
- return true, ("Current time is %d:%02d"):format(hour, minutes)
+ return true, S("Current time is @1:@2.",
+ string.format("%d", hour),
+ string.format("%02d", minutes))
end
local player_privs = core.get_player_privs(name)
if not player_privs.settime then
- return false, "You don't have permission to run this command " ..
- "(missing privilege: settime)."
+ return false, S("You don't have permission to run "
+ .. "this command (missing privilege: @1).", "settime")
end
local hour, minute = param:match("^(%d+):(%d+)$")
if not hour then
- local new_time = tonumber(param)
- if not new_time then
- return false, "Invalid time."
+ local new_time = tonumber(param) or -1
+ if new_time ~= new_time or new_time < 0 or new_time > 24000 then
+ return false, S("Invalid time (must be between 0 and 24000).")
end
- -- Backward compatibility.
- core.set_timeofday((new_time % 24000) / 24000)
+ core.set_timeofday(new_time / 24000)
core.log("action", name .. " sets time to " .. new_time)
- return true, "Time of day changed."
+ return true, S("Time of day changed.")
end
hour = tonumber(hour)
minute = tonumber(minute)
if hour < 0 or hour > 23 then
- return false, "Invalid hour (must be between 0 and 23 inclusive)."
+ return false, S("Invalid hour (must be between 0 and 23 inclusive).")
elseif minute < 0 or minute > 59 then
- return false, "Invalid minute (must be between 0 and 59 inclusive)."
+ return false, S("Invalid minute (must be between 0 and 59 inclusive).")
end
core.set_timeofday((hour * 60 + minute) / 1440)
core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
- return true, "Time of day changed."
+ return true, S("Time of day changed.")
end,
})
core.register_chatcommand("days", {
- description = "Show day count since world creation",
+ description = S("Show day count since world creation"),
func = function(name, param)
- return true, "Current day is " .. core.get_day_count()
+ return true, S("Current day is @1.", core.get_day_count())
end
})
+local function parse_shutdown_param(param)
+ local delay, reconnect, message
+ local one, two, three
+ one, two, three = param:match("^(%S+) +(%-r) +(.*)")
+ if one and two and three then
+ -- 3 arguments: delay, reconnect and message
+ return one, two, three
+ end
+ -- 2 arguments
+ one, two = param:match("^(%S+) +(.*)")
+ if one and two then
+ if tonumber(one) then
+ delay = one
+ if two == "-r" then
+ reconnect = two
+ else
+ message = two
+ end
+ elseif one == "-r" then
+ reconnect, message = one, two
+ end
+ return delay, reconnect, message
+ end
+ -- 1 argument
+ one = param:match("(.*)")
+ if tonumber(one) then
+ delay = one
+ elseif one == "-r" then
+ reconnect = one
+ else
+ message = one
+ end
+ return delay, reconnect, message
+end
+
core.register_chatcommand("shutdown", {
- params = "[<delay_in_seconds> | -1] [reconnect] [<message>]",
- description = "Shutdown server (-1 cancels a delayed shutdown)",
+ params = S("[<delay_in_seconds> | -1] [-r] [<message>]"),
+ description = S("Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)"),
privs = {server=true},
func = function(name, param)
- local delay, reconnect, message
- delay, param = param:match("^%s*(%S+)(.*)")
- if param then
- reconnect, param = param:match("^%s*(%S+)(.*)")
+ local delay, reconnect, message = parse_shutdown_param(param)
+ local bool_reconnect = reconnect == "-r"
+ if not message then
+ message = ""
end
- message = param and param:match("^%s*(.+)") or ""
delay = tonumber(delay) or 0
if delay == 0 then
core.log("action", name .. " shuts down server")
- core.chat_send_all("*** Server shutting down (operator request).")
+ core.chat_send_all("*** "..S("Server shutting down (operator request)."))
end
- core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
+ core.request_shutdown(message:trim(), bool_reconnect, delay)
return true
end,
})
core.register_chatcommand("ban", {
- params = "[<name>]",
- description = "Ban the IP of a player or show the ban list",
+ params = S("[<name>]"),
+ description = S("Ban the IP of a player or show the ban list"),
privs = {ban=true},
func = function(name, param)
if param == "" then
local ban_list = core.get_ban_list()
if ban_list == "" then
- return true, "The ban list is empty."
+ return true, S("The ban list is empty.")
else
- return true, "Ban list: " .. ban_list
+ return true, S("Ban list: @1", ban_list)
end
end
if not core.get_player_by_name(param) then
- return false, "Player is not online."
+ return false, S("Player is not online.")
end
if not core.ban_player(param) then
- return false, "Failed to ban player."
+ return false, S("Failed to ban player.")
end
local desc = core.get_ban_description(param)
core.log("action", name .. " bans " .. desc .. ".")
- return true, "Banned " .. desc .. "."
+ return true, S("Banned @1.", desc)
end,
})
core.register_chatcommand("unban", {
- params = "<name> | <IP_address>",
- description = "Remove IP ban belonging to a player/IP",
+ params = S("<name> | <IP_address>"),
+ description = S("Remove IP ban belonging to a player/IP"),
privs = {ban=true},
func = function(name, param)
if not core.unban_player_or_ip(param) then
- return false, "Failed to unban player/IP."
+ return false, S("Failed to unban player/IP.")
end
core.log("action", name .. " unbans " .. param)
- return true, "Unbanned " .. param
+ return true, S("Unbanned @1.", param)
end,
})
core.register_chatcommand("kick", {
- params = "<name> [<reason>]",
- description = "Kick a player",
+ params = S("<name> [<reason>]"),
+ description = S("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
+ return false, S("Failed to kick player @1.", 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
+ return true, S("Kicked @1.", tokick)
end,
})
core.register_chatcommand("clearobjects", {
- params = "[full | quick]",
- description = "Clear all objects in world",
+ params = S("[full | quick]"),
+ description = S("Clear all objects in world"),
privs = {server=true},
func = function(name, param)
local options = {}
@@ -1038,45 +1186,44 @@ core.register_chatcommand("clearobjects", {
elseif param == "full" then
options.mode = "full"
else
- return false, "Invalid usage, see /help clearobjects."
+ return false, S("Invalid usage, see /help clearobjects.")
end
- core.log("action", name .. " clears all objects ("
+ core.log("action", name .. " clears objects ("
.. options.mode .. " mode).")
- core.chat_send_all("Clearing all objects. This may take a long time."
- .. " You may experience a timeout. (by "
- .. name .. ")")
+ if options.mode == "full" then
+ core.chat_send_all(S("Clearing all objects. This may take a long time. "
+ .. "You may experience a timeout. (by @1)", name))
+ end
core.clear_objects(options)
core.log("action", "Object clearing done.")
- core.chat_send_all("*** Cleared all objects.")
+ core.chat_send_all("*** "..S("Cleared all objects."))
return true
end,
})
core.register_chatcommand("msg", {
- params = "<name> <message>",
- description = "Send a direct message to a player",
+ params = S("<name> <message>"),
+ description = S("Send a direct message to a player"),
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."
+ return false, S("Invalid usage, see /help msg.")
end
if not core.get_player_by_name(sendto) then
- return false, "The player " .. sendto
- .. " is not online."
+ return false, S("The player @1 is not online.", sendto)
end
core.log("action", "DM from " .. name .. " to " .. sendto
.. ": " .. message)
- core.chat_send_player(sendto, "DM from " .. name .. ": "
- .. message)
- return true, "Message sent."
+ core.chat_send_player(sendto, S("DM from @1: @2", name, message))
+ return true, S("Message sent.")
end,
})
core.register_chatcommand("last-login", {
- params = "[<name>]",
- description = "Get the last login time of a player or yourself",
+ params = S("[<name>]"),
+ description = S("Get the last login time of a player or yourself"),
func = function(name, param)
if param == "" then
param = name
@@ -1084,25 +1231,27 @@ core.register_chatcommand("last-login", {
local pauth = core.get_auth_handler().get_auth(param)
if pauth and pauth.last_login and pauth.last_login ~= -1 then
-- Time in UTC, ISO 8601 format
- return true, param.."'s last login time was " ..
- os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
+ return true, S("@1's last login time was @2.",
+ param,
+ os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login))
end
- return false, param.."'s last login time is unknown"
+ return false, S("@1's last login time is unknown.", param)
end,
})
core.register_chatcommand("clearinv", {
- params = "[<name>]",
- description = "Clear the inventory of yourself or another player",
+ params = S("[<name>]"),
+ description = S("Clear the inventory of yourself or another player"),
func = function(name, param)
local player
if param and param ~= "" and param ~= name then
if not core.check_player_privs(name, {server=true}) then
- return false, "You don't have permission"
- .. " to clear another player's inventory (missing privilege: server)"
+ return false, S("You don't have permission to "
+ .. "clear another player's inventory "
+ .. "(missing privilege: @1).", "server")
end
player = core.get_player_by_name(param)
- core.chat_send_player(param, name.." cleared your inventory.")
+ core.chat_send_player(param, S("@1 cleared your inventory.", name))
else
player = core.get_player_by_name(name)
end
@@ -1112,25 +1261,25 @@ core.register_chatcommand("clearinv", {
player:get_inventory():set_list("craft", {})
player:get_inventory():set_list("craftpreview", {})
core.log("action", name.." clears "..player:get_player_name().."'s inventory")
- return true, "Cleared "..player:get_player_name().."'s inventory."
+ return true, S("Cleared @1's inventory.", player:get_player_name())
else
- return false, "Player must be online to clear inventory!"
+ return false, S("Player must be online to clear inventory!")
end
end,
})
local function handle_kill_command(killer, victim)
if core.settings:get_bool("enable_damage") == false then
- return false, "Players can't be killed, damage has been disabled."
+ return false, S("Players can't be killed, damage has been disabled.")
end
local victimref = core.get_player_by_name(victim)
if victimref == nil then
- return false, string.format("Player %s is not online.", victim)
+ return false, S("Player @1 is not online.", victim)
elseif victimref:get_hp() <= 0 then
if killer == victim then
- return false, "You are already dead."
+ return false, S("You are already dead.")
else
- return false, string.format("%s is already dead.", victim)
+ return false, S("@1 is already dead.", victim)
end
end
if not killer == victim then
@@ -1138,12 +1287,12 @@ local function handle_kill_command(killer, victim)
end
-- Kill victim
victimref:set_hp(0)
- return true, string.format("%s has been killed.", victim)
+ return true, S("@1 has been killed.", victim)
end
core.register_chatcommand("kill", {
- params = "[<name>]",
- description = "Kill player or yourself",
+ params = S("[<name>]"),
+ description = S("Kill player or yourself"),
privs = {server=true},
func = function(name, param)
return handle_kill_command(name, param == "" and name or param)
diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua
index 057d0d0ed..29cb56aae 100644
--- a/builtin/game/falling.lua
+++ b/builtin/game/falling.lua
@@ -39,7 +39,7 @@ local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
core.register_entity(":__builtin:falling_node", {
initial_properties = {
visual = "item",
- visual_size = {x = SCALE, y = SCALE, z = SCALE},
+ visual_size = vector.new(SCALE, SCALE, SCALE),
textures = {},
physical = true,
is_visible = false,
@@ -84,9 +84,6 @@ core.register_entity(":__builtin:falling_node", {
local textures
if def.tiles and def.tiles[1] then
local tile = def.tiles[1]
- if def.drawtype == "torchlike" and def.paramtype2 ~= "wallmounted" then
- tile = def.tiles[2] or def.tiles[1]
- end
if type(tile) == "table" then
tile = tile.name
end
@@ -99,7 +96,7 @@ core.register_entity(":__builtin:falling_node", {
local vsize
if def.visual_scale then
local s = def.visual_scale
- vsize = {x = s, y = s, z = s}
+ vsize = vector.new(s, s, s)
end
self.object:set_properties({
is_visible = true,
@@ -114,15 +111,21 @@ core.register_entity(":__builtin:falling_node", {
itemstring = core.itemstring_with_palette(itemstring, node.param2)
end
-- FIXME: solution needed for paramtype2 == "leveled"
- local vsize
- if def.visual_scale then
- local s = def.visual_scale * SCALE
- vsize = {x = s, y = s, z = s}
+ -- Calculate size of falling node
+ local s = {}
+ s.x = (def.visual_scale or 1) * SCALE
+ s.y = s.x
+ s.z = s.x
+ -- Compensate for wield_scale
+ if def.wield_scale then
+ s.x = s.x / def.wield_scale.x
+ s.y = s.y / def.wield_scale.y
+ s.z = s.z / def.wield_scale.z
end
self.object:set_properties({
is_visible = true,
wield_item = itemstring,
- visual_size = vsize,
+ visual_size = s,
glow = def.light_source,
})
end
@@ -147,11 +150,7 @@ core.register_entity(":__builtin:falling_node", {
-- Rotate entity
if def.drawtype == "torchlike" then
- if def.paramtype2 == "wallmounted" then
- self.object:set_yaw(math.pi*0.25)
- else
- self.object:set_yaw(-math.pi*0.25)
- end
+ self.object:set_yaw(math.pi*0.25)
elseif ((node.param2 ~= 0 or def.drawtype == "nodebox" or def.drawtype == "mesh")
and (def.wield_image == "" or def.wield_image == nil))
or def.drawtype == "signlike"
@@ -165,8 +164,13 @@ core.register_entity(":__builtin:falling_node", {
if euler then
self.object:set_rotation(euler)
end
- elseif (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted") then
+ elseif (def.drawtype ~= "plantlike" and def.drawtype ~= "plantlike_rooted" and
+ (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted" or def.drawtype == "signlike")) then
local rot = node.param2 % 8
+ if (def.drawtype == "signlike" and def.paramtype2 ~= "wallmounted" and def.paramtype2 ~= "colorwallmounted") then
+ -- Change rotation to "floor" by default for non-wallmounted paramtype2
+ rot = 1
+ end
local pitch, yaw, roll = 0, 0, 0
if def.drawtype == "nodebox" or def.drawtype == "mesh" then
if rot == 0 then
@@ -208,6 +212,14 @@ core.register_entity(":__builtin:falling_node", {
end
end
self.object:set_rotation({x=pitch, y=yaw, z=roll})
+ elseif (def.drawtype == "mesh" and def.paramtype2 == "degrotate") then
+ local p2 = (node.param2 - (def.place_param2 or 0)) % 240
+ local yaw = (p2 / 240) * (math.pi * 2)
+ self.object:set_yaw(yaw)
+ elseif (def.drawtype == "mesh" and def.paramtype2 == "colordegrotate") then
+ local p2 = (node.param2 % 32 - (def.place_param2 or 0) % 32) % 24
+ local yaw = (p2 / 24) * (math.pi * 2)
+ self.object:set_yaw(yaw)
end
end
end,
@@ -222,7 +234,7 @@ core.register_entity(":__builtin:falling_node", {
on_activate = function(self, staticdata)
self.object:set_armor_groups({immortal = 1})
- self.object:set_acceleration({x = 0, y = -gravity, z = 0})
+ self.object:set_acceleration(vector.new(0, -gravity, 0))
local ds = core.deserialize(staticdata)
if ds and ds.node then
@@ -298,7 +310,7 @@ core.register_entity(":__builtin:falling_node", {
if self.floats then
local pos = self.object:get_pos()
- local bcp = vector.round({x = pos.x, y = pos.y - 0.7, z = pos.z})
+ local bcp = pos:offset(0, -0.7, 0):round()
local bcn = core.get_node(bcp)
local bcd = core.registered_nodes[bcn.name]
@@ -339,13 +351,12 @@ core.register_entity(":__builtin:falling_node", {
-- TODO: this hack could be avoided in the future if objects
-- could choose who to collide with
local vel = self.object:get_velocity()
- self.object:set_velocity({
- x = vel.x,
- y = player_collision.old_velocity.y,
- z = vel.z
- })
- self.object:set_pos(vector.add(self.object:get_pos(),
- {x = 0, y = -0.5, z = 0}))
+ self.object:set_velocity(vector.new(
+ vel.x,
+ player_collision.old_velocity.y,
+ vel.z
+ ))
+ self.object:set_pos(self.object:get_pos():offset(0, -0.5, 0))
end
return
elseif bcn.name == "ignore" then
@@ -407,7 +418,7 @@ local function convert_to_falling_node(pos, node)
obj:get_luaentity():set_node(node, metatable)
core.remove_node(pos)
- return true
+ return true, obj
end
function core.spawn_falling_node(pos)
@@ -425,7 +436,7 @@ local function drop_attached_node(p)
if def and def.preserve_metadata then
local oldmeta = core.get_meta(p):to_table().fields
-- Copy pos and node because the callback can modify them.
- local pos_copy = {x=p.x, y=p.y, z=p.z}
+ local pos_copy = vector.new(p)
local node_copy = {name=n.name, param1=n.param1, param2=n.param2}
local drop_stacks = {}
for k, v in pairs(drops) do
@@ -450,14 +461,14 @@ end
function builtin_shared.check_attached_node(p, n)
local def = core.registered_nodes[n.name]
- local d = {x = 0, y = 0, z = 0}
+ local d = vector.new()
if def.paramtype2 == "wallmounted" or
def.paramtype2 == "colorwallmounted" then
-- The fallback vector here is in case 'wallmounted to dir' is nil due
-- to voxelmanip placing a wallmounted node without resetting a
-- pre-existing param2 value that is out-of-range for wallmounted.
-- The fallback vector corresponds to param2 = 0.
- d = core.wallmounted_to_dir(n.param2) or {x = 0, y = 1, z = 0}
+ d = core.wallmounted_to_dir(n.param2) or vector.new(0, 1, 0)
else
d.y = -1
end
@@ -477,7 +488,7 @@ end
function core.check_single_for_falling(p)
local n = core.get_node(p)
if core.get_item_group(n.name, "falling_node") ~= 0 then
- local p_bottom = {x = p.x, y = p.y - 1, z = p.z}
+ local p_bottom = vector.offset(p, 0, -1, 0)
-- Only spawn falling node if node below is loaded
local n_bottom = core.get_node_or_nil(p_bottom)
local d_bottom = n_bottom and core.registered_nodes[n_bottom.name]
@@ -516,17 +527,17 @@ end
-- Down first as likely case, but always before self. The same with sides.
-- Up must come last, so that things above self will also fall all at once.
local check_for_falling_neighbors = {
- {x = -1, y = -1, z = 0},
- {x = 1, y = -1, z = 0},
- {x = 0, y = -1, z = -1},
- {x = 0, y = -1, z = 1},
- {x = 0, y = -1, z = 0},
- {x = -1, y = 0, z = 0},
- {x = 1, y = 0, z = 0},
- {x = 0, y = 0, z = 1},
- {x = 0, y = 0, z = -1},
- {x = 0, y = 0, z = 0},
- {x = 0, y = 1, z = 0},
+ vector.new(-1, -1, 0),
+ vector.new( 1, -1, 0),
+ vector.new( 0, -1, -1),
+ vector.new( 0, -1, 1),
+ vector.new( 0, -1, 0),
+ vector.new(-1, 0, 0),
+ vector.new( 1, 0, 0),
+ vector.new( 0, 0, 1),
+ vector.new( 0, 0, -1),
+ vector.new( 0, 0, 0),
+ vector.new( 0, 1, 0),
}
function core.check_for_falling(p)
diff --git a/builtin/game/features.lua b/builtin/game/features.lua
index 36ff1f0b0..583ef5092 100644
--- a/builtin/game/features.lua
+++ b/builtin/game/features.lua
@@ -19,6 +19,9 @@ core.features = {
object_step_has_moveresult = true,
direct_velocity_on_players = true,
use_texture_alpha_string_modes = true,
+ degrotate_240_steps = true,
+ abm_min_max_y = true,
+ dynamic_add_media_table = true,
}
function core.has_feature(arg)
diff --git a/builtin/game/forceloading.lua b/builtin/game/forceloading.lua
index e1e00920c..8043e5dea 100644
--- a/builtin/game/forceloading.lua
+++ b/builtin/game/forceloading.lua
@@ -86,12 +86,6 @@ local function read_file(filename)
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
@@ -106,7 +100,8 @@ end)
-- persists the currently forceloaded blocks to disk
local function persist_forceloaded_blocks()
- write_file(wpath.."/force_loaded.txt", blocks_forceloaded)
+ local data = core.serialize(blocks_forceloaded)
+ core.safe_file_write(wpath.."/force_loaded.txt", data)
end
-- periodical forceload persistence
diff --git a/builtin/game/init.lua b/builtin/game/init.lua
index 1d62be019..bb007fabd 100644
--- a/builtin/game/init.lua
+++ b/builtin/game/init.lua
@@ -7,8 +7,6 @@ local gamepath = scriptpath .. "game".. DIR_DELIM
-- not exposed to outer context
local builtin_shared = {}
-dofile(commonpath .. "vector.lua")
-
dofile(gamepath .. "constants.lua")
assert(loadfile(gamepath .. "item.lua"))(builtin_shared)
dofile(gamepath .. "register.lua")
diff --git a/builtin/game/item.lua b/builtin/game/item.lua
index b68177c22..5a83eafd2 100644
--- a/builtin/game/item.lua
+++ b/builtin/game/item.lua
@@ -92,12 +92,12 @@ end
-- Table of possible dirs
local facedir_to_dir = {
- {x= 0, y=0, z= 1},
- {x= 1, y=0, z= 0},
- {x= 0, y=0, z=-1},
- {x=-1, y=0, z= 0},
- {x= 0, y=-1, z= 0},
- {x= 0, y=1, z= 0},
+ vector.new( 0, 0, 1),
+ vector.new( 1, 0, 0),
+ vector.new( 0, 0, -1),
+ vector.new(-1, 0, 0),
+ vector.new( 0, -1, 0),
+ vector.new( 0, 1, 0),
}
-- Mapping from facedir value to index in facedir_to_dir.
local facedir_to_dir_map = {
@@ -136,12 +136,12 @@ end
-- table of dirs in wallmounted order
local wallmounted_to_dir = {
- [0] = {x = 0, y = 1, z = 0},
- {x = 0, y = -1, z = 0},
- {x = 1, y = 0, z = 0},
- {x = -1, y = 0, z = 0},
- {x = 0, y = 0, z = 1},
- {x = 0, y = 0, z = -1},
+ [0] = vector.new( 0, 1, 0),
+ vector.new( 0, -1, 0),
+ vector.new( 1, 0, 0),
+ vector.new(-1, 0, 0),
+ vector.new( 0, 0, 1),
+ vector.new( 0, 0, -1),
}
function core.wallmounted_to_dir(wallmounted)
return wallmounted_to_dir[wallmounted % 8]
@@ -152,12 +152,12 @@ function core.dir_to_yaw(dir)
end
function core.yaw_to_dir(yaw)
- return {x = -math.sin(yaw), y = 0, z = math.cos(yaw)}
+ return vector.new(-math.sin(yaw), 0, math.cos(yaw))
end
function core.is_colored_paramtype(ptype)
return (ptype == "color") or (ptype == "colorfacedir") or
- (ptype == "colorwallmounted")
+ (ptype == "colorwallmounted") or (ptype == "colordegrotate")
end
function core.strip_param2_color(param2, paramtype2)
@@ -168,11 +168,25 @@ function core.strip_param2_color(param2, paramtype2)
param2 = math.floor(param2 / 32) * 32
elseif paramtype2 == "colorwallmounted" then
param2 = math.floor(param2 / 8) * 8
+ elseif paramtype2 == "colordegrotate" then
+ param2 = math.floor(param2 / 32) * 32
end
-- paramtype2 == "color" requires no modification.
return param2
end
+local function has_all_groups(tbl, required_groups)
+ if type(required_groups) == "string" then
+ return (tbl[required_groups] or 0) ~= 0
+ end
+ for _, group in ipairs(required_groups) do
+ if (tbl[group] or 0) == 0 then
+ return false
+ end
+ end
+ return true
+end
+
function core.get_node_drops(node, toolname)
-- Compatibility, if node is string
local nodename = node
@@ -212,7 +226,7 @@ function core.get_node_drops(node, toolname)
if item.rarity ~= nil then
good_rarity = item.rarity < 1 or math.random(item.rarity) == 1
end
- if item.tools ~= nil then
+ if item.tools ~= nil or item.tool_groups ~= nil then
good_tool = false
end
if item.tools ~= nil and toolname then
@@ -227,6 +241,27 @@ function core.get_node_drops(node, toolname)
end
end
end
+ if item.tool_groups ~= nil and toolname then
+ local tooldef = core.registered_items[toolname]
+ if tooldef ~= nil and type(tooldef.groups) == "table" then
+ if type(item.tool_groups) == "string" then
+ -- tool_groups can be a string which specifies the required group
+ good_tool = core.get_item_group(toolname, item.tool_groups) ~= 0
+ else
+ -- tool_groups can be a list of sufficient requirements.
+ -- i.e. if any item in the list can be satisfied then the tool is good
+ assert(type(item.tool_groups) == "table")
+ for _, required_groups in ipairs(item.tool_groups) do
+ -- required_groups can be either a string (a single group),
+ -- or an array of strings where all must be in tooldef.groups
+ good_tool = has_all_groups(tooldef.groups, required_groups)
+ if good_tool then
+ break
+ end
+ end
+ end
+ end
+ end
if good_rarity and good_tool then
got_count = got_count + 1
for _, add_item in ipairs(item.items) do
@@ -288,12 +323,12 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
end
-- Place above pointed node
- local place_to = {x = above.x, y = above.y, z = above.z}
+ local place_to = vector.new(above)
-- If node under is buildable_to, place into it instead (eg. snow)
if olddef_under.buildable_to then
log("info", "node under is buildable to")
- place_to = {x = under.x, y = under.y, z = under.z}
+ place_to = vector.new(under)
end
if core.is_protected(place_to, playername) then
@@ -313,22 +348,14 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
newnode.param2 = def.place_param2
elseif (def.paramtype2 == "wallmounted" or
def.paramtype2 == "colorwallmounted") and not param2 then
- local dir = {
- x = under.x - above.x,
- y = under.y - above.y,
- z = under.z - above.z
- }
+ local dir = vector.subtract(under, above)
newnode.param2 = core.dir_to_wallmounted(dir)
-- Calculate the direction for furnaces and chests and stuff
elseif (def.paramtype2 == "facedir" or
def.paramtype2 == "colorfacedir") and not param2 then
local placer_pos = placer and placer:get_pos()
if placer_pos then
- local dir = {
- x = above.x - placer_pos.x,
- y = above.y - placer_pos.y,
- z = above.z - placer_pos.z
- }
+ local dir = vector.subtract(above, placer_pos)
newnode.param2 = core.dir_to_facedir(dir)
log("info", "facedir: " .. newnode.param2)
end
@@ -345,6 +372,8 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
color_divisor = 8
elseif def.paramtype2 == "colorfacedir" then
color_divisor = 32
+ elseif def.paramtype2 == "colordegrotate" then
+ color_divisor = 32
end
if color_divisor then
local color = math.floor(metatable.palette_index / color_divisor)
@@ -380,7 +409,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
-- Run callback
if def.after_place_node and not prevent_after_place 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 place_to_copy = vector.new(place_to)
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
if def.after_place_node(place_to_copy, placer, itemstack,
pointed_thing_copy) then
@@ -391,7 +420,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
-- Run script hook
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 place_to_copy = vector.new(place_to)
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)
@@ -470,34 +499,41 @@ function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed
return result
end
end
+ -- read definition before potentially emptying the stack
local def = itemstack:get_definition()
- if itemstack:take_item() ~= nil then
- user:set_hp(user:get_hp() + hp_change)
-
- if def and def.sound and def.sound.eat then
- core.sound_play(def.sound.eat, {
- pos = user:get_pos(),
- max_hear_distance = 16
- }, true)
- end
+ if itemstack:take_item():is_empty() then
+ return itemstack
+ end
+
+ if def and def.sound and def.sound.eat then
+ core.sound_play(def.sound.eat, {
+ pos = user:get_pos(),
+ max_hear_distance = 16
+ }, true)
+ end
- if replace_with_item then
- if itemstack:is_empty() then
- itemstack:add_item(replace_with_item)
+ -- Changing hp might kill the player causing mods to do who-knows-what to the
+ -- inventory, so do this before set_hp().
+ if replace_with_item then
+ if itemstack:is_empty() then
+ itemstack:add_item(replace_with_item)
+ else
+ local inv = user:get_inventory()
+ -- Check if inv is null, since non-players don't have one
+ if inv and inv:room_for_item("main", {name=replace_with_item}) then
+ inv:add_item("main", replace_with_item)
else
- local inv = user:get_inventory()
- -- Check if inv is null, since non-players don't have one
- if inv and inv:room_for_item("main", {name=replace_with_item}) then
- inv:add_item("main", replace_with_item)
- else
- local pos = user:get_pos()
- pos.y = math.floor(pos.y + 0.5)
- core.add_item(pos, replace_with_item)
- end
+ local pos = user:get_pos()
+ pos.y = math.floor(pos.y + 0.5)
+ core.add_item(pos, replace_with_item)
end
end
end
- return itemstack
+ user:set_wielded_item(itemstack)
+
+ user:set_hp(user:get_hp() + hp_change)
+
+ return nil -- don't overwrite wield item a second time
end
function core.item_eat(hp_change, replace_with_item)
@@ -537,11 +573,11 @@ function core.handle_node_drops(pos, drops, digger)
for _, dropped_item in pairs(drops) do
local left = give_item(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,
- }
+ local p = vector.offset(pos,
+ math.random()/2-0.25,
+ math.random()/2-0.25,
+ math.random()/2-0.25
+ )
core.add_item(p, left)
end
end
@@ -578,7 +614,7 @@ function core.node_dig(pos, node, digger)
if wielded then
local wdef = wielded:get_definition()
local tp = wielded:get_tool_capabilities()
- local dp = core.get_dig_params(def and def.groups, tp)
+ local dp = core.get_dig_params(def and def.groups, tp, wielded:get_wear())
if wdef and wdef.after_use then
wielded = wdef.after_use(wielded, digger, node, dp) or wielded
else
@@ -600,7 +636,7 @@ function core.node_dig(pos, node, digger)
if def and def.preserve_metadata then
local oldmeta = core.get_meta(pos):to_table().fields
-- Copy pos and node because the callback can modify them.
- local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
+ local pos_copy = vector.new(pos)
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
local drop_stacks = {}
for k, v in pairs(drops) do
@@ -632,7 +668,7 @@ function core.node_dig(pos, node, digger)
-- Run callback
if def and 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 pos_copy = vector.new(pos)
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
def.after_dig_node(pos_copy, node_copy, oldmetadata, digger)
end
@@ -645,7 +681,7 @@ function core.node_dig(pos, node, digger)
end
-- Copy pos and node because callback can modify them
- local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
+ local pos_copy = vector.new(pos)
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
callback(pos_copy, node_copy, digger)
end
@@ -688,7 +724,7 @@ core.nodedef_default = {
groups = {},
inventory_image = "",
wield_image = "",
- wield_scale = {x=1,y=1,z=1},
+ wield_scale = vector.new(1, 1, 1),
stack_max = default_stack_max,
usable = false,
liquids_pointable = false,
@@ -747,7 +783,7 @@ core.craftitemdef_default = {
groups = {},
inventory_image = "",
wield_image = "",
- wield_scale = {x=1,y=1,z=1},
+ wield_scale = vector.new(1, 1, 1),
stack_max = default_stack_max,
liquids_pointable = false,
tool_capabilities = nil,
@@ -766,7 +802,7 @@ core.tooldef_default = {
groups = {},
inventory_image = "",
wield_image = "",
- wield_scale = {x=1,y=1,z=1},
+ wield_scale = vector.new(1, 1, 1),
stack_max = 1,
liquids_pointable = false,
tool_capabilities = nil,
@@ -785,7 +821,7 @@ core.noneitemdef_default = { -- This is used for the hand and unknown items
groups = {},
inventory_image = "",
wield_image = "",
- wield_scale = {x=1,y=1,z=1},
+ wield_scale = vector.new(1, 1, 1),
stack_max = default_stack_max,
liquids_pointable = false,
tool_capabilities = nil,
diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua
index b8c5e16a9..e86efc50c 100644
--- a/builtin/game/misc.lua
+++ b/builtin/game/misc.lua
@@ -1,9 +1,21 @@
-- Minetest: builtin/misc.lua
+local S = core.get_translator("__builtin")
+
--
-- Misc. API functions
--
+-- @spec core.kick_player(String, String) :: Boolean
+function core.kick_player(player_name, reason)
+ if type(reason) == "string" then
+ reason = "Kicked: " .. reason
+ else
+ reason = "Kicked."
+ end
+ return core.disconnect_player(player_name, reason)
+end
+
function core.check_player_privs(name, ...)
if core.is_player(name) then
name = name:get_player_name()
@@ -42,15 +54,15 @@ end
function core.send_join_message(player_name)
if not core.is_singleplayer() then
- core.chat_send_all("*** " .. player_name .. " joined the game.")
+ core.chat_send_all("*** " .. S("@1 joined the game.", player_name))
end
end
function core.send_leave_message(player_name, timed_out)
- local announcement = "*** " .. player_name .. " left the game."
+ local announcement = "*** " .. S("@1 left the game.", player_name)
if timed_out then
- announcement = announcement .. " (timed out)"
+ announcement = "*** " .. S("@1 left the game (timed out).", player_name)
end
core.chat_send_all(announcement)
end
@@ -117,13 +129,12 @@ end
function core.get_position_from_hash(hash)
- local pos = {}
- pos.x = (hash % 65536) - 32768
+ local x = (hash % 65536) - 32768
hash = math.floor(hash / 65536)
- pos.y = (hash % 65536) - 32768
+ local y = (hash % 65536) - 32768
hash = math.floor(hash / 65536)
- pos.z = (hash % 65536) - 32768
- return pos
+ local z = (hash % 65536) - 32768
+ return vector.new(x, y, z)
end
@@ -213,7 +224,7 @@ function core.is_area_protected(minp, maxp, player_name, interval)
local y = math.floor(yf + 0.5)
for xf = minp.x, maxp.x, d.x do
local x = math.floor(xf + 0.5)
- local pos = {x = x, y = y, z = z}
+ local pos = vector.new(x, y, z)
if core.is_protected(pos, player_name) then
return pos
end
@@ -239,7 +250,7 @@ end
-- HTTP callback interface
-function core.http_add_fetch(httpenv)
+core.set_http_api_lua(function(httpenv)
httpenv.fetch = function(req, callback)
local handle = httpenv.fetch_async(req)
@@ -255,7 +266,8 @@ function core.http_add_fetch(httpenv)
end
return httpenv
-end
+end)
+core.set_http_api_lua = nil
function core.close_formspec(player_name, formname)
@@ -268,24 +280,44 @@ function core.cancel_shutdown_requests()
end
--- Callback handling for dynamic_add_media
+-- Used for callback handling with dynamic_add_media
+core.dynamic_media_callbacks = {}
+
-local dynamic_add_media_raw = core.dynamic_add_media_raw
-core.dynamic_add_media_raw = nil
-function core.dynamic_add_media(filepath, callback)
- local ret = dynamic_add_media_raw(filepath)
- if ret == false then
- return ret
+-- PNG encoder safety wrapper
+
+local o_encode_png = core.encode_png
+function core.encode_png(width, height, data, compression)
+ if type(width) ~= "number" then
+ error("Incorrect type for 'width', expected number, got " .. type(width))
end
- if callback == nil then
- core.log("deprecated", "Calling minetest.dynamic_add_media without "..
- "a callback is deprecated and will stop working in future versions.")
- else
- -- At the moment async loading is not actually implemented, so we
- -- immediately call the callback ourselves
- for _, name in ipairs(ret) do
- callback(name)
+ if type(height) ~= "number" then
+ error("Incorrect type for 'height', expected number, got " .. type(height))
+ end
+
+ local expected_byte_count = width * height * 4
+
+ if type(data) ~= "table" and type(data) ~= "string" then
+ error("Incorrect type for 'data', expected table or string, got " .. type(data))
+ end
+
+ local data_length = type(data) == "table" and #data * 4 or string.len(data)
+
+ if data_length ~= expected_byte_count then
+ error(string.format(
+ "Incorrect length of 'data', width and height imply %d bytes but %d were provided",
+ expected_byte_count,
+ data_length
+ ))
+ end
+
+ if type(data) == "table" then
+ local dataBuf = {}
+ for i = 1, #data do
+ dataBuf[i] = core.colorspec_to_bytes(data[i])
end
+ data = table.concat(dataBuf)
end
- return true
+
+ return o_encode_png(width, height, data, compression or 6)
end
diff --git a/builtin/game/privileges.lua b/builtin/game/privileges.lua
index c7417d2f4..2ff4c093c 100644
--- a/builtin/game/privileges.lua
+++ b/builtin/game/privileges.lua
@@ -1,5 +1,7 @@
-- Minetest: builtin/privileges.lua
+local S = core.get_translator("__builtin")
+
--
-- Privileges
--
@@ -15,7 +17,7 @@ function core.register_privilege(name, param)
def.give_to_admin = def.give_to_singleplayer
end
if def.description == nil then
- def.description = "(no description)"
+ def.description = S("(no description)")
end
end
local def
@@ -28,71 +30,76 @@ function core.register_privilege(name, param)
core.registered_privileges[name] = def
end
-core.register_privilege("interact", "Can interact with things and modify the world")
-core.register_privilege("shout", "Can speak in chat")
-core.register_privilege("basic_privs", "Can modify 'shout' and 'interact' privileges")
-core.register_privilege("privs", "Can modify privileges")
+core.register_privilege("interact", S("Can interact with things and modify the world"))
+core.register_privilege("shout", S("Can speak in chat"))
+
+local basic_privs =
+ core.string_to_privs((core.settings:get("basic_privs") or "shout,interact"))
+local basic_privs_desc = S("Can modify basic privileges (@1)",
+ core.privs_to_string(basic_privs, ', '))
+core.register_privilege("basic_privs", basic_privs_desc)
+
+core.register_privilege("privs", S("Can modify privileges"))
core.register_privilege("teleport", {
- description = "Can teleport self",
+ description = S("Can teleport self"),
give_to_singleplayer = false,
})
core.register_privilege("bring", {
- description = "Can teleport other players",
+ description = S("Can teleport other players"),
give_to_singleplayer = false,
})
core.register_privilege("settime", {
- description = "Can set the time of day using /time",
+ description = S("Can set the time of day using /time"),
give_to_singleplayer = false,
})
core.register_privilege("server", {
- description = "Can do server maintenance stuff",
+ description = S("Can do server maintenance stuff"),
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_privilege("protection_bypass", {
- description = "Can bypass node protection in the world",
+ description = S("Can bypass node protection in the world"),
give_to_singleplayer = false,
})
core.register_privilege("ban", {
- description = "Can ban and unban players",
+ description = S("Can ban and unban players"),
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_privilege("kick", {
- description = "Can kick players",
+ description = S("Can kick players"),
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_privilege("give", {
- description = "Can use /give and /giveme",
+ description = S("Can use /give and /giveme"),
give_to_singleplayer = false,
})
core.register_privilege("password", {
- description = "Can use /setpassword and /clearpassword",
+ description = S("Can use /setpassword and /clearpassword"),
give_to_singleplayer = false,
give_to_admin = true,
})
core.register_privilege("fly", {
- description = "Can use fly mode",
+ description = S("Can use fly mode"),
give_to_singleplayer = false,
})
core.register_privilege("fast", {
- description = "Can use fast mode",
+ description = S("Can use fast mode"),
give_to_singleplayer = false,
})
core.register_privilege("noclip", {
- description = "Can fly through solid nodes using noclip mode",
+ description = S("Can fly through solid nodes using noclip mode"),
give_to_singleplayer = false,
})
core.register_privilege("rollback", {
- description = "Can use the rollback functionality",
+ description = S("Can use the rollback functionality"),
give_to_singleplayer = false,
})
core.register_privilege("debug", {
- description = "Allows enabling various debug options that may affect gameplay",
+ description = S("Can enable wireframe"),
give_to_singleplayer = false,
- give_to_admin = true,
})
core.register_can_bypass_userlimit(function(name, ip)
diff --git a/builtin/game/register.lua b/builtin/game/register.lua
index 1cff85813..56e40c75c 100644
--- a/builtin/game/register.lua
+++ b/builtin/game/register.lua
@@ -1,5 +1,7 @@
-- Minetest: builtin/misc_register.lua
+local S = core.get_translator("__builtin")
+
--
-- Make raw registration functions inaccessible to anyone except this file
--
@@ -326,7 +328,7 @@ end
core.register_item(":unknown", {
type = "none",
- description = "Unknown Item",
+ description = S("Unknown Item"),
inventory_image = "unknown_item.png",
on_place = core.item_place,
on_secondary_use = core.item_secondary_use,
@@ -336,7 +338,7 @@ core.register_item(":unknown", {
})
core.register_node(":air", {
- description = "Air",
+ description = S("Air"),
inventory_image = "air.png",
wield_image = "air.png",
drawtype = "airlike",
@@ -353,7 +355,7 @@ core.register_node(":air", {
})
core.register_node(":ignore", {
- description = "Ignore",
+ description = S("Ignore"),
inventory_image = "ignore.png",
wield_image = "ignore.png",
drawtype = "airlike",
@@ -366,11 +368,12 @@ core.register_node(":ignore", {
air_equivalent = true,
drop = "",
groups = {not_in_creative_inventory=1},
+ node_placement_prediction = "",
on_place = function(itemstack, placer, pointed_thing)
core.chat_send_player(
placer:get_player_name(),
core.colorize("#FF0000",
- "You can't place 'ignore' nodes!"))
+ S("You can't place 'ignore' nodes!")))
return ""
end,
})
@@ -607,6 +610,7 @@ core.registered_on_modchannel_message, core.register_on_modchannel_message = mak
core.registered_on_player_inventory_actions, core.register_on_player_inventory_action = make_registration()
core.registered_allow_player_inventory_actions, core.register_allow_player_inventory_action = make_registration()
core.registered_on_rightclickplayers, core.register_on_rightclickplayer = make_registration()
+core.registered_on_liquid_transformed, core.register_on_liquid_transformed = make_registration()
--
-- Compatibility for on_mapgen_init()
diff --git a/builtin/game/voxelarea.lua b/builtin/game/voxelarea.lua
index 724761414..64436bf1a 100644
--- a/builtin/game/voxelarea.lua
+++ b/builtin/game/voxelarea.lua
@@ -1,6 +1,6 @@
VoxelArea = {
- MinEdge = {x=1, y=1, z=1},
- MaxEdge = {x=0, y=0, z=0},
+ MinEdge = vector.new(1, 1, 1),
+ MaxEdge = vector.new(0, 0, 0),
ystride = 0,
zstride = 0,
}
@@ -19,11 +19,11 @@ 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,
- }
+ return vector.new(
+ MaxEdge.x - MinEdge.x + 1,
+ MaxEdge.y - MinEdge.y + 1,
+ MaxEdge.z - MinEdge.z + 1
+ )
end
function VoxelArea:getVolume()
diff --git a/builtin/init.lua b/builtin/init.lua
index 89b1fdc64..7a9b5c427 100644
--- a/builtin/init.lua
+++ b/builtin/init.lua
@@ -30,6 +30,7 @@ local clientpath = scriptdir .. "client" .. DIR_DELIM
local commonpath = scriptdir .. "common" .. DIR_DELIM
local asyncpath = scriptdir .. "async" .. DIR_DELIM
+dofile(commonpath .. "vector.lua")
dofile(commonpath .. "strict.lua")
dofile(commonpath .. "serialize.lua")
dofile(commonpath .. "misc_helpers.lua")
diff --git a/builtin/locale/__builtin.de.tr b/builtin/locale/__builtin.de.tr
new file mode 100644
index 000000000..1b29f81e7
--- /dev/null
+++ b/builtin/locale/__builtin.de.tr
@@ -0,0 +1,245 @@
+# textdomain: __builtin
+Empty command.=Leerer Befehl.
+Invalid command: @1=Ungültiger Befehl: @1
+Invalid command usage.=Ungültige Befehlsverwendung.
+ (@1 s)= (@1 s)
+Command execution took @1 s=Befehlsausführung brauchte @1 s
+You don't have permission to run this command (missing privileges: @1).=Sie haben keine Erlaubnis, diesen Befehl auszuführen (fehlende Privilegien: @1).
+Unable to get position of player @1.=Konnte Position vom Spieler @1 nicht ermitteln.
+Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)=Ungültiges Gebietsformat. Erwartet: (x1,y1,z1) (x2,y2,z2)
+<action>=<Aktion>
+Show chat action (e.g., '/me orders a pizza' displays '<player name> orders a pizza')=Chataktion zeigen (z.B. wird „/me isst Pizza“ zu „<Spielername> isst Pizza“)
+Show the name of the server owner=Den Namen des Servereigentümers zeigen
+The administrator of this server is @1.=Der Administrator dieses Servers ist @1.
+There's no administrator named in the config file.=In der Konfigurationsdatei wurde kein Administrator angegeben.
+@1 does not have any privileges.=@1 hat keine Privilegien.
+Privileges of @1: @2=Privilegien von @1: @2
+[<name>]=[<Name>]
+Show privileges of yourself or another player=Ihre eigenen Privilegien oder die eines anderen Spielers anzeigen
+Player @1 does not exist.=Spieler @1 existiert nicht.
+<privilege>=<Privileg>
+Return list of all online players with privilege=Liste aller Spieler mit einem Privileg ausgeben
+Invalid parameters (see /help haspriv).=Ungültige Parameter (siehe „/help haspriv“).
+Unknown privilege!=Unbekanntes Privileg!
+No online player has the "@1" privilege.=Kein online spielender Spieler hat das „@1“-Privileg.
+Players online with the "@1" privilege: @2=Derzeit online spielende Spieler mit dem „@1“-Privileg: @2
+Your privileges are insufficient.=Ihre Privilegien sind unzureichend.
+Your privileges are insufficient. '@1' only allows you to grant: @2=Ihre Privilegien sind unzureichend. Mit „@1“ können Sie nur folgendes gewähren: @2
+Unknown privilege: @1=Unbekanntes Privileg: @1
+@1 granted you privileges: @2=@1 gewährte Ihnen Privilegien: @2
+<name> (<privilege> [, <privilege2> [<...>]] | all)=<Name> (<Privileg> [, <Privileg2> [<...>]] | all)
+Give privileges to player=Privileg an Spieler vergeben
+Invalid parameters (see /help grant).=Ungültige Parameter (siehe „/help grant“).
+<privilege> [, <privilege2> [<...>]] | all=<Privileg> [, <Privileg2> [<...>]] | all
+Grant privileges to yourself=Privilegien an Ihnen selbst vergeben
+Invalid parameters (see /help grantme).=Ungültige Parameter (siehe „/help grantme“).
+Your privileges are insufficient. '@1' only allows you to revoke: @2=Ihre Privilegien sind unzureichend. Mit „@1“ können Sie nur folgendes entziehen: @2
+Note: Cannot revoke in singleplayer: @1=Anmerkung: Im Einzelspielermodus kann man folgendes nicht entziehen: @1
+Note: Cannot revoke from admin: @1=Anmerkung: Vom Admin kann man folgendes nicht entziehen: @1
+No privileges were revoked.=Es wurden keine Privilegien entzogen.
+@1 revoked privileges from you: @2=@1 entfernte Privilegien von Ihnen: @2
+Remove privileges from player=Privilegien von Spieler entfernen
+Invalid parameters (see /help revoke).=Ungültige Parameter (siehe „/help revoke“).
+Revoke privileges from yourself=Privilegien von Ihnen selbst entfernen
+Invalid parameters (see /help revokeme).=Ungültige Parameter (siehe „/help revokeme“).
+<name> <password>=<Name> <Passwort>
+Set player's password=Passwort von Spieler setzen
+Name field required.=Namensfeld benötigt.
+Your password was cleared by @1.=Ihr Passwort wurde von @1 geleert.
+Password of player "@1" cleared.=Passwort von Spieler „@1“ geleert.
+Your password was set by @1.=Ihr Passwort wurde von @1 gesetzt.
+Password of player "@1" set.=Passwort von Spieler „@1“ gesetzt.
+<name>=<Name>
+Set empty password for a player=Leeres Passwort für einen Spieler setzen
+Reload authentication data=Authentifizierungsdaten erneut laden
+Done.=Fertig.
+Failed.=Fehlgeschlagen.
+Remove a player's data=Daten eines Spielers löschen
+Player "@1" removed.=Spieler „@1“ gelöscht.
+No such player "@1" to remove.=Es gibt keinen Spieler „@1“, der gelöscht werden könnte.
+Player "@1" is connected, cannot remove.=Spieler „@1“ ist verbunden, er kann nicht gelöscht werden.
+Unhandled remove_player return code @1.=Nicht berücksichtigter remove_player-Rückgabewert @1.
+Cannot teleport out of map bounds!=Eine Teleportation außerhalb der Kartengrenzen ist nicht möglich!
+Cannot get player with name @1.=Spieler mit Namen @1 kann nicht gefunden werden.
+Cannot teleport, @1 is attached to an object!=Teleportation nicht möglich, @1 ist an einem Objekt befestigt!
+Teleporting @1 to @2.=Teleportation von @1 nach @2
+One does not teleport to oneself.=Man teleportiert sich doch nicht zu sich selbst.
+Cannot get teleportee with name @1.=Der zu teleportierende Spieler mit Namen @1 kann nicht gefunden werden.
+Cannot get target player with name @1.=Zielspieler mit Namen @1 kann nicht gefunden werden.
+Teleporting @1 to @2 at @3.=Teleportation von @1 zu @2 bei @3
+<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>=<X>,<Y>,<Z> | <zu_Name> | <Name> <X>,<Y>,<Z> | <Name> <zu_Name>
+Teleport to position or player=Zu Position oder Spieler teleportieren
+You don't have permission to teleport other players (missing privilege: @1).=Sie haben nicht die Erlaubnis, andere Spieler zu teleportieren (fehlendes Privileg: @1).
+([-n] <name> <value>) | <name>=([-n] <Name> <Wert>) | <Name>
+Set or read server configuration setting=Serverkonfigurationseinstellung setzen oder lesen
+Failed. Use '/set -n <name> <value>' to create a new setting.=Fehlgeschlagen. Benutzen Sie „/set -n <Name> <Wert>“, um eine neue Einstellung zu erstellen.
+@1 @= @2=@1 @= @2
+<not set>=<nicht gesetzt>
+Invalid parameters (see /help set).=Ungültige Parameter (siehe „/help set“).
+Finished emerging @1 blocks in @2ms.=Fertig mit Erzeugung von @1 Blöcken in @2 ms.
+emergeblocks update: @1/@2 blocks emerged (@3%)=emergeblocks-Update: @1/@2 Kartenblöcke geladen (@3%)
+(here [<radius>]) | (<pos1> <pos2>)=(here [<Radius>]) | (<Pos1> <Pos2>)
+Load (or, if nonexistent, generate) map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Lade (oder, wenn nicht existent, generiere) Kartenblöcke im Gebiet zwischen Pos1 und Pos2 (<Pos1> und <Pos2> müssen in Klammern stehen)
+Started emerge of area ranging from @1 to @2.=Start des Ladevorgangs des Gebiets zwischen @1 und @2.
+Delete map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Kartenblöcke innerhalb des Gebiets zwischen Pos1 und Pos2 löschen (<Pos1> und <Pos2> müssen in Klammern stehen)
+Successfully cleared area ranging from @1 to @2.=Gebiet zwischen @1 und @2 erfolgreich geleert.
+Failed to clear one or more blocks in area.=Fehlgeschlagen: Ein oder mehrere Kartenblöcke im Gebiet konnten nicht geleert werden.
+Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in parentheses)=Setzt das Licht im Gebiet zwischen Pos1 und Pos2 zurück (<Pos1> und <Pos2> müssen in Klammern stehen)
+Successfully reset light in the area ranging from @1 to @2.=Das Licht im Gebiet zwischen @1 und @2 wurde erfolgreich zurückgesetzt.
+Failed to load one or more blocks in area.=Fehlgeschlagen: Ein oder mehrere Kartenblöcke im Gebiet konnten nicht geladen werden.
+List mods installed on the server=Installierte Mods auf dem Server auflisten
+No mods installed.=Es sind keine Mods installiert.
+Cannot give an empty item.=Ein leerer Gegenstand kann nicht gegeben werden.
+Cannot give an unknown item.=Ein unbekannter Gegenstand kann nicht gegeben werden.
+Giving 'ignore' is not allowed.=„ignore“ darf nicht gegeben werden.
+@1 is not a known player.=@1 ist kein bekannter Spieler.
+@1 partially added to inventory.=@1 teilweise ins Inventar eingefügt.
+@1 could not be added to inventory.=@1 konnte nicht ins Inventar eingefügt werden.
+@1 added to inventory.=@1 zum Inventar hinzugefügt.
+@1 partially added to inventory of @2.=@1 teilweise ins Inventar von @2 eingefügt.
+@1 could not be added to inventory of @2.=@1 konnte nicht ins Inventar von @2 eingefügt werden.
+@1 added to inventory of @2.=@1 ins Inventar von @2 eingefügt.
+<name> <ItemString> [<count> [<wear>]]=<Name> <ItemString> [<Anzahl> [<Abnutzung>]]
+Give item to player=Gegenstand an Spieler geben
+Name and ItemString required.=Name und ItemString benötigt.
+<ItemString> [<count> [<wear>]]=<ItemString> [<Anzahl> [<Abnutzung>]]
+Give item to yourself=Gegenstand Ihnen selbst geben
+ItemString required.=ItemString benötigt.
+<EntityName> [<X>,<Y>,<Z>]=<EntityName> [<X>,<Y>,<Z>]
+Spawn entity at given (or your) position=Entity an angegebener (oder Ihrer eigenen) Position spawnen
+EntityName required.=EntityName benötigt.
+Unable to spawn entity, player is nil.=Entity konnte nicht gespawnt werden, Spieler ist nil.
+Cannot spawn an unknown entity.=Ein unbekanntes Entity kann nicht gespawnt werden.
+Invalid parameters (@1).=Ungültige Parameter (@1).
+@1 spawned.=@1 gespawnt.
+@1 failed to spawn.=@1 konnte nicht gespawnt werden.
+Destroy item in hand=Gegenstand in der Hand zerstören
+Unable to pulverize, no player.=Konnte nicht pulverisieren, kein Spieler.
+Unable to pulverize, no item in hand.=Konnte nicht pulverisieren, kein Gegenstand in der Hand.
+An item was pulverized.=Ein Gegenstand wurde pulverisiert.
+[<range>] [<seconds>] [<limit>]=[<Reichweite>] [<Sekunden>] [<Limit>]
+Check who last touched a node or a node near it within the time specified by <seconds>. Default: range @= 0, seconds @= 86400 @= 24h, limit @= 5. Set <seconds> to inf for no time limit=Überprüfen, wer als letztes einen Node oder einen Node in der Nähe innerhalb der in <Sekunden> angegebenen Zeitspanne angefasst hat. Standard: Reichweite @= 0, Sekunden @= 86400 @= 24h, Limit @= 5. <Sekunden> auf „inf“ setzen, um Zeitlimit zu deaktivieren.
+Rollback functions are disabled.=Rollback-Funktionen sind deaktiviert.
+That limit is too high!=Dieses Limit ist zu hoch!
+Checking @1 ...=Überprüfe @1 ...
+Nobody has touched the specified location in @1 seconds.=Niemand hat die angegebene Position seit @1 Sekunden angefasst.
+@1 @2 @3 -> @4 @5 seconds ago.=@1 @2 @3 -> @4 vor @5 Sekunden.
+Punch a node (range@=@1, seconds@=@2, limit@=@3).=Hauen Sie einen Node (Reichweite@=@1, Sekunden@=@2, Limit@=@3).
+(<name> [<seconds>]) | (:<actor> [<seconds>])=(<Name> [<Sekunden>]) | (:<Akteur> [<Sekunden>])
+Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit=Aktionen eines Spielers zurückrollen. Standard für <Sekunden> ist 60. <Sekunden> auf „inf“ setzen, um Zeitlimit zu deaktivieren
+Invalid parameters. See /help rollback and /help rollback_check.=Ungültige Parameter. Siehe /help rollback und /help rollback_check.
+Reverting actions of player '@1' since @2 seconds.=Die Aktionen des Spielers „@1“ seit @2 Sekunden werden rückgängig gemacht.
+Reverting actions of @1 since @2 seconds.=Die Aktionen von @1 seit @2 Sekunden werden rückgängig gemacht.
+(log is too long to show)=(Protokoll ist zu lang für die Anzeige)
+Reverting actions succeeded.=Die Aktionen wurden erfolgreich rückgängig gemacht.
+Reverting actions FAILED.=FEHLGESCHLAGEN: Die Aktionen konnten nicht rückgängig gemacht werden.
+Show server status=Serverstatus anzeigen
+This command was disabled by a mod or game.=Dieser Befehl wurde von einer Mod oder einem Spiel deaktiviert.
+[<0..23>:<0..59> | <0..24000>]=[<0..23>:<0..59> | <0..24000>]
+Show or set time of day=Tageszeit anzeigen oder setzen
+Current time is @1:@2.=Es ist jetzt @1:@2 Uhr.
+You don't have permission to run this command (missing privilege: @1).=Sie haben nicht die Erlaubnis, diesen Befehl auszuführen (fehlendes Privileg: @1).
+Invalid time (must be between 0 and 24000).=Ungültige Zeit (muss zwischen 0 und 24000 liegen).
+Time of day changed.=Tageszeit geändert.
+Invalid hour (must be between 0 and 23 inclusive).=Ungültige Stunde (muss zwischen 0 und 23 inklusive liegen).
+Invalid minute (must be between 0 and 59 inclusive).=Ungültige Minute (muss zwischen 0 und 59 inklusive liegen).
+Show day count since world creation=Anzahl Tage seit der Erschaffung der Welt anzeigen
+Current day is @1.=Aktueller Tag ist @1.
+[<delay_in_seconds> | -1] [-r] [<message>]=[<Verzögerung_in_Sekunden> | -1] [-r] [<Nachricht>]
+Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)=Server herunterfahren (-1 bricht einen verzögerten Abschaltvorgang ab, -r erlaubt Spielern, sich wiederzuverbinden)
+Server shutting down (operator request).=Server wird heruntergefahren (Betreiberanfrage).
+Ban the IP of a player or show the ban list=Die IP eines Spielers verbannen oder die Bannliste anzeigen
+The ban list is empty.=Die Bannliste ist leer.
+Ban list: @1=Bannliste: @1
+Player is not online.=Spieler ist nicht online.
+Failed to ban player.=Konnte Spieler nicht verbannen.
+Banned @1.=@1 verbannt.
+<name> | <IP_address>=<Name> | <IP_Adresse>
+Remove IP ban belonging to a player/IP=Einen IP-Bann auf einen Spieler zurücknehmen
+Failed to unban player/IP.=Konnte Bann auf Spieler/IP nicht zurücknehmen.
+Unbanned @1.=Bann auf @1 zurückgenommen.
+<name> [<reason>]=<Name> [<Grund>]
+Kick a player=Spieler hinauswerfen
+Failed to kick player @1.=Spieler @1 konnte nicht hinausgeworfen werden.
+Kicked @1.=@1 hinausgeworfen.
+[full | quick]=[full | quick]
+Clear all objects in world=Alle Objekte in der Welt löschen
+Invalid usage, see /help clearobjects.=Ungültige Verwendung, siehe /help clearobjects.
+Clearing all objects. This may take a long time. You may experience a timeout. (by @1)=Lösche alle Objekte. Dies kann eine lange Zeit dauern. Eine Netzwerkzeitüberschreitung könnte für Sie auftreten. (von @1)
+Cleared all objects.=Alle Objekte gelöscht.
+<name> <message>=<Name> <Nachricht>
+Send a direct message to a player=Eine Direktnachricht an einen Spieler senden
+Invalid usage, see /help msg.=Ungültige Verwendung, siehe /help msg.
+The player @1 is not online.=Der Spieler @1 ist nicht online.
+DM from @1: @2=DN von @1: @2
+Message sent.=Nachricht gesendet.
+Get the last login time of a player or yourself=Den letzten Loginzeitpunkt eines Spielers oder Ihren eigenen anfragen
+@1's last login time was @2.=Letzter Loginzeitpunkt von @1 war @2.
+@1's last login time is unknown.=Letzter Loginzeitpunkt von @1 ist unbekannt.
+Clear the inventory of yourself or another player=Das Inventar von Ihnen oder einem anderen Spieler leeren
+You don't have permission to clear another player's inventory (missing privilege: @1).=Sie haben nicht die Erlaubnis, das Inventar eines anderen Spielers zu leeren (fehlendes Privileg: @1).
+@1 cleared your inventory.=@1 hat Ihr Inventar geleert.
+Cleared @1's inventory.=Inventar von @1 geleert.
+Player must be online to clear inventory!=Spieler muss online sein, um das Inventar leeren zu können!
+Players can't be killed, damage has been disabled.=Spieler können nicht getötet werden, Schaden ist deaktiviert.
+Player @1 is not online.=Spieler @1 ist nicht online.
+You are already dead.=Sie sind schon tot.
+@1 is already dead.=@1 ist bereits tot.
+@1 has been killed.=@1 wurde getötet.
+Kill player or yourself=Einen Spieler oder Sie selbst töten
+Invalid parameters (see /help @1).=Ungültige Parameter (siehe „/help @1“).
+Too many arguments, try using just /help <command>=Zu viele Argumente. Probieren Sie es mit „/help <Befehl>“
+Available commands: @1=Verfügbare Befehle: @1
+Use '/help <cmd>' to get more information, or '/help all' to list everything.=„/help <Befehl>“ benutzen, um mehr Informationen zu erhalten, oder „/help all“, um alles aufzulisten.
+Available commands:=Verfügbare Befehle:
+Command not available: @1=Befehl nicht verfügbar: @1
+[all | privs | <cmd>] [-t]=[all | privs | <Befehl>] [-t]
+Get help for commands or list privileges (-t: output in chat)=Hilfe für Befehle erhalten oder Privilegien auflisten (-t: Ausgabe im Chat)
+Available privileges:=Verfügbare Privilegien:
+Command=Befehl
+Parameters=Parameter
+For more information, click on any entry in the list.=Für mehr Informationen klicken Sie auf einen beliebigen Eintrag in der Liste.
+Double-click to copy the entry to the chat history.=Doppelklicken, um den Eintrag in die Chathistorie einzufügen.
+Command: @1 @2=Befehl: @1 @2
+Available commands: (see also: /help <cmd>)=Verfügbare Befehle: (siehe auch: /help <Befehl>)
+Close=Schließen
+Privilege=Privileg
+Description=Beschreibung
+print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=print [<Filter>] | dump [<Filter>] | save [<Format> [<Filter>]]
+Handle the profiler and profiling data=Den Profiler und Profilingdaten verwalten
+Statistics written to action log.=Statistiken zum Aktionsprotokoll geschrieben.
+Statistics were reset.=Statistiken wurden zurückgesetzt.
+Usage: @1=Verwendung: @1
+Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).=Format kann entweder „txt“, „csv“, „lua“, „json“ oder „json_pretty“ sein (die Struktur kann sich in Zukunft ändern).
+@1 joined the game.=@1 ist dem Spiel beigetreten.
+@1 left the game.=@1 hat das Spiel verlassen.
+@1 left the game (timed out).=@1 hat das Spiel verlassen (Netzwerkzeitüberschreitung).
+(no description)=(keine Beschreibung)
+Can interact with things and modify the world=Kann mit Dingen interagieren und die Welt verändern
+Can speak in chat=Kann im Chat sprechen
+Can modify basic privileges (@1)=Kann grundlegende Privilegien anpassen (@1)
+Can modify privileges=Kann Privilegien anpassen
+Can teleport self=Kann sich selbst teleportieren
+Can teleport other players=Kann andere Spieler teleportieren
+Can set the time of day using /time=Kann die Tageszeit mit /time setzen
+Can do server maintenance stuff=Kann Serverwartungsdinge machen
+Can bypass node protection in the world=Kann den Schutz auf Blöcken in der Welt umgehen
+Can ban and unban players=Kann Spieler verbannen und entbannen
+Can kick players=Kann Spieler hinauswerfen
+Can use /give and /giveme=Kann /give und /giveme benutzen
+Can use /setpassword and /clearpassword=Kann /setpassword und /clearpassword benutzen
+Can use fly mode=Kann den Flugmodus benutzen
+Can use fast mode=Kann den Schnellmodus benutzen
+Can fly through solid nodes using noclip mode=Kann durch feste Blöcke mit dem Geistmodus fliegen
+Can use the rollback functionality=Kann die Rollback-Funktionalität benutzen
+Can view more debug info that might give a gameplay advantage=Kann zusätzliche Debuginformationen betrachten, welche einen spielerischen Vorteil geben könnten
+Can enable wireframe=Kann Drahtmodell aktivieren
+Unknown Item=Unbekannter Gegenstand
+Air=Luft
+Ignore=Ignorieren
+You can't place 'ignore' nodes!=Sie können keine „ignore“-Blöcke platzieren!
+Values below show absolute/relative times spend per server step by the instrumented function.=Die unten angegebenen Werte zeigen absolute/relative Zeitspannen, die je Server-Step von der instrumentierten Funktion in Anspruch genommen wurden.
+A total of @1 sample(s) were taken.=Es wurden insgesamt @1 Datenpunkt(e) aufgezeichnet.
+The output is limited to '@1'.=Die Ausgabe ist beschränkt auf „@1“.
+Saving of profile failed: @1=Speichern des Profils fehlgeschlagen: @1
+Profile saved to @1=Profil abgespeichert nach @1
diff --git a/builtin/locale/__builtin.it.tr b/builtin/locale/__builtin.it.tr
new file mode 100644
index 000000000..77f85c766
--- /dev/null
+++ b/builtin/locale/__builtin.it.tr
@@ -0,0 +1,258 @@
+# textdomain: __builtin
+Empty command.=Comando vuoto.
+Invalid command: @1=Comando non valido: @1
+Invalid command usage.=Utilizzo del comando non valido.
+ (@1 s)=
+Command execution took @1 s=
+You don't have permission to run this command (missing privileges: @1).=Non hai il permesso di eseguire questo comando (privilegi mancanti: @1).
+Unable to get position of player @1.=Impossibile ottenere la posizione del giocatore @1.
+Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)=Formato dell'area non corretto. Richiesto: (x1,y1,z1) (x2,y2,z2)
+<action>=<azione>
+Show chat action (e.g., '/me orders a pizza' displays '<player name> orders a pizza')=Mostra un'azione in chat (es. `/me ordina una pizza` mostra `<nome giocatore> ordina una pizza`)
+Show the name of the server owner=Mostra il nome del proprietario del server
+The administrator of this server is @1.=L'amministratore di questo server è @1.
+There's no administrator named in the config file.=Non c'è nessun amministratore nel file di configurazione.
+@1 does not have any privileges.=
+Privileges of @1: @2=Privilegi di @1: @2
+[<name>]=[<nome>]
+Show privileges of yourself or another player=Mostra i privilegi propri o di un altro giocatore
+Player @1 does not exist.=Il giocatore @1 non esiste.
+<privilege>=<privilegio>
+Return list of all online players with privilege=Ritorna una lista di tutti i giocatori connessi col tale privilegio
+Invalid parameters (see /help haspriv).=Parametri non validi (vedi /help haspriv).
+Unknown privilege!=Privilegio sconosciuto!
+No online player has the "@1" privilege.=
+Players online with the "@1" privilege: @2=Giocatori connessi con il privilegio "@1": @2
+Your privileges are insufficient.=I tuoi privilegi sono insufficienti.
+Your privileges are insufficient. '@1' only allows you to grant: @2=
+Unknown privilege: @1=Privilegio sconosciuto: @1
+@1 granted you privileges: @2=@1 ti ha assegnato i seguenti privilegi: @2
+<name> (<privilege> [, <privilege2> [<...>]] | all)=
+Give privileges to player=Dà privilegi al giocatore
+Invalid parameters (see /help grant).=Parametri non validi (vedi /help grant).
+<privilege> [, <privilege2> [<...>]] | all=
+Grant privileges to yourself=Assegna dei privilegi a te stessǝ
+Invalid parameters (see /help grantme).=Parametri non validi (vedi /help grantme).
+Your privileges are insufficient. '@1' only allows you to revoke: @2=
+Note: Cannot revoke in singleplayer: @1=
+Note: Cannot revoke from admin: @1=
+No privileges were revoked.=
+@1 revoked privileges from you: @2=@1 ti ha revocato i seguenti privilegi: @2
+Remove privileges from player=Rimuove privilegi dal giocatore
+Invalid parameters (see /help revoke).=Parametri non validi (vedi /help revoke).
+Revoke privileges from yourself=Revoca privilegi a te stessǝ
+Invalid parameters (see /help revokeme).=Parametri non validi (vedi /help revokeme).
+<name> <password>=<nome> <password>
+Set player's password=Imposta la password del giocatore
+Name field required.=Campo "nome" richiesto.
+Your password was cleared by @1.=La tua password è stata resettata da @1.
+Password of player "@1" cleared.=Password del giocatore "@1" resettata.
+Your password was set by @1.=La tua password è stata impostata da @1.
+Password of player "@1" set.=Password del giocatore "@1" impostata.
+<name>=<nome>
+Set empty password for a player=Imposta una password vuota a un giocatore
+Reload authentication data=Ricarica i dati d'autenticazione
+Done.=Fatto.
+Failed.=Errore.
+Remove a player's data=Rimuove i dati di un giocatore
+Player "@1" removed.=Giocatore "@1" rimosso.
+No such player "@1" to remove.=Non è presente nessun giocatore "@1" da rimuovere.
+Player "@1" is connected, cannot remove.=Il giocatore "@1" è connesso, non può essere rimosso.
+Unhandled remove_player return code @1.=Codice ritornato da remove_player non gestito (@1).
+Cannot teleport out of map bounds!=Non ci si può teletrasportare fuori dai limiti della mappa!
+Cannot get player with name @1.=Impossibile trovare il giocatore chiamato @1.
+Cannot teleport, @1 is attached to an object!=Impossibile teletrasportare, @1 è attaccato a un oggetto!
+Teleporting @1 to @2.=Teletrasportando @1 da @2.
+One does not teleport to oneself.=Non ci si può teletrasportare su se stessi.
+Cannot get teleportee with name @1.=Impossibile trovare il giocatore chiamato @1 per il teletrasporto
+Cannot get target player with name @1.=Impossibile trovare il giocatore chiamato @1 per il teletrasporto
+Teleporting @1 to @2 at @3.=Teletrasportando @1 da @2 a @3
+<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>=<X>,<Y>,<Z> | <da_nome> | <nome> <X>,<Y>,<Z> | <nome> <da_nome>
+Teleport to position or player=Teletrasporta a una posizione o da un giocatore
+You don't have permission to teleport other players (missing privilege: @1).=Non hai il permesso di teletrasportare altri giocatori (privilegio mancante: @1).
+([-n] <name> <value>) | <name>=([-n] <nome> <valore>) | <nome>
+Set or read server configuration setting=Imposta o ottieni le configurazioni del server
+Failed. Use '/set -n <name> <value>' to create a new setting.=Errore. Usa 'set -n <nome> <valore>' per creare una nuova impostazione
+@1 @= @2=@1 @= @2
+<not set>=<non impostato>
+Invalid parameters (see /help set).=Parametri non validi (vedi /help set).
+Finished emerging @1 blocks in @2ms.=Finito di emergere @1 blocchi in @2ms
+emergeblocks update: @1/@2 blocks emerged (@3%)=aggiornamento emergeblocks: @1/@2 blocchi emersi (@3%)
+(here [<radius>]) | (<pos1> <pos2>)=(here [<raggio>]) | (<pos1> <pos2>)
+Load (or, if nonexistent, generate) map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Carica (o, se non esiste, genera) blocchi mappa contenuti nell'area tra pos1 e pos2 (<pos1> e <pos2> vanno tra parentesi)
+Started emerge of area ranging from @1 to @2.=Iniziata emersione dell'area tra @1 e @2.
+Delete map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Cancella i blocchi mappa contenuti nell'area tra pos1 e pos2 (<pos1> e <pos2> vanno tra parentesi)
+Successfully cleared area ranging from @1 to @2.=Area tra @1 e @2 ripulita con successo.
+Failed to clear one or more blocks in area.=Errore nel ripulire uno o più blocchi mappa nell'area
+Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in parentheses)=Reimposta l'illuminazione nell'area tra pos1 e po2 (<pos1> e <pos2> vanno tra parentesi)
+Successfully reset light in the area ranging from @1 to @2.=Luce nell'area tra @1 e @2 reimpostata con successo.
+Failed to load one or more blocks in area.=Errore nel caricare uno o più blocchi mappa nell'area.
+List mods installed on the server=Elenca le mod installate nel server
+No mods installed.=
+Cannot give an empty item.=Impossibile dare un oggetto vuoto.
+Cannot give an unknown item.=Impossibile dare un oggetto sconosciuto.
+Giving 'ignore' is not allowed.=Non è permesso dare 'ignore'.
+@1 is not a known player.=@1 non è un giocatore conosciuto.
+@1 partially added to inventory.=@1 parzialmente aggiunto all'inventario.
+@1 could not be added to inventory.=@1 non può essere aggiunto all'inventario.
+@1 added to inventory.=@1 aggiunto all'inventario.
+@1 partially added to inventory of @2.=@1 parzialmente aggiunto all'inventario di @2.
+@1 could not be added to inventory of @2.=Non è stato possibile aggiungere @1 all'inventario di @2.
+@1 added to inventory of @2.=@1 aggiunto all'inventario di @2.
+<name> <ItemString> [<count> [<wear>]]=<nome> <NomeOggetto> [<quantità> [<usura>]]
+Give item to player=Dà oggetti ai giocatori
+Name and ItemString required.=Richiesti nome e NomeOggetto.
+<ItemString> [<count> [<wear>]]=<NomeOggetto> [<quantità> [<usura>]]
+Give item to yourself=Dà oggetti a te stessǝ
+ItemString required.=Richiesto NomeOggetto.
+<EntityName> [<X>,<Y>,<Z>]=<NomeEntità> [<X>,<Y>,<Z>]
+Spawn entity at given (or your) position=Genera un'entità alla data coordinata (o la tua)
+EntityName required.=Richiesto NomeEntità
+Unable to spawn entity, player is nil.=Impossibile generare l'entità, il giocatore è nil.
+Cannot spawn an unknown entity.=Impossibile generare un'entità sconosciuta.
+Invalid parameters (@1).=Parametri non validi (@1).
+@1 spawned.=Generata entità @1.
+@1 failed to spawn.=Errore nel generare @1
+Destroy item in hand=Distrugge l'oggetto in mano
+Unable to pulverize, no player.=Impossibile polverizzare, nessun giocatore.
+Unable to pulverize, no item in hand.=Impossibile polverizzare, nessun oggetto in mano.
+An item was pulverized.=Un oggetto è stato polverizzato.
+[<range>] [<seconds>] [<limit>]=[<raggio>] [<secondi>] [<limite>]
+Check who last touched a node or a node near it within the time specified by <seconds>. Default: range @= 0, seconds @= 86400 @= 24h, limit @= 5. Set <seconds> to inf for no time limit=Controlla chi è l'ultimo giocatore che ha toccato un nodo o un nodo nelle sue vicinanze, negli ultimi secondi indicati. Di base: raggio @= 0, secondi @= 86400 @= 24h, limite @= 5.
+Rollback functions are disabled.=Le funzioni di rollback sono disabilitate.
+That limit is too high!=Il limite è troppo alto!
+Checking @1 ...=Controllando @1 ...
+Nobody has touched the specified location in @1 seconds.=Nessuno ha toccato il punto specificato negli ultimi @1 secondi.
+@1 @2 @3 -> @4 @5 seconds ago.=@1 @2 @3 -> @4 @5 secondi fa.
+Punch a node (range@=@1, seconds@=@2, limit@=@3).=Colpisce un nodo (raggio@=@1, secondi@=@2, limite@=@3)
+(<name> [<seconds>]) | (:<actor> [<seconds>])=(<nome> [<secondi>]) | (:<attore> [<secondi>])
+Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit=Riavvolge le azioni di un giocatore. Di base, <secondi> è 60. Imposta <secondi> a inf per nessun limite di tempo
+Invalid parameters. See /help rollback and /help rollback_check.=Parametri non validi. Vedi /help rollback e /help rollback_check.
+Reverting actions of player '@1' since @2 seconds.=Riavvolge le azioni del giocatore '@1' avvenute negli ultimi @2 secondi.
+Reverting actions of @1 since @2 seconds.=Riavvolge le azioni di @1 avvenute negli ultimi @2 secondi.
+(log is too long to show)=(il log è troppo lungo per essere mostrato)
+Reverting actions succeeded.=Riavvolgimento azioni avvenuto con successo.
+Reverting actions FAILED.=Errore nel riavvolgere le azioni.
+Show server status=Mostra lo stato del server
+This command was disabled by a mod or game.=Questo comando è stato disabilitato da una mod o dal gioco.
+[<0..23>:<0..59> | <0..24000>]=[<0..23>:<0..59> | <0..24000>]
+Show or set time of day=Mostra o imposta l'orario della giornata
+Current time is @1:@2.=Orario corrente: @1:@2.
+You don't have permission to run this command (missing privilege: @1).=Non hai il permesso di eseguire questo comando (privilegio mancante: @1)
+Invalid time (must be between 0 and 24000).=
+Time of day changed.=Orario della giornata cambiato.
+Invalid hour (must be between 0 and 23 inclusive).=Ora non valida (deve essere tra 0 e 23 inclusi)
+Invalid minute (must be between 0 and 59 inclusive).=Minuto non valido (deve essere tra 0 e 59 inclusi)
+Show day count since world creation=Mostra il conteggio dei giorni da quando il mondo è stato creato
+Current day is @1.=Giorno attuale: @1.
+[<delay_in_seconds> | -1] [-r] [<message>]=
+Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)=
+Server shutting down (operator request).=Arresto del server in corso (per richiesta dell'operatore)
+Ban the IP of a player or show the ban list=Bandisce l'IP del giocatore o mostra la lista di quelli banditi
+The ban list is empty.=La lista banditi è vuota.
+Ban list: @1=Lista banditi: @1
+Player is not online.=Il giocatore non è connesso.
+Failed to ban player.=Errore nel bandire il giocatore.
+Banned @1.=@1 banditǝ.
+<name> | <IP_address>=<nome> | <indirizzo_IP>
+Remove IP ban belonging to a player/IP=Perdona l'IP appartenente a un giocatore/IP
+Failed to unban player/IP.=Errore nel perdonare il giocatore/IP
+Unbanned @1.=@1 perdonatǝ
+<name> [<reason>]=<nome> [<ragione>]
+Kick a player=Caccia un giocatore
+Failed to kick player @1.=Errore nel cacciare il giocatore @1.
+Kicked @1.=@1 cacciatǝ.
+[full | quick]=[full | quick]
+Clear all objects in world=Elimina tutti gli oggetti/entità nel mondo
+Invalid usage, see /help clearobjects.=Uso incorretto, vedi /help clearobjects.
+Clearing all objects. This may take a long time. You may experience a timeout. (by @1)=Eliminando tutti gli oggetti/entità. Questo potrebbe richiedere molto tempo e farti eventualmente crashare. (di @1)
+Cleared all objects.=Tutti gli oggetti sono stati eliminati.
+<name> <message>=<nome> <messaggio>
+Send a direct message to a player=Invia un messaggio privato al giocatore
+Invalid usage, see /help msg.=Uso incorretto, vedi /help msg
+The player @1 is not online.=Il giocatore @1 non è connesso.
+DM from @1: @2=Messaggio privato da @1: @2
+Message sent.=Messaggio inviato.
+Get the last login time of a player or yourself=Ritorna l'ultimo accesso di un giocatore o di te stessǝ
+@1's last login time was @2.=L'ultimo accesso di @1 è avvenuto il @2
+@1's last login time is unknown.=L'ultimo accesso di @1 non è conosciuto
+Clear the inventory of yourself or another player=Svuota l'inventario tuo o di un altro giocatore
+You don't have permission to clear another player's inventory (missing privilege: @1).=Non hai il permesso di svuotare l'inventario di un altro giocatore (privilegio mancante: @1).
+@1 cleared your inventory.=@1 ha svuotato il tuo inventario.
+Cleared @1's inventory.=L'inventario di @1 è stato svuotato.
+Player must be online to clear inventory!=Il giocatore deve essere connesso per svuotarne l'inventario!
+Players can't be killed, damage has been disabled.=I giocatori non possono essere uccisi, il danno è disabilitato.
+Player @1 is not online.=Il giocatore @1 non è connesso.
+You are already dead.=Sei già mortǝ.
+@1 is already dead.=@1 è già mortǝ.
+@1 has been killed.=@1 è stato uccisǝ.
+Kill player or yourself=Uccide un giocatore o te stessǝ
+Invalid parameters (see /help @1).=
+Too many arguments, try using just /help <command>=
+Available commands: @1=Comandi disponibili: @1
+Use '/help <cmd>' to get more information, or '/help all' to list everything.=Usa '/help <comando>' per ottenere più informazioni, o '/help all' per elencare tutti i comandi.
+Available commands:=Comandi disponibili:
+Command not available: @1=Comando non disponibile: @1
+[all | privs | <cmd>] [-t]=
+Get help for commands or list privileges (-t: output in chat)=
+Available privileges:=Privilegi disponibili:
+Command=Comando
+Parameters=Parametri
+For more information, click on any entry in the list.=Per più informazioni, clicca su una qualsiasi voce dell'elenco.
+Double-click to copy the entry to the chat history.=Doppio click per copiare la voce nella cronologia della chat.
+Command: @1 @2=Comando: @1 @2
+Available commands: (see also: /help <cmd>)=Comandi disponibili: (vedi anche /help <comando>)
+Close=Chiudi
+Privilege=Privilegio
+Description=Descrizione
+print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=print [<filtro>] | dump [<filtro>] | save [<formato> [<filtro>]] | reset
+Handle the profiler and profiling data=Gestisce il profiler e i dati da esso elaborati
+Statistics written to action log.=Statistiche scritte nel log delle azioni.
+Statistics were reset.=Le statistiche sono state resettate.
+Usage: @1=Utilizzo: @1
+Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).=I formati supportati sono txt, csv, lua, json e json_pretty (le strutture potrebbero essere soggetti a cambiamenti).
+@1 joined the game.=
+@1 left the game.=
+@1 left the game (timed out).=
+(no description)=(nessuna descrizione)
+Can interact with things and modify the world=Si può interagire con le cose e modificare il mondo
+Can speak in chat=Si può parlare in chat
+Can modify basic privileges (@1)=
+Can modify privileges=Si possono modificare i privilegi
+Can teleport self=Si può teletrasportare se stessз
+Can teleport other players=Si possono teletrasportare gli altri giocatori
+Can set the time of day using /time=Si può impostate l'orario della giornata tramite /time
+Can do server maintenance stuff=Si possono eseguire operazioni di manutenzione del server
+Can bypass node protection in the world=Si può aggirare la protezione dei nodi nel mondo
+Can ban and unban players=Si possono bandire e perdonare i giocatori
+Can kick players=Si possono cacciare i giocatori
+Can use /give and /giveme=Si possono usare /give e /give me
+Can use /setpassword and /clearpassword=Si possono usare /setpassword e /clearpassword
+Can use fly mode=Si può usare la modalità volo
+Can use fast mode=Si può usare la modalità rapida
+Can fly through solid nodes using noclip mode=Si può volare attraverso i nodi solidi con la modalità incorporea
+Can use the rollback functionality=Si può usare la funzione di rollback
+Can view more debug info that might give a gameplay advantage=
+Can enable wireframe=
+Unknown Item=Oggetto sconosciuto
+Air=Aria
+Ignore=Ignora
+You can't place 'ignore' nodes!=Non puoi piazzare nodi 'ignore'!
+Values below show absolute/relative times spend per server step by the instrumented function.=
+A total of @1 sample(s) were taken.=
+The output is limited to '@1'.=
+Saving of profile failed: @1=
+Profile saved to @1=
+
+
+##### not used anymore #####
+
+Invalid time.=Orario non valido.
+[all | privs | <cmd>]=[all | privs | <comando>]
+Get help for commands or list privileges=Richiama la finestra d'aiuto dei comandi o dei privilegi
+Allows enabling various debug options that may affect gameplay=Permette di abilitare varie opzioni di debug che potrebbero influenzare l'esperienza di gioco
+[<delay_in_seconds> | -1] [reconnect] [<message>]=[<ritardo_in_secondi> | -1] [reconnect] [<messaggio>]
+Shutdown server (-1 cancels a delayed shutdown)=Arresta il server (-1 annulla un arresto programmato)
+<name> (<privilege> | all)=<nome> (<privilegio> | all)
+<privilege> | all=<privilegio> | all
+Can modify 'shout' and 'interact' privileges=Si possono modificare i privilegi 'shout' e 'interact'
diff --git a/builtin/locale/template.txt b/builtin/locale/template.txt
new file mode 100644
index 000000000..308d17f37
--- /dev/null
+++ b/builtin/locale/template.txt
@@ -0,0 +1,245 @@
+# textdomain: __builtin
+Empty command.=
+Invalid command: @1=
+Invalid command usage.=
+ (@1 s)=
+Command execution took @1 s=
+You don't have permission to run this command (missing privileges: @1).=
+Unable to get position of player @1.=
+Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)=
+<action>=
+Show chat action (e.g., '/me orders a pizza' displays '<player name> orders a pizza')=
+Show the name of the server owner=
+The administrator of this server is @1.=
+There's no administrator named in the config file.=
+@1 does not have any privileges.=
+Privileges of @1: @2=
+[<name>]=
+Show privileges of yourself or another player=
+Player @1 does not exist.=
+<privilege>=
+Return list of all online players with privilege=
+Invalid parameters (see /help haspriv).=
+Unknown privilege!=
+No online player has the "@1" privilege.=
+Players online with the "@1" privilege: @2=
+Your privileges are insufficient.=
+Your privileges are insufficient. '@1' only allows you to grant: @2=
+Unknown privilege: @1=
+@1 granted you privileges: @2=
+<name> (<privilege> [, <privilege2> [<...>]] | all)=
+Give privileges to player=
+Invalid parameters (see /help grant).=
+<privilege> [, <privilege2> [<...>]] | all=
+Grant privileges to yourself=
+Invalid parameters (see /help grantme).=
+Your privileges are insufficient. '@1' only allows you to revoke: @2=
+Note: Cannot revoke in singleplayer: @1=
+Note: Cannot revoke from admin: @1=
+No privileges were revoked.=
+@1 revoked privileges from you: @2=
+Remove privileges from player=
+Invalid parameters (see /help revoke).=
+Revoke privileges from yourself=
+Invalid parameters (see /help revokeme).=
+<name> <password>=
+Set player's password=
+Name field required.=
+Your password was cleared by @1.=
+Password of player "@1" cleared.=
+Your password was set by @1.=
+Password of player "@1" set.=
+<name>=
+Set empty password for a player=
+Reload authentication data=
+Done.=
+Failed.=
+Remove a player's data=
+Player "@1" removed.=
+No such player "@1" to remove.=
+Player "@1" is connected, cannot remove.=
+Unhandled remove_player return code @1.=
+Cannot teleport out of map bounds!=
+Cannot get player with name @1.=
+Cannot teleport, @1 is attached to an object!=
+Teleporting @1 to @2.=
+One does not teleport to oneself.=
+Cannot get teleportee with name @1.=
+Cannot get target player with name @1.=
+Teleporting @1 to @2 at @3.=
+<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>=
+Teleport to position or player=
+You don't have permission to teleport other players (missing privilege: @1).=
+([-n] <name> <value>) | <name>=
+Set or read server configuration setting=
+Failed. Use '/set -n <name> <value>' to create a new setting.=
+@1 @= @2=
+<not set>=
+Invalid parameters (see /help set).=
+Finished emerging @1 blocks in @2ms.=
+emergeblocks update: @1/@2 blocks emerged (@3%)=
+(here [<radius>]) | (<pos1> <pos2>)=
+Load (or, if nonexistent, generate) map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=
+Started emerge of area ranging from @1 to @2.=
+Delete map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=
+Successfully cleared area ranging from @1 to @2.=
+Failed to clear one or more blocks in area.=
+Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in parentheses)=
+Successfully reset light in the area ranging from @1 to @2.=
+Failed to load one or more blocks in area.=
+List mods installed on the server=
+No mods installed.=
+Cannot give an empty item.=
+Cannot give an unknown item.=
+Giving 'ignore' is not allowed.=
+@1 is not a known player.=
+@1 partially added to inventory.=
+@1 could not be added to inventory.=
+@1 added to inventory.=
+@1 partially added to inventory of @2.=
+@1 could not be added to inventory of @2.=
+@1 added to inventory of @2.=
+<name> <ItemString> [<count> [<wear>]]=
+Give item to player=
+Name and ItemString required.=
+<ItemString> [<count> [<wear>]]=
+Give item to yourself=
+ItemString required.=
+<EntityName> [<X>,<Y>,<Z>]=
+Spawn entity at given (or your) position=
+EntityName required.=
+Unable to spawn entity, player is nil.=
+Cannot spawn an unknown entity.=
+Invalid parameters (@1).=
+@1 spawned.=
+@1 failed to spawn.=
+Destroy item in hand=
+Unable to pulverize, no player.=
+Unable to pulverize, no item in hand.=
+An item was pulverized.=
+[<range>] [<seconds>] [<limit>]=
+Check who last touched a node or a node near it within the time specified by <seconds>. Default: range @= 0, seconds @= 86400 @= 24h, limit @= 5. Set <seconds> to inf for no time limit=
+Rollback functions are disabled.=
+That limit is too high!=
+Checking @1 ...=
+Nobody has touched the specified location in @1 seconds.=
+@1 @2 @3 -> @4 @5 seconds ago.=
+Punch a node (range@=@1, seconds@=@2, limit@=@3).=
+(<name> [<seconds>]) | (:<actor> [<seconds>])=
+Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit=
+Invalid parameters. See /help rollback and /help rollback_check.=
+Reverting actions of player '@1' since @2 seconds.=
+Reverting actions of @1 since @2 seconds.=
+(log is too long to show)=
+Reverting actions succeeded.=
+Reverting actions FAILED.=
+Show server status=
+This command was disabled by a mod or game.=
+[<0..23>:<0..59> | <0..24000>]=
+Show or set time of day=
+Current time is @1:@2.=
+You don't have permission to run this command (missing privilege: @1).=
+Invalid time (must be between 0 and 24000).=
+Time of day changed.=
+Invalid hour (must be between 0 and 23 inclusive).=
+Invalid minute (must be between 0 and 59 inclusive).=
+Show day count since world creation=
+Current day is @1.=
+[<delay_in_seconds> | -1] [-r] [<message>]=
+Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)=
+Server shutting down (operator request).=
+Ban the IP of a player or show the ban list=
+The ban list is empty.=
+Ban list: @1=
+Player is not online.=
+Failed to ban player.=
+Banned @1.=
+<name> | <IP_address>=
+Remove IP ban belonging to a player/IP=
+Failed to unban player/IP.=
+Unbanned @1.=
+<name> [<reason>]=
+Kick a player=
+Failed to kick player @1.=
+Kicked @1.=
+[full | quick]=
+Clear all objects in world=
+Invalid usage, see /help clearobjects.=
+Clearing all objects. This may take a long time. You may experience a timeout. (by @1)=
+Cleared all objects.=
+<name> <message>=
+Send a direct message to a player=
+Invalid usage, see /help msg.=
+The player @1 is not online.=
+DM from @1: @2=
+Message sent.=
+Get the last login time of a player or yourself=
+@1's last login time was @2.=
+@1's last login time is unknown.=
+Clear the inventory of yourself or another player=
+You don't have permission to clear another player's inventory (missing privilege: @1).=
+@1 cleared your inventory.=
+Cleared @1's inventory.=
+Player must be online to clear inventory!=
+Players can't be killed, damage has been disabled.=
+Player @1 is not online.=
+You are already dead.=
+@1 is already dead.=
+@1 has been killed.=
+Kill player or yourself=
+Invalid parameters (see /help @1).=
+Too many arguments, try using just /help <command>=
+Available commands: @1=
+Use '/help <cmd>' to get more information, or '/help all' to list everything.=
+Available commands:=
+Command not available: @1=
+[all | privs | <cmd>] [-t]=
+Get help for commands or list privileges (-t: output in chat)=
+Available privileges:=
+Command=
+Parameters=
+For more information, click on any entry in the list.=
+Double-click to copy the entry to the chat history.=
+Command: @1 @2=
+Available commands: (see also: /help <cmd>)=
+Close=
+Privilege=
+Description=
+print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=
+Handle the profiler and profiling data=
+Statistics written to action log.=
+Statistics were reset.=
+Usage: @1=
+Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).=
+@1 joined the game.=
+@1 left the game.=
+@1 left the game (timed out).=
+(no description)=
+Can interact with things and modify the world=
+Can speak in chat=
+Can modify basic privileges (@1)=
+Can modify privileges=
+Can teleport self=
+Can teleport other players=
+Can set the time of day using /time=
+Can do server maintenance stuff=
+Can bypass node protection in the world=
+Can ban and unban players=
+Can kick players=
+Can use /give and /giveme=
+Can use /setpassword and /clearpassword=
+Can use fly mode=
+Can use fast mode=
+Can fly through solid nodes using noclip mode=
+Can use the rollback functionality=
+Can view more debug info that might give a gameplay advantage=
+Can enable wireframe=
+Unknown Item=
+Air=
+Ignore=
+You can't place 'ignore' nodes!=
+Values below show absolute/relative times spend per server step by the instrumented function.=
+A total of @1 sample(s) were taken.=
+The output is limited to '@1'.=
+Saving of profile failed: @1=
+Profile saved to @1=
diff --git a/builtin/mainmenu/common.lua b/builtin/mainmenu/common.lua
index cd896f9ec..8db8bb8d1 100644
--- a/builtin/mainmenu/common.lua
+++ b/builtin/mainmenu/common.lua
@@ -14,14 +14,11 @@
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
---------------------------------------------------------------------------------
+
-- Global menu data
---------------------------------------------------------------------------------
menudata = {}
---------------------------------------------------------------------------------
-- Local cached values
---------------------------------------------------------------------------------
local min_supp_proto, max_supp_proto
function common_update_cached_supp_proto()
@@ -29,14 +26,12 @@ function common_update_cached_supp_proto()
max_supp_proto = core.get_max_supp_proto()
end
common_update_cached_supp_proto()
---------------------------------------------------------------------------------
+
-- Menu helper functions
---------------------------------------------------------------------------------
---------------------------------------------------------------------------------
local function render_client_count(n)
- if n > 99 then return '99+'
- elseif n >= 0 then return tostring(n)
+ if n > 999 then return '99+'
+ elseif n >= 0 then return tostring(n)
else return '?' end
end
@@ -50,21 +45,7 @@ local function configure_selected_world_params(idx)
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 ..
- (flagname and "server_flags_" .. flagname .. ".png" or "blank.png")) .. "," ..
- "2=" .. core.formspec_escape(defaulttexturedir .. "server_ping_4.png") .. "," ..
- "3=" .. core.formspec_escape(defaulttexturedir .. "server_ping_3.png") .. "," ..
- "4=" .. core.formspec_escape(defaulttexturedir .. "server_ping_2.png") .. "," ..
- "5=" .. core.formspec_escape(defaulttexturedir .. "server_ping_1.png")
-end
-
-
---------------------------------------------------------------------------------
-function render_serverlist_row(spec, is_favorite)
+function render_serverlist_row(spec)
local text = ""
if spec.name then
text = text .. core.formspec_escape(spec.name:trim())
@@ -75,31 +56,29 @@ function render_serverlist_row(spec, is_favorite)
end
end
- local grey_out = not is_server_protocol_compat(spec.proto_min, spec.proto_max)
+ local grey_out = not spec.is_compatible
- local details
- if is_favorite then
- details = "1,"
- else
- details = "0,"
- end
+ local details = {}
- if spec.ping then
- local ping = spec.ping * 1000
- if ping <= 50 then
- details = details .. "2,"
- elseif ping <= 100 then
- details = details .. "3,"
- elseif ping <= 250 then
- details = details .. "4,"
+ if spec.lag or spec.ping then
+ local lag = (spec.lag or 0) * 1000 + (spec.ping or 0) * 250
+ if lag <= 125 then
+ table.insert(details, "1")
+ elseif lag <= 175 then
+ table.insert(details, "2")
+ elseif lag <= 250 then
+ table.insert(details, "3")
else
- details = details .. "5,"
+ table.insert(details, "4")
end
else
- details = details .. "0,"
+ table.insert(details, "0")
end
- if spec.clients and spec.clients_max then
+ table.insert(details, ",")
+
+ local color = (grey_out and "#aaaaaa") or ((spec.is_favorite and "#ddddaa") or "#ffffff")
+ if spec.clients and (spec.clients_max or 0) > 0 then
local clients_percent = 100 * spec.clients / spec.clients_max
-- Choose a color depending on how many clients are connected
@@ -110,68 +89,59 @@ function render_serverlist_row(spec, is_favorite)
elseif clients_percent <= 60 then clients_color = '#a1e587' -- 0-60%: green
elseif clients_percent <= 90 then clients_color = '#ffdc97' -- 60-90%: yellow
elseif clients_percent == 100 then clients_color = '#dd5b5b' -- full server: red (darker)
- else clients_color = '#ffba97' -- 90-100%: orange
+ else clients_color = '#ffba97' -- 90-100%: orange
end
- details = details .. clients_color .. ',' ..
- render_client_count(spec.clients) .. ',/,' ..
- render_client_count(spec.clients_max) .. ','
-
- elseif grey_out then
- details = details .. '#aaaaaa,?,/,?,'
+ table.insert(details, clients_color)
+ table.insert(details, render_client_count(spec.clients) .. " / " ..
+ render_client_count(spec.clients_max))
else
- details = details .. ',?,/,?,'
+ table.insert(details, color)
+ table.insert(details, "?")
end
if spec.creative then
- details = details .. "1,"
+ table.insert(details, "1") -- creative icon
else
- details = details .. "0,"
- end
-
- if spec.damage then
- details = details .. "1,"
- else
- details = details .. "0,"
+ table.insert(details, "0")
end
if spec.pvp then
- details = details .. "1,"
+ table.insert(details, "2") -- pvp icon
+ elseif spec.damage then
+ table.insert(details, "1") -- heart icon
else
- details = details .. "0,"
+ table.insert(details, "0")
end
- return details .. (grey_out and '#aaaaaa,' or ',') .. text
-end
+ table.insert(details, color)
+ table.insert(details, text)
---------------------------------------------------------------------------------
-os.tempfolder = function()
- local temp = core.get_temp_path()
- return temp .. DIR_DELIM .. "MT_" .. math.random(0, 10000)
+ return table.concat(details, ",")
end
-
---------------------------------------------------------------------------------
+---------------------------------------------------------------------------------
os.tmpname = function()
- local path = os.tempfolder()
- io.open(path, "w"):close()
- return path
+ error('do not use') -- instead use core.get_temp_path()
end
-
--------------------------------------------------------------------------------
-function menu_render_worldlist()
- local retval = ""
+
+function menu_render_worldlist(show_gameid)
+ local retval = {}
local current_worldlist = menudata.worldlist:get_list()
+ local row
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) .. "\\]"
+ row = v.name
+ if show_gameid == nil or show_gameid == true then
+ row = row .. " [" .. v.gameid .. "]"
+ end
+ retval[#retval+1] = core.formspec_escape(row)
+
end
- return retval
+ return table.concat(retval, ",")
end