aboutsummaryrefslogtreecommitdiff
path: root/src/script/lua_api/l_mainmenu.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/script/lua_api/l_mainmenu.cpp')
0 files changed, 0 insertions, 0 deletions
ci.yml?h=lifo-fixes&id2=7068bc90af1f452359a1fcfe20fa01fc88f3d70a'>.gitlab-ci.yml223
-rw-r--r--.hgtags2
-rw-r--r--.mailmap33
-rw-r--r--.travis.yml39
-rw-r--r--CMakeLists.txt235
-rw-r--r--CONTRIBUTING.md106
-rw-r--r--Makefile.bak90
-rw-r--r--README.txt557
-rw-r--r--addlicensecomments.sh9
-rw-r--r--build/android/Makefile836
-rw-r--r--build/android/build.gradle48
-rw-r--r--build/android/gradle/wrapper/gradle-wrapper.jarbin0 -> 49896 bytes-rw-r--r--build/android/gradle/wrapper/gradle-wrapper.properties6
-rwxr-xr-xbuild/android/gradlew164
-rw-r--r--build/android/gradlew.bat90
-rw-r--r--build/android/jni/Android.mk402
-rw-r--r--build/android/jni/Application.mk6
-rw-r--r--build/android/jni/Deps.mk7
-rw-r--r--build/android/jni/Irrlicht.mk8
-rw-r--r--build/android/patches/irrlicht-back_button.patch20
-rw-r--r--build/android/patches/irrlicht-native_activity.patch13
-rw-r--r--build/android/patches/irrlicht-texturehack.patch240
-rw-r--r--build/android/patches/irrlicht-touchcount.patch30
-rw-r--r--build/android/patches/libiconv_android.patch39
-rw-r--r--build/android/patches/libiconv_stdio.patch13
-rw-r--r--build/android/patches/libvorbis-libogg-fpu.patch37
-rw-r--r--build/android/patches/openssl_arch.patch13
-rw-r--r--build/android/settings.gradle2
-rw-r--r--build/android/src/debug/AndroidManifest.xml4
-rw-r--r--build/android/src/main/AndroidManifest.xml34
-rw-r--r--build/android/src/main/java/net.minetest.minetest/MinetestAssetCopy.java416
-rw-r--r--build/android/src/main/java/net.minetest.minetest/MinetestTextEntry.java91
-rw-r--r--build/android/src/main/java/net.minetest.minetest/MtNativeActivity.java99
-rw-r--r--build/android/src/main/res/drawable-hdpi/irr_icon.pngbin0 -> 5490 bytes-rw-r--r--build/android/src/main/res/drawable-ldpi/irr_icon.pngbin0 -> 2262 bytes-rw-r--r--build/android/src/main/res/drawable-mdpi/irr_icon.pngbin0 -> 3110 bytes-rw-r--r--build/android/src/main/res/drawable-xhdpi/irr_icon.pngbin0 -> 7610 bytes-rw-r--r--build/android/src/main/res/layout/assetcopy.xml24
-rw-r--r--build/android/src/main/res/values/strings.xml5
-rw-r--r--build/android/src/main/res/values/styles.xml11
-rw-r--r--builtin/async/init.lua17
-rw-r--r--builtin/client/chatcommands.lua65
-rw-r--r--builtin/client/init.lua23
-rw-r--r--builtin/client/register.lua73
-rw-r--r--builtin/common/after.lua33
-rw-r--r--builtin/common/async_event.lua40
-rw-r--r--builtin/common/chatcommands.lua112
-rw-r--r--builtin/common/filterlist.lua320
-rw-r--r--builtin/common/misc_helpers.lua658
-rw-r--r--builtin/common/serialize.lua221
-rw-r--r--builtin/common/strict.lua57
-rw-r--r--builtin/common/vector.lua145
-rw-r--r--builtin/fstk/buttonbar.lua215
-rw-r--r--builtin/fstk/dialog.lua69
-rw-r--r--builtin/fstk/tabview.lua273
-rw-r--r--builtin/fstk/ui.lua208
-rw-r--r--builtin/game/auth.lua216
-rw-r--r--builtin/game/chatcommands.lua968
-rw-r--r--builtin/game/constants.lua27
-rw-r--r--builtin/game/deprecated.lua72
-rw-r--r--builtin/game/detached_inventory.lua20
-rw-r--r--builtin/game/falling.lua331
-rw-r--r--builtin/game/features.lua33
-rw-r--r--builtin/game/forceloading.lua100
-rw-r--r--builtin/game/init.lua36
-rw-r--r--builtin/game/item.lua757
-rw-r--r--builtin/game/item_entity.lua228
-rw-r--r--builtin/game/misc.lua189
-rw-r--r--builtin/game/privileges.lua92
-rw-r--r--builtin/game/register.lua570
-rw-r--r--builtin/game/statbars.lua165
-rw-r--r--builtin/game/static_spawn.lua25
-rw-r--r--builtin/game/voxelarea.lua132
-rw-r--r--builtin/init.lua52
-rw-r--r--builtin/mainmenu/common.lua336
-rw-r--r--builtin/mainmenu/dlg_config_world.lua287
-rw-r--r--builtin/mainmenu/dlg_create_world.lua143
-rw-r--r--builtin/mainmenu/dlg_delete_mod.lua69
-rw-r--r--builtin/mainmenu/dlg_delete_world.lua61
-rw-r--r--builtin/mainmenu/dlg_rename_modpack.lua67
-rw-r--r--builtin/mainmenu/dlg_settings_advanced.lua772
-rw-r--r--builtin/mainmenu/gamemgr.lua83
-rw-r--r--builtin/mainmenu/generate_from_settingtypes.lua99
-rw-r--r--builtin/mainmenu/init.lua167
-rw-r--r--builtin/mainmenu/init_simple.lua4
-rw-r--r--builtin/mainmenu/modmgr.lua570
-rw-r--r--builtin/mainmenu/store.lua614
-rw-r--r--builtin/mainmenu/tab_credits.lua106
-rw-r--r--builtin/mainmenu/tab_local.lua317
-rw-r--r--builtin/mainmenu/tab_mods.lua185
-rw-r--r--builtin/mainmenu/tab_online.lua350
-rw-r--r--builtin/mainmenu/tab_settings.lua410
-rw-r--r--builtin/mainmenu/tab_simple_main.lua220
-rw-r--r--builtin/mainmenu/tab_texturepacks.lua132
-rw-r--r--builtin/mainmenu/textures.lua185
-rw-r--r--builtin/profiler/init.lua80
-rw-r--r--builtin/profiler/instrumentation.lua232
-rw-r--r--builtin/profiler/reporter.lua277
-rw-r--r--builtin/profiler/sampling.lua206
-rw-r--r--builtin/settingtypes.txt1509
-rw-r--r--client/serverlist/.gitignore2
-rw-r--r--client/shaders/default_shader/opengl_fragment.glsl4
-rw-r--r--client/shaders/default_shader/opengl_vertex.glsl9
-rw-r--r--client/shaders/minimap_shader/opengl_fragment.glsl32
-rw-r--r--client/shaders/minimap_shader/opengl_vertex.glsl9
-rw-r--r--client/shaders/nodes_shader/opengl_fragment.glsl219
-rw-r--r--client/shaders/nodes_shader/opengl_vertex.glsl144
-rw-r--r--client/shaders/selection_shader/opengl_fragment.glsl9
-rw-r--r--client/shaders/selection_shader/opengl_vertex.glsl9
-rw-r--r--client/shaders/wielded_shader/opengl_fragment.glsl127
-rw-r--r--client/shaders/wielded_shader/opengl_vertex.glsl32
-rw-r--r--clientmods/preview/init.lua152
-rw-r--r--cmake/Modules/FindCURL.cmake19
-rw-r--r--cmake/Modules/FindGMP.cmake27
-rw-r--r--cmake/Modules/FindGettextLib.cmake78
-rw-r--r--cmake/Modules/FindIrrlicht.cmake101
-rw-r--r--cmake/Modules/FindJson.cmake26
-rw-r--r--cmake/Modules/FindLua.cmake28
-rw-r--r--cmake/Modules/FindLuaJIT.cmake50
-rw-r--r--cmake/Modules/FindNcursesw.cmake204
-rw-r--r--cmake/Modules/FindOpenGLES2.cmake112
-rw-r--r--cmake/Modules/FindSQLite3.cmake9
-rw-r--r--cmake/Modules/FindVorbis.cmake46
-rw-r--r--cmake/Modules/GenerateVersion.cmake26
-rw-r--r--cmake/Modules/misc.cmake21
-rw-r--r--data/cloud.pngbin118 -> 0 bytes-rw-r--r--data/coalstone.pngbin1557 -> 0 bytes-rw-r--r--data/crack.pngbin1085 -> 0 bytes-rw-r--r--data/fontlucida.pngbin17284 -> 0 bytes-rw-r--r--data/grass.pngbin1683 -> 0 bytes-rw-r--r--data/grass2.pngbin493 -> 0 bytes-rw-r--r--data/grass_footsteps.pngbin1609 -> 0 bytes-rw-r--r--data/leaves.pngbin913 -> 0 bytes-rw-r--r--data/mese.pngbin203 -> 0 bytes-rw-r--r--data/mud_with_grass.pngbin1596 -> 0 bytes-rw-r--r--data/pauseMenu.guibin17970 -> 0 bytes-rw-r--r--data/player.pngbin212 -> 0 bytes-rw-r--r--data/player_back.pngbin201 -> 0 bytes-rw-r--r--data/rat.pngbin920 -> 0 bytes-rw-r--r--data/sign.pngbin1374 -> 0 bytes-rw-r--r--data/sign_back.pngbin1312 -> 0 bytes-rw-r--r--data/skybox1.pngbin19476 -> 0 bytes-rw-r--r--data/skybox1_source.pngbin5477 -> 0 bytes-rw-r--r--data/skybox2.pngbin2272 -> 0 bytes-rw-r--r--data/skybox3.pngbin2272 -> 0 bytes-rw-r--r--data/stick.pngbin947 -> 0 bytes-rw-r--r--data/stone.pngbin1609 -> 0 bytes-rw-r--r--data/stone_with_arrow.pngbin856 -> 0 bytes-rw-r--r--data/tf.jpgbin39989 -> 0 bytes-rw-r--r--data/tool_mesepick.pngbin990 -> 0 bytes-rw-r--r--data/tool_stpick.pngbin990 -> 0 bytes-rw-r--r--data/tool_wpick.pngbin989 -> 0 bytes-rw-r--r--data/torch.pngbin925 -> 0 bytes-rw-r--r--data/torch_on_ceiling.pngbin913 -> 0 bytes-rw-r--r--data/torch_on_floor.pngbin917 -> 0 bytes-rw-r--r--data/tree_top.pngbin1024 -> 0 bytes-rw-r--r--data/water.pngbin214 -> 0 bytes-rw-r--r--data/water_old.pngbin103 -> 0 bytes-rw-r--r--doc/Doxyfile.in53
-rw-r--r--doc/README.android130
-rw-r--r--doc/README.txt169
-rw-r--r--doc/client_lua_api.md1152
-rw-r--r--doc/fst_api.txt171
-rw-r--r--doc/gpl-2.0.txt339
-rw-r--r--doc/lgpl-2.1.txt502
-rw-r--r--doc/lua_api.txt4838
-rw-r--r--doc/main_page.dox8
-rw-r--r--doc/menu_lua_api.txt241
-rw-r--r--doc/minetest.6114
-rw-r--r--doc/minetestserver.62
-rw-r--r--doc/protocol.txt108
-rw-r--r--doc/texture_packs.txt161
-rw-r--r--doc/world_format.txt631
-rw-r--r--fonts/Arimo-LICENSE.txt2
-rw-r--r--fonts/Arimo-Regular.ttfbin0 -> 436876 bytes-rw-r--r--fonts/Cousine-LICENSE.txt2
-rw-r--r--fonts/Cousine-Regular.ttfbin0 -> 309040 bytes-rw-r--r--fonts/DroidSansFallbackFull-LICENSE.txt13
-rw-r--r--fonts/DroidSansFallbackFull.ttfbin0 -> 4529044 bytes-rw-r--r--fonts/mono_dejavu_sans_10.xmlbin0 -> 257014 bytes-rw-r--r--fonts/mono_dejavu_sans_100.pngbin0 -> 56121 bytes-rw-r--r--fonts/mono_dejavu_sans_11.xmlbin0 -> 263644 bytes-rw-r--r--fonts/mono_dejavu_sans_110.pngbin0 -> 67613 bytes-rw-r--r--fonts/mono_dejavu_sans_12.xmlbin0 -> 268932 bytes-rw-r--r--fonts/mono_dejavu_sans_120.pngbin0 -> 73938 bytes-rw-r--r--fonts/mono_dejavu_sans_14.xmlbin0 -> 269188 bytes-rw-r--r--fonts/mono_dejavu_sans_140.pngbin0 -> 89073 bytes-rw-r--r--fonts/mono_dejavu_sans_16.xmlbin0 -> 275642 bytes-rw-r--r--fonts/mono_dejavu_sans_160.pngbin0 -> 101939 bytes-rw-r--r--fonts/mono_dejavu_sans_18.xmlbin0 -> 279962 bytes-rw-r--r--fonts/mono_dejavu_sans_180.pngbin0 -> 122274 bytes-rw-r--r--fonts/mono_dejavu_sans_20.xmlbin0 -> 282588 bytes-rw-r--r--fonts/mono_dejavu_sans_200.pngbin0 -> 138662 bytes-rw-r--r--fonts/mono_dejavu_sans_22.xmlbin0 -> 283950 bytes-rw-r--r--fonts/mono_dejavu_sans_220.pngbin0 -> 152844 bytes-rw-r--r--fonts/mono_dejavu_sans_24.xmlbin0 -> 286626 bytes-rw-r--r--fonts/mono_dejavu_sans_240.pngbin0 -> 170247 bytes-rw-r--r--fonts/mono_dejavu_sans_26.xmlbin0 -> 289710 bytes-rw-r--r--fonts/mono_dejavu_sans_260.pngbin0 -> 190156 bytes-rw-r--r--fonts/mono_dejavu_sans_28.xmlbin0 -> 292596 bytes-rw-r--r--fonts/mono_dejavu_sans_280.pngbin0 -> 200848 bytes-rw-r--r--fonts/mono_dejavu_sans_4.xmlbin0 -> 237740 bytes-rw-r--r--fonts/mono_dejavu_sans_40.pngbin0 -> 15668 bytes-rw-r--r--fonts/mono_dejavu_sans_6.xmlbin0 -> 245472 bytes-rw-r--r--fonts/mono_dejavu_sans_60.pngbin0 -> 29291 bytes-rw-r--r--fonts/mono_dejavu_sans_8.xmlbin0 -> 251876 bytes-rw-r--r--fonts/mono_dejavu_sans_80.pngbin0 -> 45552 bytes-rw-r--r--fonts/mono_dejavu_sans_9.xmlbin0 -> 254016 bytes-rw-r--r--fonts/mono_dejavu_sans_90.pngbin0 -> 50995 bytes-rw-r--r--games/minimal/game.conf2
-rw-r--r--games/minimal/menu/background.pngbin0 -> 392 bytes-rw-r--r--games/minimal/menu/icon.pngbin0 -> 218 bytes-rw-r--r--games/minimal/mods/bucket/depends.txt2
-rw-r--r--games/minimal/mods/bucket/init.lua95
-rw-r--r--games/minimal/mods/bucket/textures/bucket.pngbin0 -> 182 bytes-rw-r--r--games/minimal/mods/bucket/textures/bucket_lava.pngbin0 -> 183 bytes-rw-r--r--games/minimal/mods/bucket/textures/bucket_water.pngbin0 -> 180 bytes-rw-r--r--games/minimal/mods/default/init.lua1898
-rw-r--r--games/minimal/mods/default/mapgen.lua137
-rw-r--r--games/minimal/mods/default/sounds/default_grass_footstep.1.oggbin0 -> 7014 bytes-rw-r--r--games/minimal/mods/default/textures/bubble.pngbin0 -> 273 bytes-rw-r--r--games/minimal/mods/default/textures/crack_anylength.pngbin0 -> 255 bytes-rw-r--r--games/minimal/mods/default/textures/default_apple.pngbin0 -> 109 bytes-rw-r--r--games/minimal/mods/default/textures/default_book.pngbin0 -> 157 bytes-rw-r--r--games/minimal/mods/default/textures/default_bookshelf.pngbin0 -> 515 bytes-rw-r--r--games/minimal/mods/default/textures/default_brick.pngbin0 -> 457 bytes-rw-r--r--games/minimal/mods/default/textures/default_cactus_side.pngbin0 -> 144 bytes-rw-r--r--games/minimal/mods/default/textures/default_cactus_top.pngbin0 -> 121 bytes-rw-r--r--games/minimal/mods/default/textures/default_chest.pngbin0 -> 263 bytes-rw-r--r--games/minimal/mods/default/textures/default_clay.pngbin0 -> 318 bytes-rw-r--r--games/minimal/mods/default/textures/default_clay_brick.pngbin0 -> 173 bytes-rw-r--r--games/minimal/mods/default/textures/default_clay_lump.pngbin0 -> 140 bytes-rw-r--r--games/minimal/mods/default/textures/default_cloud.pngbin0 -> 83 bytes-rw-r--r--games/minimal/mods/default/textures/default_coal_lump.pngbin0 -> 138 bytes-rw-r--r--games/minimal/mods/default/textures/default_cobble.pngbin0 -> 374 bytes-rw-r--r--games/minimal/mods/default/textures/default_dirt.png (renamed from data/mud.png)bin1586 -> 1586 bytes-rw-r--r--games/minimal/mods/default/textures/default_fence.pngbin0 -> 480 bytes-rw-r--r--games/minimal/mods/default/textures/default_furnace_fire_bg.pngbin0 -> 220 bytes-rw-r--r--games/minimal/mods/default/textures/default_furnace_fire_fg.pngbin0 -> 719 bytes-rw-r--r--games/minimal/mods/default/textures/default_furnace_front.pngbin0 -> 159 bytes-rw-r--r--games/minimal/mods/default/textures/default_furnace_front_active.pngbin0 -> 283 bytes-rw-r--r--games/minimal/mods/default/textures/default_furnace_side.pngbin0 -> 125 bytes-rw-r--r--games/minimal/mods/default/textures/default_glass.pngbin0 -> 210 bytes-rw-r--r--games/minimal/mods/default/textures/default_grass.pngbin0 -> 782 bytes-rw-r--r--games/minimal/mods/default/textures/default_grass_footsteps.pngbin0 -> 771 bytes-rw-r--r--games/minimal/mods/default/textures/default_grass_side.pngbin0 -> 878 bytes-rw-r--r--games/minimal/mods/default/textures/default_gravel.pngbin0 -> 172 bytes-rw-r--r--games/minimal/mods/default/textures/default_iron_lump.pngbin0 -> 140 bytes-rw-r--r--games/minimal/mods/default/textures/default_junglegrass.pngbin0 -> 237 bytes-rw-r--r--games/minimal/mods/default/textures/default_ladder.pngbin0 -> 368 bytes-rw-r--r--games/minimal/mods/default/textures/default_lava.pngbin0 -> 172 bytes-rw-r--r--games/minimal/mods/default/textures/default_lava_flowing_animated.pngbin0 -> 7818 bytes-rw-r--r--games/minimal/mods/default/textures/default_lava_source_animated.pngbin0 -> 3145 bytes-rw-r--r--games/minimal/mods/default/textures/default_leaves.pngbin0 -> 1683 bytes-rw-r--r--games/minimal/mods/default/textures/default_mese.pngbin0 -> 129 bytes-rw-r--r--games/minimal/mods/default/textures/default_mineral_coal.pngbin0 -> 142 bytes-rw-r--r--games/minimal/mods/default/textures/default_mineral_iron.pngbin0 -> 922 bytes-rw-r--r--games/minimal/mods/default/textures/default_mossycobble.pngbin0 -> 603 bytes-rw-r--r--games/minimal/mods/default/textures/default_nc_back.pngbin0 -> 186 bytes-rw-r--r--games/minimal/mods/default/textures/default_nc_front.pngbin0 -> 204 bytes-rw-r--r--games/minimal/mods/default/textures/default_nc_rb.pngbin0 -> 137 bytes-rw-r--r--games/minimal/mods/default/textures/default_nc_side.pngbin0 -> 148 bytes-rw-r--r--games/minimal/mods/default/textures/default_paper.pngbin0 -> 139 bytes-rw-r--r--games/minimal/mods/default/textures/default_papyrus.pngbin0 -> 165 bytes-rw-r--r--games/minimal/mods/default/textures/default_rail.pngbin0 -> 437 bytes-rw-r--r--games/minimal/mods/default/textures/default_rail_crossing.pngbin0 -> 388 bytes-rw-r--r--games/minimal/mods/default/textures/default_rail_curved.pngbin0 -> 375 bytes-rw-r--r--games/minimal/mods/default/textures/default_rail_t_junction.pngbin0 -> 374 bytes-rw-r--r--games/minimal/mods/default/textures/default_river_water.pngbin0 -> 716 bytes-rw-r--r--games/minimal/mods/default/textures/default_sand.pngbin0 -> 762 bytes-rw-r--r--games/minimal/mods/default/textures/default_sandstone.pngbin0 -> 744 bytes-rw-r--r--games/minimal/mods/default/textures/default_sapling.pngbin0 -> 328 bytes-rw-r--r--games/minimal/mods/default/textures/default_scorched_stuff.pngbin0 -> 164 bytes-rw-r--r--games/minimal/mods/default/textures/default_sign_wall.pngbin0 -> 416 bytes-rw-r--r--games/minimal/mods/default/textures/default_steel_block.pngbin0 -> 128 bytes-rw-r--r--games/minimal/mods/default/textures/default_steel_ingot.pngbin0 -> 144 bytes-rw-r--r--games/minimal/mods/default/textures/default_stick.pngbin0 -> 120 bytes-rw-r--r--games/minimal/mods/default/textures/default_stone.pngbin0 -> 389 bytes-rw-r--r--games/minimal/mods/default/textures/default_tnt_bottom.pngbin0 -> 98 bytes-rw-r--r--games/minimal/mods/default/textures/default_tnt_side.pngbin0 -> 129 bytes-rw-r--r--games/minimal/mods/default/textures/default_tnt_top.pngbin0 -> 146 bytes-rw-r--r--games/minimal/mods/default/textures/default_tool_mesepick.pngbin0 -> 155 bytes-rw-r--r--games/minimal/mods/default/textures/default_tool_steelaxe.pngbin0 -> 140 bytes-rw-r--r--games/minimal/mods/default/textures/default_tool_steelpick.pngbin0 -> 163 bytes-rw-r--r--games/minimal/mods/default/textures/default_tool_steelshovel.pngbin0 -> 144 bytes-rw-r--r--games/minimal/mods/default/textures/default_tool_steelsword.pngbin0 -> 169 bytes-rw-r--r--games/minimal/mods/default/textures/default_tool_stoneaxe.pngbin0 -> 141 bytes-rw-r--r--games/minimal/mods/default/textures/default_tool_stonepick.pngbin0 -> 155 bytes-rw-r--r--games/minimal/mods/default/textures/default_tool_stoneshovel.pngbin0 -> 144 bytes-rw-r--r--games/minimal/mods/default/textures/default_tool_stonesword.pngbin0 -> 168 bytes-rw-r--r--games/minimal/mods/default/textures/default_tool_woodaxe.pngbin0 -> 134 bytes-rw-r--r--games/minimal/mods/default/textures/default_tool_woodpick.pngbin0 -> 149 bytes-rw-r--r--games/minimal/mods/default/textures/default_tool_woodshovel.pngbin0 -> 135 bytes-rw-r--r--games/minimal/mods/default/textures/default_tool_woodsword.pngbin0 -> 147 bytes-rw-r--r--games/minimal/mods/default/textures/default_torch.pngbin0 -> 146 bytes-rw-r--r--games/minimal/mods/default/textures/default_torch_on_ceiling.pngbin0 -> 137 bytes-rw-r--r--games/minimal/mods/default/textures/default_torch_on_floor.pngbin0 -> 138 bytes-rw-r--r--games/minimal/mods/default/textures/default_tree.png (renamed from data/tree.png)bin1445 -> 1445 bytes-rw-r--r--games/minimal/mods/default/textures/default_tree_top.pngbin0 -> 177 bytes-rw-r--r--games/minimal/mods/default/textures/default_water.pngbin0 -> 302 bytes-rw-r--r--games/minimal/mods/default/textures/default_wood.png (renamed from data/wood.png)bin1400 -> 1400 bytes-rw-r--r--games/minimal/mods/default/textures/heart.pngbin0 -> 248 bytes-rw-r--r--games/minimal/mods/default/textures/player.pngbin0 -> 157 bytes-rw-r--r--games/minimal/mods/default/textures/player_back.pngbin0 -> 149 bytes-rw-r--r--games/minimal/mods/default/textures/treeprop.pngbin0 -> 757 bytes-rw-r--r--games/minimal/mods/default/textures/wieldhand.pngbin0 -> 212 bytes-rw-r--r--games/minimal/mods/experimental/depends.txt2
-rw-r--r--games/minimal/mods/experimental/init.lua644
-rw-r--r--games/minimal/mods/experimental/textures/experimental_dummyball.pngbin0 -> 399 bytes-rw-r--r--games/minimal/mods/experimental/textures/experimental_tester_tool_1.pngbin0 -> 160 bytes-rw-r--r--games/minimal/mods/give_initial_stuff/depends.txt2
-rw-r--r--games/minimal/mods/give_initial_stuff/init.lua16
-rw-r--r--games/minimal/mods/legacy/depends.txt2
-rw-r--r--games/minimal/mods/legacy/init.lua128
-rw-r--r--games/minimal/mods/legacy/textures/apple_iron.pngbin0 -> 119 bytes-rw-r--r--games/minimal/mods/legacy/textures/cooked_rat.pngbin0 -> 145 bytes-rw-r--r--games/minimal/mods/legacy/textures/dungeon_master.pngbin0 -> 3007 bytes-rw-r--r--games/minimal/mods/legacy/textures/fireball.pngbin0 -> 279 bytes-rw-r--r--games/minimal/mods/legacy/textures/firefly.pngbin0 -> 99 bytes-rw-r--r--games/minimal/mods/legacy/textures/oerkki1.pngbin0 -> 165 bytes-rw-r--r--games/minimal/mods/legacy/textures/oerkki1_damaged.pngbin0 -> 210 bytes-rw-r--r--games/minimal/mods/legacy/textures/rat.pngbin0 -> 141 bytes-rw-r--r--games/minimal/mods/stairs/depends.txt1
-rw-r--r--games/minimal/mods/stairs/init.lua93
-rw-r--r--games/minimal/mods/test/init.lua11
-rwxr-xr-xgenmap.py38
-rw-r--r--lib/gmp/CMakeLists.txt7
-rw-r--r--lib/gmp/mini-gmp.c4130
-rw-r--r--lib/gmp/mini-gmp.h256
-rw-r--r--lib/jsoncpp/CMakeLists.txt7
-rw-r--r--lib/jsoncpp/json/UPDATING16
-rw-r--r--lib/jsoncpp/json/json.h1981
-rw-r--r--lib/jsoncpp/jsoncpp.cpp4937
-rw-r--r--lib/lua/CMakeLists.txt77
-rw-r--r--lib/lua/COPYRIGHT34
-rw-r--r--lib/lua/src/CMakeLists.txt54
-rw-r--r--lib/lua/src/lapi.c1087
-rw-r--r--lib/lua/src/lapi.h16
-rw-r--r--lib/lua/src/lauxlib.c653
-rw-r--r--lib/lua/src/lauxlib.h174
-rw-r--r--lib/lua/src/lbaselib.c653
-rw-r--r--lib/lua/src/lcode.c839
-rw-r--r--lib/lua/src/lcode.h76
-rw-r--r--lib/lua/src/ldblib.c397
-rw-r--r--lib/lua/src/ldebug.c638
-rw-r--r--lib/lua/src/ldebug.h33
-rw-r--r--lib/lua/src/ldo.c518
-rw-r--r--lib/lua/src/ldo.h57
-rw-r--r--lib/lua/src/ldump.c164
-rw-r--r--lib/lua/src/lfunc.c174
-rw-r--r--lib/lua/src/lfunc.h34
-rw-r--r--lib/lua/src/lgc.c711
-rw-r--r--lib/lua/src/lgc.h110
-rw-r--r--lib/lua/src/linit.c38
-rw-r--r--lib/lua/src/liolib.c553
-rw-r--r--lib/lua/src/llex.c467
-rw-r--r--lib/lua/src/llex.h81
-rw-r--r--lib/lua/src/llimits.h128
-rw-r--r--lib/lua/src/lmathlib.c263
-rw-r--r--lib/lua/src/lmem.c86
-rw-r--r--lib/lua/src/lmem.h49
-rw-r--r--lib/lua/src/loadlib.c666
-rw-r--r--lib/lua/src/lobject.c214
-rw-r--r--lib/lua/src/lobject.h381
-rw-r--r--lib/lua/src/lopcodes.c102
-rw-r--r--lib/lua/src/lopcodes.h268
-rw-r--r--lib/lua/src/loslib.c243
-rw-r--r--lib/lua/src/lparser.c1339
-rw-r--r--lib/lua/src/lparser.h82
-rw-r--r--lib/lua/src/lstate.c214
-rw-r--r--lib/lua/src/lstate.h169
-rw-r--r--lib/lua/src/lstring.c111
-rw-r--r--lib/lua/src/lstring.h31
-rw-r--r--lib/lua/src/lstrlib.c869
-rw-r--r--lib/lua/src/ltable.c588
-rw-r--r--lib/lua/src/ltable.h40
-rw-r--r--lib/lua/src/ltablib.c287
-rw-r--r--lib/lua/src/ltm.c75
-rw-r--r--lib/lua/src/ltm.h54
-rw-r--r--lib/lua/src/lua.c392
-rw-r--r--lib/lua/src/lua.h388
-rw-r--r--lib/lua/src/luac.c200
-rw-r--r--lib/lua/src/luaconf.h763
-rw-r--r--lib/lua/src/lualib.h53
-rw-r--r--lib/lua/src/lundump.c227
-rw-r--r--lib/lua/src/lundump.h36
-rw-r--r--lib/lua/src/lvm.c763
-rw-r--r--lib/lua/src/lvm.h36
-rw-r--r--lib/lua/src/lzio.c82
-rw-r--r--lib/lua/src/lzio.h67
-rw-r--r--lib/lua/src/print.c227
-rw-r--r--licensecomment.txt19
-rwxr-xr-xmakepackage_binary.sh61
-rw-r--r--minetest.conf.example1910
-rw-r--r--minetest.conf.example.extra63
-rw-r--r--minetest.sln20
-rw-r--r--minetest.vcproj406
-rw-r--r--misc/Info.plist14
-rw-r--r--misc/debpkg-control36
-rw-r--r--misc/minetest-icon-24x24.pngbin0 -> 587 bytes-rw-r--r--misc/minetest-icon.icnsbin0 -> 242668 bytes-rw-r--r--misc/minetest-icon.icobin0 -> 9662 bytes-rw-r--r--misc/minetest-xorg-icon-128.pngbin0 -> 11241 bytes-rw-r--r--misc/minetest.exe.manifest15
-rw-r--r--misc/minetest.svg183
-rw-r--r--misc/net.minetest.minetest.appdata.xml58
-rw-r--r--misc/net.minetest.minetest.desktop17
-rw-r--r--misc/winresource.rc58
-rw-r--r--mods/mods_here.txt4
-rw-r--r--po/be/minetest.po5500
-rw-r--r--po/ca/minetest.po5000
-rw-r--r--po/cs/minetest.po4984
-rw-r--r--po/da/minetest.po5444
-rw-r--r--po/de/minetest.po6078
-rw-r--r--po/dv/minetest.po4688
-rw-r--r--po/eo/minetest.po4971
-rw-r--r--po/es/minetest.po5103
-rw-r--r--po/et/minetest.po5041
-rw-r--r--po/fr/minetest.po5976
-rw-r--r--po/he/minetest.po4702
-rw-r--r--po/hu/minetest.po5462
-rw-r--r--po/id/minetest.po5330
-rw-r--r--po/it/minetest.po5780
-rw-r--r--po/ja/minetest.po5100
-rw-r--r--po/jbo/minetest.po4803
-rw-r--r--po/ko/minetest.po5125
-rw-r--r--po/ky/minetest.po4941
-rw-r--r--po/lt/minetest.po4909
-rw-r--r--po/minetest.pot4672
-rw-r--r--po/ms/minetest.po5356
-rw-r--r--po/nb/minetest.po4733
-rw-r--r--po/nl/minetest.po6061
-rw-r--r--po/pl/minetest.po5498
-rw-r--r--po/pt/minetest.po5317
-rw-r--r--po/pt_BR/minetest.po5884
-rw-r--r--po/ro/minetest.po5023
-rw-r--r--po/ru/minetest.po5522
-rw-r--r--po/sl/minetest.po4684
-rw-r--r--po/sr_Cyrl/minetest.po4854
-rw-r--r--po/sv/minetest.po4702
-rw-r--r--po/sw/minetest.po5497
-rw-r--r--po/tr/minetest.po5552
-rw-r--r--po/uk/minetest.po4836
-rw-r--r--po/zh_CN/minetest.po5186
-rw-r--r--po/zh_TW/minetest.po5549
-rw-r--r--src/CMakeLists.txt887
-rw-r--r--src/activeobject.h86
-rw-r--r--src/ban.cpp146
-rw-r--r--src/ban.h52
-rw-r--r--src/camera.cpp595
-rw-r--r--src/camera.h228
-rw-r--r--src/cavegen.cpp863
-rw-r--r--src/cavegen.h245
-rw-r--r--src/cguittfont/CGUITTFont.cpp1197
-rw-r--r--src/cguittfont/CGUITTFont.h393
-rw-r--r--src/cguittfont/CMakeLists.txt30
-rw-r--r--src/cguittfont/irrUString.h3892
-rw-r--r--src/cguittfont/xCGUITTFont.cpp5
-rw-r--r--src/cguittfont/xCGUITTFont.h7
-rw-r--r--src/chat.cpp784
-rw-r--r--src/chat.h294
-rw-r--r--src/chat_interface.h82
-rw-r--r--src/client.cpp3024
-rw-r--r--src/client.h779
-rw-r--r--src/client/CMakeLists.txt8
-rw-r--r--src/client/clientlauncher.cpp732
-rw-r--r--src/client/clientlauncher.h94
-rw-r--r--src/client/inputhandler.cpp119
-rw-r--r--src/client/inputhandler.h401
-rw-r--r--src/client/joystick_controller.cpp261
-rw-r--r--src/client/joystick_controller.h174
-rw-r--r--src/client/keys.h93
-rw-r--r--src/client/tile.cpp2414
-rw-r--r--src/client/tile.h353
-rw-r--r--src/clientenvironment.cpp847
-rw-r--r--src/clientenvironment.h195
-rw-r--r--src/clientiface.cpp855
-rw-r--r--src/clientiface.h526
-rw-r--r--src/clientmap.cpp733
-rw-r--r--src/clientmap.h159
-rw-r--r--src/clientmedia.cpp655
-rw-r--r--src/clientmedia.h151
-rw-r--r--src/clientobject.cpp66
-rw-r--r--src/clientobject.h112
-rw-r--r--src/clientserver.h227
-rw-r--r--src/clientsimpleobject.h37
-rw-r--r--src/cloudparams.h33
-rw-r--r--src/clouds.cpp376
-rw-r--r--src/clouds.h147
-rw-r--r--src/cmake_config.h.in40
-rw-r--r--src/cmake_config_githash.h.in10
-rw-r--r--src/collision.cpp590
-rw-r--r--src/collision.h97
-rw-r--r--src/config.h42
-rw-r--r--src/config.h.in16
-rw-r--r--src/connection.cpp1381
-rw-r--r--src/connection.h505
-rw-r--r--src/constants.h114
-rw-r--r--src/content_abm.cpp34
-rw-r--r--src/content_abm.h32
-rw-r--r--src/content_cao.cpp1853
-rw-r--r--src/content_cao.h216
-rw-r--r--src/content_cso.cpp79
-rw-r--r--src/content_cso.h30
-rw-r--r--src/content_mapblock.cpp1308
-rw-r--r--src/content_mapblock.h146
-rw-r--r--src/content_mapnode.cpp169
-rw-r--r--src/content_mapnode.h37
-rw-r--r--src/content_nodemeta.cpp200
-rw-r--r--src/content_nodemeta.h40
-rw-r--r--src/content_sao.cpp1447
-rw-r--r--src/content_sao.h416
-rw-r--r--src/convert_json.cpp387
-rw-r--r--src/convert_json.h34
-rw-r--r--src/craftdef.cpp1139
-rw-r--r--src/craftdef.h453
-rw-r--r--src/database-dummy.cpp59
-rw-r--r--src/database-dummy.h47
-rw-r--r--src/database-files.cpp179
-rw-r--r--src/database-files.h46
-rw-r--r--src/database-leveldb.cpp101
-rw-r--r--src/database-leveldb.h50
-rw-r--r--src/database-postgresql.cpp635
-rw-r--r--src/database-postgresql.h150
-rw-r--r--src/database-redis.cpp203
-rw-r--r--src/database-redis.h54
-rw-r--r--src/database-sqlite3.cpp629
-rw-r--r--src/database-sqlite3.h196
-rw-r--r--src/database.cpp69
-rw-r--r--src/database.h65
-rw-r--r--src/daynightratio.h69
-rw-r--r--src/debug.cpp326
-rw-r--r--src/debug.h275
-rw-r--r--src/defaultsettings.cpp453
-rw-r--r--src/defaultsettings.h38
-rw-r--r--src/drawscene.cpp674
-rw-r--r--src/drawscene.h39
-rw-r--r--src/dungeongen.cpp681
-rw-r--r--src/dungeongen.h113
-rw-r--r--src/emerge.cpp667
-rw-r--r--src/emerge.h184
-rw-r--r--src/environment.cpp344
-rw-r--r--src/environment.h128
-rw-r--r--src/event.h72
-rw-r--r--src/event_manager.h115
-rw-r--r--src/exceptions.h161
-rw-r--r--src/face_position_cache.cpp110
-rw-r--r--src/face_position_cache.h44
-rw-r--r--src/filecache.cpp89
-rw-r--r--src/filecache.h45
-rw-r--r--src/filesys.cpp732
-rw-r--r--src/filesys.h97
-rw-r--r--src/fontengine.cpp513
-rw-r--r--src/fontengine.h139
-rw-r--r--src/game.cpp4727
-rw-r--r--src/game.h57
-rw-r--r--src/gamedef.h85
-rw-r--r--src/gameparams.h35
-rw-r--r--src/genericobject.cpp200
-rw-r--r--src/genericobject.h88
-rw-r--r--src/gettext.cpp248
-rw-r--r--src/gettext.h63
-rw-r--r--src/gettime.h43
-rw-r--r--src/guiChatConsole.cpp663
-rw-r--r--src/guiChatConsole.h138
-rw-r--r--src/guiEngine.cpp626
-rw-r--r--src/guiEngine.h313
-rw-r--r--src/guiFileSelectMenu.cpp119
-rw-r--r--src/guiFileSelectMenu.h63
-rw-r--r--src/guiFormSpecMenu.cpp3890
-rw-r--r--src/guiFormSpecMenu.h579
-rw-r--r--src/guiInventoryMenu.cpp351
-rw-r--r--src/guiInventoryMenu.h111
-rw-r--r--src/guiKeyChangeMenu.cpp434
-rw-r--r--src/guiKeyChangeMenu.h77
-rw-r--r--src/guiMainMenu.h68
-rw-r--r--src/guiMessageMenu.cpp152
-rw-r--r--src/guiMessageMenu.h61
-rw-r--r--src/guiPasswordChange.cpp267
-rw-r--r--src/guiPasswordChange.h55
-rw-r--r--src/guiPauseMenu.cpp195
-rw-r--r--src/guiPauseMenu.h50
-rw-r--r--src/guiTable.cpp1273
-rw-r--r--src/guiTable.h268
-rw-r--r--src/guiTextInputMenu.cpp192
-rw-r--r--src/guiTextInputMenu.h61
-rw-r--r--src/guiVolumeChange.cpp166
-rw-r--r--src/guiVolumeChange.h50
-rw-r--r--src/guiscalingfilter.cpp169
-rw-r--r--src/guiscalingfilter.h52
-rw-r--r--src/heightmap.cpp1060
-rw-r--r--src/heightmap.h572
-rw-r--r--src/httpfetch.cpp832
-rw-r--r--src/httpfetch.h122
-rw-r--r--src/hud.cpp781
-rw-r--r--src/hud.h202
-rw-r--r--src/imagefilters.cpp172
-rw-r--r--src/imagefilters.h46
-rw-r--r--src/intlGUIEditBox.cpp1510
-rw-r--r--src/intlGUIEditBox.h178
-rw-r--r--src/inventory.cpp953
-rw-r--r--src/inventory.h640
-rw-r--r--src/inventorymanager.cpp922
-rw-r--r--src/inventorymanager.h262
-rw-r--r--src/irr_aabb3d.h29
-rw-r--r--src/irr_v2d.h (renamed from src/common_irrlicht.h)25
-rw-r--r--src/irr_v3d.h33
-rw-r--r--src/irrlicht_changes/CMakeLists.txt7
-rw-r--r--src/irrlicht_changes/static_text.cpp683
-rw-r--r--src/irrlicht_changes/static_text.h268
-rw-r--r--src/irrlichttypes.h66
-rw-r--r--src/irrlichttypes_bloated.h31
-rw-r--r--src/irrlichttypes_extrabloated.h38
-rw-r--r--src/irrlichtwrapper.cpp219
-rw-r--r--src/irrlichtwrapper.h200
-rw-r--r--src/itemdef.cpp599
-rw-r--r--src/itemdef.h178
-rw-r--r--src/itemgroup.h36
-rw-r--r--src/itemstackmetadata.cpp45
-rw-r--r--src/itemstackmetadata.h35
-rw-r--r--src/jthread/CMakeLists.txt16
-rw-r--r--src/jthread/LICENSE.MIT20
-rw-r--r--src/jthread/jmutex.h70
-rw-r--r--src/jthread/jmutexautolock.h43
-rw-r--r--src/jthread/jthread.h77
-rw-r--r--src/jthread/pthread/jmutex.cpp67
-rw-r--r--src/jthread/pthread/jthread.cpp180
-rw-r--r--src/jthread/win32/jmutex.cpp83
-rw-r--r--src/jthread/win32/jthread.cpp177
-rw-r--r--src/keycode.cpp392
-rw-r--r--src/keycode.h70
-rw-r--r--src/light.cpp183
-rw-r--r--src/light.h109
-rw-r--r--src/localplayer.cpp1104
-rw-r--r--src/localplayer.h182
-rw-r--r--src/log.cpp367
-rw-r--r--src/log.h216
-rw-r--r--src/main.cpp3645
-rw-r--r--src/main.h51
-rw-r--r--src/mainmenumanager.h186
-rw-r--r--src/map.cpp5215
-rw-r--r--src/map.h852
-rw-r--r--src/map_settings_manager.cpp193
-rw-r--r--src/map_settings_manager.h79
-rw-r--r--src/mapblock.cpp1774
-rw-r--r--src/mapblock.h858
-rw-r--r--src/mapblock_mesh.cpp1595
-rw-r--r--src/mapblock_mesh.h276
-rw-r--r--src/mapblockobject.cpp948
-rw-r--r--src/mapblockobject.h1112
-rw-r--r--src/mapgen.cpp1103
-rw-r--r--src/mapgen.h310
-rw-r--r--src/mapgen_flat.cpp276
-rw-r--r--src/mapgen_flat.h78
-rw-r--r--src/mapgen_fractal.cpp396
-rw-r--r--src/mapgen_fractal.h89
-rw-r--r--src/mapgen_singlenode.cpp107
-rw-r--r--src/mapgen_singlenode.h52
-rw-r--r--src/mapgen_v5.cpp291
-rw-r--r--src/mapgen_v5.h76
-rw-r--r--src/mapgen_v6.cpp1111
-rw-r--r--src/mapgen_v6.h170
-rw-r--r--src/mapgen_v7.cpp729
-rw-r--r--src/mapgen_v7.h108
-rw-r--r--src/mapgen_valleys.cpp751
-rw-r--r--src/mapgen_valleys.h137
-rw-r--r--src/mapnode.cpp839
-rw-r--r--src/mapnode.h679
-rw-r--r--src/mapsector.cpp569
-rw-r--r--src/mapsector.h280
-rw-r--r--src/materials.cpp87
-rw-r--r--src/materials.h97
-rw-r--r--src/mesh.cpp1136
-rw-r--r--src/mesh.h127
-rw-r--r--src/mesh_generator_thread.cpp335
-rw-r--r--src/mesh_generator_thread.h135
-rw-r--r--src/metadata.cpp108
-rw-r--r--src/metadata.h61
-rw-r--r--src/mg_biome.cpp243
-rw-r--r--src/mg_biome.h233
-rw-r--r--src/mg_decoration.cpp378
-rw-r--r--src/mg_decoration.h153
-rw-r--r--src/mg_ore.cpp448
-rw-r--r--src/mg_ore.h172
-rw-r--r--src/mg_schematic.cpp583
-rw-r--r--src/mg_schematic.h150
-rw-r--r--src/minimap.cpp636
-rw-r--r--src/minimap.h168
-rw-r--r--src/modalMenu.h94
-rw-r--r--src/modifiedstate.h36
-rw-r--r--src/mods.cpp461
-rw-r--r--src/mods.h241
-rw-r--r--src/nameidmapping.cpp49
-rw-r--r--src/nameidmapping.h88
-rw-r--r--src/network/CMakeLists.txt16
-rw-r--r--src/network/clientopcodes.cpp213
-rw-r--r--src/network/clientopcodes.h52
-rw-r--r--src/network/clientpackethandler.cpp1312
-rw-r--r--src/network/connection.cpp3129
-rw-r--r--src/network/connection.h1088
-rw-r--r--src/network/networkpacket.cpp534
-rw-r--r--src/network/networkpacket.h135
-rw-r--r--src/network/networkprotocol.h968
-rw-r--r--src/network/serveropcodes.cpp213
-rw-r--r--src/network/serveropcodes.h52
-rw-r--r--src/network/serverpackethandler.cpp2072
-rw-r--r--src/nodedef.cpp2030
-rw-r--r--src/nodedef.h525
-rw-r--r--src/nodemetadata.cpp251
-rw-r--r--src/nodemetadata.h99
-rw-r--r--src/nodetimer.cpp152
-rw-r--r--src/nodetimer.h136
-rw-r--r--src/noise.cpp810
-rw-r--r--src/noise.h254
-rw-r--r--src/objdef.cpp184
-rw-r--r--src/objdef.h99
-rw-r--r--src/object_properties.cpp171
-rw-r--r--src/object_properties.h65
-rw-r--r--src/particles.cpp662
-rw-r--r--src/particles.h224
-rw-r--r--src/pathfinder.cpp1411
-rw-r--r--src/pathfinder.h66
-rw-r--r--src/player.cpp503
-rw-r--r--src/player.h370
-rw-r--r--src/porting.cpp1024
-rw-r--r--src/porting.h330
-rw-r--r--src/porting_android.cpp327
-rw-r--r--src/porting_android.h81
-rw-r--r--src/profiler.cpp53
-rw-r--r--src/profiler.h206
-rw-r--r--src/quicktune.cpp105
-rw-r--r--src/quicktune.h104
-rw-r--r--src/quicktune_shortcutter.h88
-rw-r--r--src/raycast.cpp89
-rw-r--r--src/raycast.h38
-rw-r--r--src/reflowscan.cpp207
-rw-r--r--src/reflowscan.h50
-rw-r--r--src/remoteplayer.cpp225
-rw-r--r--src/remoteplayer.h172
-rw-r--r--src/rollback.cpp973
-rw-r--r--src/rollback.h107
-rw-r--r--src/rollback_interface.cpp238
-rw-r--r--src/rollback_interface.h167
-rw-r--r--src/script/CMakeLists.txt21
-rw-r--r--src/script/common/CMakeLists.txt10
-rw-r--r--src/script/common/c_content.cpp1741
-rw-r--r--src/script/common/c_content.h191
-rw-r--r--src/script/common/c_converter.cpp700
-rw-r--r--src/script/common/c_converter.h121
-rw-r--r--src/script/common/c_internal.cpp200
-rw-r--r--src/script/common/c_internal.h110
-rw-r--r--src/script/common/c_types.cpp34
-rw-r--r--src/script/common/c_types.h64
-rw-r--r--src/script/cpp_api/CMakeLists.txt19
-rw-r--r--src/script/cpp_api/s_async.cpp300
-rw-r--r--src/script/cpp_api/s_async.h167
-rw-r--r--src/script/cpp_api/s_base.cpp344
-rw-r--r--src/script/cpp_api/s_base.h137
-rw-r--r--src/script/cpp_api/s_client.cpp230
-rw-r--r--src/script/cpp_api/s_client.h63
-rw-r--r--src/script/cpp_api/s_entity.cpp294
-rw-r--r--src/script/cpp_api/s_entity.h50
-rw-r--r--src/script/cpp_api/s_env.cpp255
-rw-r--r--src/script/cpp_api/s_env.h48
-rw-r--r--src/script/cpp_api/s_internal.h86
-rw-r--r--src/script/cpp_api/s_inventory.cpp249
-rw-r--r--src/script/cpp_api/s_inventory.h71
-rw-r--r--src/script/cpp_api/s_item.cpp262
-rw-r--r--src/script/cpp_api/s_item.h62
-rw-r--r--src/script/cpp_api/s_mainmenu.cpp93
-rw-r--r--src/script/cpp_api/s_mainmenu.h48
-rw-r--r--src/script/cpp_api/s_node.cpp300
-rw-r--r--src/script/cpp_api/s_node.h64
-rw-r--r--src/script/cpp_api/s_nodemeta.cpp243
-rw-r--r--src/script/cpp_api/s_nodemeta.h67
-rw-r--r--src/script/cpp_api/s_player.cpp199
-rw-r--r--src/script/cpp_api/s_player.h50
-rw-r--r--src/script/cpp_api/s_security.cpp781
-rw-r--r--src/script/cpp_api/s_security.h76
-rw-r--r--src/script/cpp_api/s_server.cpp160
-rw-r--r--src/script/cpp_api/s_server.h52
-rw-r--r--src/script/lua_api/CMakeLists.txt33
-rw-r--r--src/script/lua_api/l_areastore.cpp396
-rw-r--r--src/script/lua_api/l_areastore.h67
-rw-r--r--src/script/lua_api/l_base.cpp86
-rw-r--r--src/script/lua_api/l_base.h75
-rw-r--r--src/script/lua_api/l_camera.cpp202
-rw-r--r--src/script/lua_api/l_camera.h44
-rw-r--r--src/script/lua_api/l_client.cpp356
-rw-r--r--src/script/lua_api/l_client.h98
-rw-r--r--src/script/lua_api/l_craft.cpp516
-rw-r--r--src/script/lua_api/l_craft.h51
-rw-r--r--src/script/lua_api/l_env.cpp1163
-rw-r--r--src/script/lua_api/l_env.h256
-rw-r--r--src/script/lua_api/l_http.cpp193
-rw-r--r--src/script/lua_api/l_http.h50
-rw-r--r--src/script/lua_api/l_internal.h48
-rw-r--r--src/script/lua_api/l_inventory.cpp546
-rw-r--r--src/script/lua_api/l_inventory.h130
-rw-r--r--src/script/lua_api/l_item.cpp628
-rw-r--r--src/script/lua_api/l_item.h153
-rw-r--r--src/script/lua_api/l_itemstackmeta.cpp120
-rw-r--r--src/script/lua_api/l_itemstackmeta.h59
-rw-r--r--src/script/lua_api/l_localplayer.cpp358
-rw-r--r--src/script/lua_api/l_localplayer.h85
-rw-r--r--src/script/lua_api/l_mainmenu.cpp1165
-rw-r--r--src/script/lua_api/l_mainmenu.h157
-rw-r--r--src/script/lua_api/l_mapgen.cpp1531
-rw-r--r--src/script/lua_api/l_mapgen.h123
-rw-r--r--src/script/lua_api/l_metadata.cpp266
-rw-r--r--src/script/lua_api/l_metadata.h75
-rw-r--r--src/script/lua_api/l_minimap.cpp227
-rw-r--r--src/script/lua_api/l_minimap.h65
-rw-r--r--src/script/lua_api/l_nodemeta.cpp278
-rw-r--r--src/script/lua_api/l_nodemeta.h97
-rw-r--r--src/script/lua_api/l_nodetimer.cpp173
-rw-r--r--src/script/lua_api/l_nodetimer.h66
-rw-r--r--src/script/lua_api/l_noise.cpp717
-rw-r--r--src/script/lua_api/l_noise.h197
-rw-r--r--src/script/lua_api/l_object.cpp2009
-rw-r--r--src/script/lua_api/l_object.h336
-rw-r--r--src/script/lua_api/l_particles.cpp280
-rw-r--r--src/script/lua_api/l_particles.h37
-rw-r--r--src/script/lua_api/l_rollback.cpp117
-rw-r--r--src/script/lua_api/l_rollback.h38
-rw-r--r--src/script/lua_api/l_server.cpp547
-rw-r--r--src/script/lua_api/l_server.h114
-rw-r--r--src/script/lua_api/l_settings.cpp277
-rw-r--r--src/script/lua_api/l_settings.h81
-rw-r--r--src/script/lua_api/l_sound.cpp53
-rw-r--r--src/script/lua_api/l_sound.h36
-rw-r--r--src/script/lua_api/l_storage.cpp147
-rw-r--r--src/script/lua_api/l_storage.h63
-rw-r--r--src/script/lua_api/l_util.cpp560
-rw-r--r--src/script/lua_api/l_util.h110
-rw-r--r--src/script/lua_api/l_vmanip.cpp474
-rw-r--r--src/script/lua_api/l_vmanip.h84
-rw-r--r--src/script/scripting_client.cpp88
-rw-r--r--src/script/scripting_client.h46
-rw-r--r--src/script/scripting_mainmenu.cpp96
-rw-r--r--src/script/scripting_mainmenu.h54
-rw-r--r--src/script/scripting_server.cpp119
-rw-r--r--src/script/scripting_server.h59
-rw-r--r--src/serialization.cpp79
-rw-r--r--src/serialization.h82
-rw-r--r--src/server.cpp5994
-rw-r--r--src/server.h995
-rw-r--r--src/serverenvironment.cpp2274
-rw-r--r--src/serverenvironment.h447
-rw-r--r--src/serverlist.cpp269
-rw-r--r--src/serverlist.h55
-rw-r--r--src/servermain.cpp364
-rw-r--r--src/serverobject.cpp100
-rw-r--r--src/serverobject.h266
-rw-r--r--src/settings.cpp1036
-rw-r--r--src/settings.h248
-rw-r--r--src/settings_translation_file.cpp776
-rw-r--r--src/shader.cpp884
-rw-r--r--src/shader.h161
-rw-r--r--src/sky.cpp697
-rw-r--r--src/sky.h145
-rw-r--r--src/socket.cpp592
-rw-r--r--src/socket.h86
-rw-r--r--src/sound.cpp25
-rw-r--r--src/sound.h121
-rw-r--r--src/sound_openal.cpp698
-rw-r--r--src/sound_openal.h28
-rw-r--r--src/staticobject.cpp88
-rw-r--r--src/staticobject.h104
-rw-r--r--src/strfnd.h116
-rw-r--r--src/subgame.cpp325
-rw-r--r--src/subgame.h106
-rw-r--r--src/terminal_chat_console.cpp455
-rw-r--r--src/terminal_chat_console.h131
-rw-r--r--src/test.cpp1159
-rw-r--r--src/test.h26
-rw-r--r--src/threading/CMakeLists.txt7
-rw-r--r--src/threading/atomic.h139
-rw-r--r--src/threading/event.cpp91
-rw-r--r--src/threading/event.h67
-rw-r--r--src/threading/mutex.cpp116
-rw-r--r--src/threading/mutex.h84
-rw-r--r--src/threading/mutex_auto_lock.h62
-rw-r--r--src/threading/semaphore.cpp162
-rw-r--r--src/threading/semaphore.h55
-rw-r--r--src/threading/thread.cpp449
-rw-r--r--src/threading/thread.h179
-rw-r--r--src/threads.h99
-rw-r--r--src/tile.cpp113
-rw-r--r--src/tile.h111
-rw-r--r--src/tileanimation.cpp131
-rw-r--r--src/tileanimation.h62
-rw-r--r--src/tool.cpp210
-rw-r--r--src/tool.h143
-rw-r--r--src/touchscreengui.cpp1085
-rw-r--r--src/touchscreengui.h275
-rw-r--r--src/treegen.cpp875
-rw-r--r--src/treegen.h94
-rw-r--r--src/unittest/CMakeLists.txt30
-rw-r--r--src/unittest/test.cpp646
-rw-r--r--src/unittest/test.h147
-rw-r--r--src/unittest/test_areastore.cpp162
-rw-r--r--src/unittest/test_collision.cpp180
-rw-r--r--src/unittest/test_compression.cpp172
-rw-r--r--src/unittest/test_connection.cpp348
-rw-r--r--src/unittest/test_filepath.cpp264
-rw-r--r--src/unittest/test_inventory.cpp143
-rw-r--r--src/unittest/test_keycode.cpp129
-rw-r--r--src/unittest/test_map_settings_manager.cpp261
-rw-r--r--src/unittest/test_mapnode.cpp57
-rw-r--r--src/unittest/test_nodedef.cpp67
-rw-r--r--src/unittest/test_noderesolver.cpp210
-rw-r--r--src/unittest/test_noise.cpp285
-rw-r--r--src/unittest/test_objdef.cpp106
-rw-r--r--src/unittest/test_player.cpp40
-rw-r--r--src/unittest/test_profiler.cpp73
-rw-r--r--src/unittest/test_random.cpp274
-rw-r--r--src/unittest/test_schematic.cpp280
-rw-r--r--src/unittest/test_serialization.cpp649
-rw-r--r--src/unittest/test_settings.cpp207
-rw-r--r--src/unittest/test_socket.cpp151
-rw-r--r--src/unittest/test_threading.cpp183
-rw-r--r--src/unittest/test_utilities.cpp328
-rw-r--r--src/unittest/test_voxelalgorithms.cpp263
-rw-r--r--src/unittest/test_voxelmanipulator.cpp108
-rw-r--r--src/util/CMakeLists.txt16
-rw-r--r--src/util/areastore.cpp300
-rw-r--r--src/util/areastore.h204
-rw-r--r--src/util/auth.cpp137
-rw-r--r--src/util/auth.h50
-rw-r--r--src/util/base64.cpp131
-rw-r--r--src/util/base64.h10
-rw-r--r--src/util/basic_macros.h61
-rw-r--r--src/util/container.h307
-rw-r--r--src/util/cpp11.h32
-rw-r--r--src/util/cpp11_container.h43
-rw-r--r--src/util/directiontables.cpp101
-rw-r--r--src/util/directiontables.h88
-rw-r--r--src/util/enriched_string.cpp166
-rw-r--r--src/util/enriched_string.h91
-rw-r--r--src/util/hex.h61
-rw-r--r--src/util/md32_common.h428
-rw-r--r--src/util/numeric.cpp163
-rw-r--r--src/util/numeric.h379
-rw-r--r--src/util/pointedthing.cpp117
-rw-r--r--src/util/pointedthing.h91
-rw-r--r--src/util/pointer.h308
-rw-r--r--src/util/serialize.cpp684
-rw-r--r--src/util/serialize.h660
-rw-r--r--src/util/sha1.cpp207
-rw-r--r--src/util/sha1.h52
-rw-r--r--src/util/sha2.h157
-rw-r--r--src/util/sha256.c399
-rw-r--r--src/util/srp.cpp1036
-rw-r--r--src/util/srp.h194
-rw-r--r--src/util/strfnd.h82
-rw-r--r--src/util/string.cpp746
-rw-r--r--src/util/string.h653
-rw-r--r--src/util/thread.h234
-rw-r--r--src/util/timetaker.cpp64
-rw-r--r--src/util/timetaker.h54
-rw-r--r--src/utility.cpp331
-rw-r--r--src/utility.h1578
-rw-r--r--src/version.cpp38
-rw-r--r--src/version.h28
-rw-r--r--src/voxel.cpp985
-rw-r--r--src/voxel.h346
-rw-r--r--src/voxelalgorithms.cpp1474
-rw-r--r--src/voxelalgorithms.h173
-rw-r--r--src/wieldmesh.cpp590
-rw-r--r--src/wieldmesh.h141
-rw-r--r--textures/base/pack/air.pngbin0 -> 225 bytes-rw-r--r--textures/base/pack/blank.pngbin0 -> 95 bytes-rw-r--r--textures/base/pack/camera_btn.pngbin0 -> 729 bytes-rw-r--r--textures/base/pack/chat_btn.pngbin0 -> 399 bytes-rw-r--r--textures/base/pack/debug_btn.pngbin0 -> 895 bytes-rw-r--r--textures/base/pack/down.pngbin0 -> 1328 bytes-rw-r--r--textures/base/pack/down_arrow.pngbin0 -> 373 bytes-rw-r--r--textures/base/pack/drop_btn.pngbin0 -> 1035 bytes-rw-r--r--textures/base/pack/fast_btn.pngbin0 -> 875 bytes-rw-r--r--textures/base/pack/fly_btn.pngbin0 -> 720 bytes-rw-r--r--textures/base/pack/gear_icon.pngbin0 -> 1005 bytes-rw-r--r--textures/base/pack/halo.pngbin0 -> 144 bytes-rw-r--r--textures/base/pack/ignore.pngbin0 -> 234 bytes-rw-r--r--textures/base/pack/inventory_btn.pngbin0 -> 343 bytes-rw-r--r--textures/base/pack/jump_btn.pngbin0 -> 434 bytes-rw-r--r--textures/base/pack/left_arrow.pngbin0 -> 400 bytes-rw-r--r--textures/base/pack/logo.pngbin0 -> 12188 bytes-rw-r--r--textures/base/pack/menu_bg.pngbin0 -> 124 bytes-rw-r--r--textures/base/pack/menu_header.pngbin0 -> 1628 bytes-rw-r--r--textures/base/pack/minimap_mask_round.pngbin0 -> 1858 bytes-rw-r--r--textures/base/pack/minimap_mask_square.pngbin0 -> 420 bytes-rw-r--r--textures/base/pack/minimap_overlay_round.pngbin0 -> 22044 bytes-rw-r--r--textures/base/pack/minimap_overlay_square.pngbin0 -> 1686 bytes-rw-r--r--textures/base/pack/no_screenshot.pngbin0 -> 586 bytes-rw-r--r--textures/base/pack/noclip_btn.pngbin0 -> 1087 bytes-rw-r--r--textures/base/pack/object_marker_red.pngbin0 -> 449 bytes-rw-r--r--textures/base/pack/player_marker.pngbin0 -> 2178 bytes-rw-r--r--textures/base/pack/progress_bar.pngbin0 -> 413 bytes-rw-r--r--textures/base/pack/progress_bar_bg.pngbin0 -> 354 bytes-rw-r--r--textures/base/pack/rangeview_btn.pngbin0 -> 1476 bytes-rw-r--r--textures/base/pack/rare_controls.pngbin0 -> 349 bytes-rw-r--r--textures/base/pack/right_arrow.pngbin0 -> 396 bytes-rw-r--r--textures/base/pack/server_flags_creative.pngbin0 -> 273 bytes-rw-r--r--textures/base/pack/server_flags_damage.pngbin0 -> 713 bytes-rw-r--r--textures/base/pack/server_flags_favorite.pngbin0 -> 916 bytes-rw-r--r--textures/base/pack/server_flags_pvp.pngbin0 -> 1048 bytes-rw-r--r--textures/base/pack/server_ping_1.pngbin0 -> 251 bytes-rw-r--r--textures/base/pack/server_ping_2.pngbin0 -> 244 bytes-rw-r--r--textures/base/pack/server_ping_3.pngbin0 -> 245 bytes-rw-r--r--textures/base/pack/server_ping_4.pngbin0 -> 213 bytes-rw-r--r--textures/base/pack/smoke_puff.pngbin0 -> 202 bytes-rw-r--r--textures/base/pack/sunrisebg.pngbin0 -> 4231 bytes-rw-r--r--textures/base/pack/unknown_item.pngbin0 -> 292 bytes-rw-r--r--textures/base/pack/unknown_node.pngbin0 -> 193 bytes-rw-r--r--textures/base/pack/unknown_object.pngbin0 -> 254 bytes-rw-r--r--textures/base/pack/up_arrow.pngbin0 -> 373 bytes-rw-r--r--textures/texture_packs_here.txt1
-rwxr-xr-xutil/buildbot/buildwin32.sh157
-rwxr-xr-xutil/buildbot/buildwin64.sh158
-rw-r--r--util/buildbot/toolchain_mingw.cmake17
-rw-r--r--util/buildbot/toolchain_mingw64.cmake17
-rwxr-xr-xutil/bump_version.sh121
-rw-r--r--util/colors.txt78
-rwxr-xr-xutil/generate-texture-normals.sh255
-rwxr-xr-xutil/minetestmapper.py752
-rwxr-xr-xutil/sectors2sqlite.py132
-rwxr-xr-xutil/test_multiplayer.sh43
-rwxr-xr-xutil/travis/before_install.sh54
-rw-r--r--util/travis/clang-format-whitelist.txt389
-rw-r--r--util/travis/common.sh54
-rw-r--r--util/travis/lint.sh45
-rwxr-xr-xutil/travis/script.sh72
-rw-r--r--util/travis/toolchain_mingw.cmake.in18
-rwxr-xr-xutil/updatepo.sh76
-rw-r--r--util/wireshark/minetest.lua1329
1039 files changed, 417685 insertions, 31100 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 000000000..d2e2f2940
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,28 @@
+BasedOnStyle: LLVM
+IndentWidth: 8
+UseTab: Always
+BreakBeforeBraces: Custom
+Standard: Cpp03
+BraceWrapping:
+ AfterClass: true
+ AfterControlStatement: false
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: true
+ AfterStruct: true
+ AfterUnion: true
+ BeforeCatch: false
+ BeforeElse: false
+AllowShortIfStatementsOnASingleLine: false
+IndentCaseLabels: false
+AccessModifierOffset: -8
+ColumnLimit: 90
+AllowShortFunctionsOnASingleLine: Inline
+SortIncludes: false
+IncludeCategories:
+ - Regex: '^".*'
+ Priority: 2
+ - Regex: '^<.*'
+ Priority: 1
+AlignAfterOpenBracket: DontAlign
+ContinuationIndentWidth: 16
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..2e62a4e59
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+*.cpp diff=cpp
+*.h diff=cpp
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..7b5ecab67
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,101 @@
+## Editors and Development environments
+*~
+*.swp
+*.bak*
+*.orig
+# Vim
+*.vim
+# Kate
+.*.kate-swp
+.swp.*
+# KDevelop4
+.kdev4/
+*.kdev4
+# Eclipse (CDT and LDT)
+.project
+.cproject
+.settings/
+.buildpath
+.metadata
+# GNU Global
+tags
+!tags/
+gtags.files
+.idea/*
+# Codelite
+*.project
+
+## Files related to minetest development cycle
+/*.patch
+*.diff
+# GNU Patch reject file
+*.rej
+
+## Non-static Minetest directories or symlinks to these
+/bin/
+/games/*
+!/games/minimal/
+/cache
+/textures/*
+!/textures/base/
+/screenshots
+/sounds
+/mods/*
+!/mods/minetest/
+/mods/minetest/*
+!/mods/minetest/mods_here.txt
+/worlds
+/world/
+/clientmods/*
+!/clientmods/preview/
+/client/mod_storage/
+
+## Configuration/log files
+minetest.conf
+debug.txt
+
+## Other files generated by minetest
+screenshot_*.png
+
+## Doxygen files
+doc/Doxyfile
+doc/html/
+doc/doxygen_*
+
+## Build files
+CMakeFiles
+Makefile
+!build/android/Makefile
+cmake_install.cmake
+CMakeCache.txt
+CPackConfig.cmake
+CPackSourceConfig.cmake
+src/android_version.h
+src/android_version_githash.h
+src/cmake_config.h
+src/cmake_config_githash.h
+src/lua/build/
+locale/
+.directory
+*.cbp
+*.layout
+*.o
+*.a
+*.ninja
+.ninja*
+*.gch
+cmake-build-debug/
+cmake-build-release/
+
+## Android build files
+build/android/src/main/assets
+build/android/build
+build/android/deps
+build/android/libs
+build/android/jni/lib
+build/android/jni/src
+build/android/src/main/jniLibs
+build/android/obj
+build/android/local.properties
+build/android/.gradle
+timestamp
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 000000000..ef3b371a4
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,223 @@
+---
+# Github repository is cloned every day on Gitlab.com
+# https://gitlab.com/minetest/minetest
+# Pipelines URL: https://gitlab.com/minetest/minetest/pipelines
+
+stages:
+ - build
+ - package
+ - deploy
+
+variables:
+ MINETEST_GAME_REPO: "https://github.com/minetest/minetest_game.git"
+
+.build_template: &build_definition
+ stage: build
+ script:
+ - 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 -DBUILD_SERVER=TRUE ..
+ - make -j2
+ - make install
+ artifacts:
+ when: on_success
+ expire_in: 1h
+ paths:
+ - artifact/*
+
+.debpkg_template: &debpkg_template
+ stage: package
+ before_script:
+ - apt-get update -y
+ - apt-get install -y git
+ - mkdir -p build/deb/minetest/DEBIAN/
+ - cp misc/debpkg-control build/deb/minetest/DEBIAN/control
+ - cp -Rp artifact/minetest/usr build/deb/minetest/
+ script:
+ - git clone $MINETEST_GAME_REPO build/deb/minetest/usr/share/minetest/games/minetest
+ - 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/LEVELDB_PLACEHOLDER/'$LEVELDB_PKG'/g' build/deb/minetest/DEBIAN/control
+ - cd build/deb/ && dpkg-deb -b minetest/
+ artifacts:
+ when: on_success
+ expire_in: 30 day
+ paths:
+ - build/deb/*.deb
+
+.debpkg_install: &debpkg_install
+ stage: deploy
+ before_script:
+ - apt-get update -y
+ - apt-get install -y libc6 libcurl3-gnutls libfreetype6 libirrlicht1.8 $LEVELDB_PKG liblua5.1-0 libluajit-5.1-2 libopenal1 libstdc++6 libvorbisfile3 libx11-6 zlib1g
+ script:
+ - dpkg -i build/deb/*.deb
+
+##
+## Debian
+##
+
+# Jessie
+
+build:debian-8:
+ <<: *build_definition
+ image: debian:8
+ 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-8:
+ image: debian:8
+ dependencies:
+ - build:debian-8
+ variables:
+ LEVELDB_PKG: libleveldb1
+ <<: *debpkg_template
+
+deploy:debian-8:
+ image: debian:8
+ dependencies:
+ - package:debian-8
+ variables:
+ LEVELDB_PKG: libleveldb1
+ <<: *debpkg_install
+
+# Stretch
+
+build:debian-9:
+ <<: *build_definition
+ 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:
+ image: debian:9
+ dependencies:
+ - build:debian-9
+ variables:
+ LEVELDB_PKG: libleveldb1v5
+ <<: *debpkg_template
+
+deploy:debian-9:
+ image: debian:9
+ dependencies:
+ - package:debian-9
+ variables:
+ LEVELDB_PKG: libleveldb1v5
+ <<: *debpkg_install
+
+##
+## Ubuntu
+##
+
+# Trusty
+
+build:ubuntu-14.04:
+ <<: *build_definition
+ image: ubuntu:trusty
+ 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-14.04:
+ image: ubuntu:trusty
+ dependencies:
+ - build:ubuntu-14.04
+ variables:
+ LEVELDB_PKG: libleveldb1
+ <<: *debpkg_template
+
+deploy:ubuntu-14.04:
+ image: ubuntu:trusty
+ dependencies:
+ - package:ubuntu-14.04
+ variables:
+ LEVELDB_PKG: libleveldb1
+ <<: *debpkg_install
+
+# Xenial
+
+build:ubuntu-16.04:
+ <<: *build_definition
+ 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
+
+package:ubuntu-16.04:
+ image: ubuntu:xenial
+ dependencies:
+ - build:ubuntu-16.04
+ variables:
+ LEVELDB_PKG: libleveldb1v5
+ <<: *debpkg_template
+
+deploy:ubuntu-16.04:
+ image: ubuntu:xenial
+ dependencies:
+ - package:ubuntu-16.04
+ variables:
+ LEVELDB_PKG: libleveldb1v5
+ <<: *debpkg_install
+
+# Yakkety
+
+build:ubuntu-16.10:
+ <<: *build_definition
+ image: ubuntu:yakkety
+ 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-16.10:
+ image: ubuntu:yakkety
+ dependencies:
+ - build:ubuntu-16.10
+ variables:
+ LEVELDB_PKG: libleveldb1v5
+ <<: *debpkg_template
+
+deploy:ubuntu-16.10:
+ image: ubuntu:yakkety
+ dependencies:
+ - package:ubuntu-16.10
+ variables:
+ LEVELDB_PKG: libleveldb1v5
+ <<: *debpkg_install
+
+# Zesty
+
+build:ubuntu-17.04:
+ <<: *build_definition
+ image: ubuntu:zesty
+ 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-17.04:
+ image: ubuntu:zesty
+ dependencies:
+ - build:ubuntu-17.04
+ variables:
+ LEVELDB_PKG: libleveldb1v5
+ <<: *debpkg_template
+
+deploy:ubuntu-17.04:
+ image: ubuntu:zesty
+ dependencies:
+ - package:ubuntu-17.04
+ variables:
+ LEVELDB_PKG: libleveldb1v5
+ <<: *debpkg_install
+
+##
+## Fedora
+##
+
+build:fedora-24:
+ <<: *build_definition
+ image: fedora:24
+ before_script:
+ - dnf -y install make automake gcc gcc-c++ kernel-devel cmake libcurl* openal* libvorbis* libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel irrlicht-devel bzip2-libs gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel doxygen spatialindex-devel bzip2-devel
diff --git a/.hgtags b/.hgtags
deleted file mode 100644
index 1382e9445..000000000
--- a/.hgtags
+++ /dev/null
@@ -1,2 +0,0 @@
-c37bcfd89dd627fdb131ae3f77fcaab02721bf76 working
-69547bd6be420eb40f55524fd2131cbbaa2e0e29 110107195706-exp
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 000000000..c487460a0
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,33 @@
+0gb.us <0gb.us@0gb.us> <us_0gb@laptop-0gb-us.0gb.us>
+Calinou <calinou9999@gmail.com> <calinou9999spam@gmail.com>
+Perttu Ahola <celeron55@gmail.com> celeron55 <celeron55@gmail.com>
+Perttu Ahola <celeron55@gmail.com> celeron55 <celeron55@armada.(none)>
+Craig Robbins <kde.psych@gmail.com> <crobbins@localhost.localdomain>
+Diego Martínez <kaeza@users.sf.net> <lkaezadl3@gmail.com>
+Ilya Zhuravlev <zhuravlevilya@ya.ru> <whatever@xyz.is>
+kwolekr <kwolekr@minetest.net> <mirrorisim@gmail.com>
+PilzAdam <pilzadam@minetest.net> PilzAdam <adam-k@outlook.com>
+PilzAdam <pilzadam@minetest.net> Pilz Adam <PilzAdam@gmx.de>
+PilzAdam <pilzadam@minetest.net> PilzAdam <PilzAdam@gmx.de>
+proller <proller@github.com> <proler@github.com>
+proller <proller@github.com> <proler@gmail.com>
+RealBadAngel <maciej.kasatkin@o2.pl> <mk@realbadangel.pl>
+RealBadAngel <maciej.kasatkin@o2.pl> <maciej.kasatkin@yahoo.com>
+Selat <LongExampleTestName@gmail.com> <LongExampletestName@gmail.com>
+ShadowNinja <shadowninja@minetest.net> ShadowNinja <noreply@gmail.com>
+Shen Zheyu <arsdragonfly@gmail.com> arsdragonfly <arsdragonfly@gmail.com>
+Pavel Elagin <elagin.pasha@gmail.com> elagin <elagin.pasha@gmail.com>
+Esteban I. Ruiz Moreno <exio4.com@gmail.com> Esteban I. RM <exio4.com@gmail.com>
+manuel duarte <ffrogger0@yahoo.com> manuel joaquim <ffrogger0@yahoo.com>
+manuel duarte <ffrogger0@yahoo.com> sweetbomber <ffrogger _zero_ at yahoo dot com>
+Diego Martínez <kaeza@users.sf.net> kaeza <kaeza@users.sf.net>
+Diego Martínez <kaeza@users.sf.net> Diego Martinez <kaeza@users.sf.net>
+Lord James <neftali_dtctv@hotmail.com> Lord89James <neftali_dtctv@hotmail.com>
+BlockMen <nmuelll@web.de> Block Men <nmuelll@web.de>
+sfan5 <sfan5@live.de> Sfan5 <sfan5@live.de>
+DannyDark <the_skeleton_of_a_child@yahoo.co.uk> dannydark <the_skeleton_of_a_child@yahoo.co.uk>
+Ilya Pavlov <TTChangeTheWorld@gmail.com> Ilya <TTChangeTheWorld@gmail.com>
+Ilya Zhuravlev <zhuravlevilya@ya.ru> xyzz <zhuravlevilya@ya.ru>
+sapier <Sapier at GMX dot net> sapier <sapier AT gmx DOT net>
+sapier <Sapier at GMX dot net> sapier <sapier at gmx dot net>
+
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..57d934c90
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,39 @@
+language: cpp
+before_install: ./util/travis/before_install.sh
+script: ./util/travis/script.sh
+sudo: required
+notifications:
+ email: false
+matrix:
+ fast_finish: true
+ include:
+ - env: PLATFORM=Win32
+ compiler: gcc
+ os: linux
+ - env: PLATFORM=Win64
+ compiler: gcc
+ os: linux
+ - env: PLATFORM=Unix COMPILER=clang
+ compiler: clang
+ os: osx
+ - env: PLATFORM=Unix COMPILER=g++
+ compiler: gcc
+ os: linux
+ - env: PLATFORM=Unix COMPILER=clang
+ compiler: clang
+ os: linux
+ - env: PLATFORM=Unix COMPILER=clang VALGRIND=1
+ compiler: clang
+ os: linux
+ dist: trusty
+ - env: COMPILER=none LINT=1
+ compiler: clang
+ os: linux
+ dist: trusty
+ - env: PLATFORM=Unix COMPILER=g++-6
+ compiler: gcc
+ os: linux
+addons:
+ apt:
+ sources: &sources
+ - ubuntu-toolchain-r-test
diff --git a/CMakeLists.txt b/CMakeLists.txt
index dc9d98b36..b63b10f70 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,29 +1,54 @@
cmake_minimum_required(VERSION 2.6)
+
if(${CMAKE_VERSION} STREQUAL "2.8.2")
- # bug http://vtk.org/Bug/view.php?id=11020
- message( WARNING "CMake/CPack version 2.8.2 will not create working .deb packages!")
-endif(${CMAKE_VERSION} STREQUAL "2.8.2")
+ # Bug http://vtk.org/Bug/view.php?id=11020
+ message(WARNING "CMake/CPack version 2.8.2 will not create working .deb packages!")
+endif()
# This can be read from ${PROJECT_NAME} after project() is called
project(minetest)
+set(PROJECT_NAME_CAPITALIZED "Minetest")
+
+# Also remember to set PROTOCOL_VERSION in network/networkprotocol.h when releasing
set(VERSION_MAJOR 0)
-set(VERSION_MINOR 0)
-set(VERSION_PATCH 1)
-set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}")
+set(VERSION_MINOR 4)
+set(VERSION_PATCH 17)
+set(VERSION_TWEAK 2)
+set(VERSION_EXTRA "LinuxForks" CACHE STRING "Stuff to append to version string")
+
+# Change to false for releases
+set(DEVELOPMENT_BUILD FALSE)
+
+set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_TWEAK}")
+if(VERSION_EXTRA)
+ set(VERSION_STRING ${VERSION_STRING}-${VERSION_EXTRA})
+elseif(DEVELOPMENT_BUILD)
+ set(VERSION_STRING "${VERSION_STRING}-dev")
+endif()
-# Configuration options
+if (CMAKE_BUILD_TYPE STREQUAL Debug)
+ # Append "-debug" to version string
+ set(VERSION_STRING "${VERSION_STRING}-debug")
+endif()
+
+message(STATUS "*** Will build version ${VERSION_STRING} ***")
+
+# Configuration options
+set(DEFAULT_RUN_IN_PLACE FALSE)
if(WIN32)
- set(RUN_IN_PLACE 1 CACHE BOOL "Run directly in source directory structure")
-else()
- set(RUN_IN_PLACE 0 CACHE BOOL "Run directly in source directory structure")
+ set(DEFAULT_RUN_IN_PLACE TRUE)
endif()
+set(RUN_IN_PLACE ${DEFAULT_RUN_IN_PLACE} CACHE BOOL
+ "Run directly in source directory structure")
-set(BUILD_CLIENT 1 CACHE BOOL "Build client")
-set(BUILD_SERVER 1 CACHE BOOL "Build server")
-set(WARN_ALL 1 CACHE BOOL "Enable -Wall for Release build")
+set(BUILD_CLIENT TRUE CACHE BOOL "Build client")
+set(BUILD_SERVER FALSE CACHE BOOL "Build server")
+
+
+set(WARN_ALL TRUE CACHE BOOL "Enable -Wall for Release build")
if(NOT CMAKE_BUILD_TYPE)
# Default to release
@@ -32,39 +57,152 @@ endif()
# Included stuff
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
-include(${CMAKE_SOURCE_DIR}/cmake/Modules/misc.cmake)
-# This is done here so that relative search paths are more reasnable
+
+# This is done here so that relative search paths are more reasonable
find_package(Irrlicht)
-#
+
# Installation
-#
if(WIN32)
- set(DATADIR "data")
+ set(SHAREDIR ".")
set(BINDIR "bin")
set(DOCDIR "doc")
+ set(EXAMPLE_CONF_DIR ".")
+ set(LOCALEDIR "locale")
elseif(APPLE)
- set(DATADIR "share/minetest")
- set(BINDIR "bin")
- set(DOCDIR "share/doc/minetest")
-elseif(UNIX)
- set(DATADIR "share/minetest")
- set(BINDIR "bin")
- set(DOCDIR "share/doc/minetest")
+ set(BUNDLE_NAME ${PROJECT_NAME}.app)
+ set(BUNDLE_PATH "${BUNDLE_NAME}")
+ set(BINDIR ${BUNDLE_NAME}/Contents/MacOS)
+ set(SHAREDIR ${BUNDLE_NAME}/Contents/Resources)
+ set(DOCDIR "${SHAREDIR}/${PROJECT_NAME}")
+ set(EXAMPLE_CONF_DIR ${DOCDIR})
+ set(LOCALEDIR "${SHAREDIR}/locale")
+elseif(UNIX) # Linux, BSD etc
+ if(RUN_IN_PLACE)
+ set(SHAREDIR ".")
+ set(BINDIR "bin")
+ set(DOCDIR "doc")
+ set(EXAMPLE_CONF_DIR ".")
+ set(MANDIR "unix/man")
+ set(XDG_APPS_DIR "unix/applications")
+ set(APPDATADIR "unix/metainfo")
+ set(ICONDIR "unix/icons")
+ set(LOCALEDIR "locale")
+ else()
+ set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}")
+ set(BINDIR "${CMAKE_INSTALL_PREFIX}/bin")
+ set(DOCDIR "${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME}")
+ set(MANDIR "${CMAKE_INSTALL_PREFIX}/share/man")
+ set(EXAMPLE_CONF_DIR ${DOCDIR})
+ set(XDG_APPS_DIR "${CMAKE_INSTALL_PREFIX}/share/applications")
+ set(APPDATADIR "${CMAKE_INSTALL_PREFIX}/share/metainfo")
+ set(ICONDIR "${CMAKE_INSTALL_PREFIX}/share/icons")
+ set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}/locale")
+ endif()
endif()
-install(FILES "doc/README.txt" DESTINATION "${DOCDIR}")
-install(FILES "minetest.conf.example" DESTINATION "${DOCDIR}")
+set(CUSTOM_SHAREDIR "" CACHE STRING "Directory to install data files into")
+if(NOT CUSTOM_SHAREDIR STREQUAL "")
+ set(SHAREDIR "${CUSTOM_SHAREDIR}")
+ message(STATUS "Using SHAREDIR=${SHAREDIR}")
+endif()
+
+set(CUSTOM_BINDIR "" CACHE STRING "Directory to install binaries into")
+if(NOT CUSTOM_BINDIR STREQUAL "")
+ set(BINDIR "${CUSTOM_BINDIR}")
+ message(STATUS "Using BINDIR=${BINDIR}")
+endif()
+
+set(CUSTOM_DOCDIR "" CACHE STRING "Directory to install documentation into")
+if(NOT CUSTOM_DOCDIR STREQUAL "")
+ set(DOCDIR "${CUSTOM_DOCDIR}")
+ if(NOT RUN_IN_PLACE)
+ set(EXAMPLE_CONF_DIR ${DOCDIR})
+ endif()
+ message(STATUS "Using DOCDIR=${DOCDIR}")
+endif()
+
+set(CUSTOM_MANDIR "" CACHE STRING "Directory to install manpages into")
+if(NOT CUSTOM_MANDIR STREQUAL "")
+ set(MANDIR "${CUSTOM_MANDIR}")
+ message(STATUS "Using MANDIR=${MANDIR}")
+endif()
+
+set(CUSTOM_EXAMPLE_CONF_DIR "" CACHE STRING "Directory to install example config file into")
+if(NOT CUSTOM_EXAMPLE_CONF_DIR STREQUAL "")
+ set(EXAMPLE_CONF_DIR "${CUSTOM_EXAMPLE_CONF_DIR}")
+ message(STATUS "Using EXAMPLE_CONF_DIR=${EXAMPLE_CONF_DIR}")
+endif()
+
+set(CUSTOM_XDG_APPS_DIR "" CACHE STRING "Directory to install .desktop files into")
+if(NOT CUSTOM_XDG_APPS_DIR STREQUAL "")
+ set(XDG_APPS_DIR "${CUSTOM_XDG_APPS_DIR}")
+ message(STATUS "Using XDG_APPS_DIR=${XDG_APPS_DIR}")
+endif()
+
+set(CUSTOM_ICONDIR "" CACHE STRING "Directory to install icons into")
+if(NOT CUSTOM_ICONDIR STREQUAL "")
+ set(ICONDIR "${CUSTOM_ICONDIR}")
+ message(STATUS "Using ICONDIR=${ICONDIR}")
+endif()
+
+set(CUSTOM_LOCALEDIR "" CACHE STRING "Directory to install l10n files into")
+if(NOT CUSTOM_LOCALEDIR STREQUAL "")
+ set(LOCALEDIR "${CUSTOM_LOCALEDIR}")
+ message(STATUS "Using LOCALEDIR=${LOCALEDIR}")
+endif()
+
+
+install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/builtin" DESTINATION "${SHAREDIR}")
+install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/client" DESTINATION "${SHAREDIR}")
+install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/clientmods" DESTINATION "${SHAREDIR}")
+install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/games" DESTINATION "${SHAREDIR}" PATTERN ".git*" EXCLUDE)
+
+if(BUILD_CLIENT)
+ install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/textures/base/pack" DESTINATION "${SHAREDIR}/textures/base")
+endif()
+if(RUN_IN_PLACE)
+ install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/mods/mods_here.txt" DESTINATION "${SHAREDIR}/mods")
+ install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/textures/texture_packs_here.txt" DESTINATION "${SHAREDIR}/textures")
+endif()
+
+install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/fonts" DESTINATION "${SHAREDIR}")
+
+install(FILES "README.txt" DESTINATION "${DOCDIR}")
+install(FILES "doc/lua_api.txt" DESTINATION "${DOCDIR}")
+install(FILES "doc/menu_lua_api.txt" DESTINATION "${DOCDIR}")
+install(FILES "doc/texture_packs.txt" DESTINATION "${DOCDIR}")
+install(FILES "doc/world_format.txt" DESTINATION "${DOCDIR}")
+install(FILES "minetest.conf.example" DESTINATION "${EXAMPLE_CONF_DIR}")
+
+if(UNIX AND NOT APPLE)
+ install(FILES "doc/minetest.6" "doc/minetestserver.6" DESTINATION "${MANDIR}/man6")
+ install(FILES "misc/net.minetest.minetest.desktop" DESTINATION "${XDG_APPS_DIR}")
+ install(FILES "misc/net.minetest.minetest.appdata.xml" DESTINATION "${APPDATADIR}")
+ install(FILES "misc/minetest.svg" DESTINATION "${ICONDIR}/hicolor/scalable/apps")
+ install(FILES "misc/minetest-xorg-icon-128.png"
+ DESTINATION "${ICONDIR}/hicolor/128x128/apps"
+ RENAME "minetest.png")
+endif()
+
+if(APPLE)
+ install(FILES "misc/minetest-icon.icns" DESTINATION "${SHAREDIR}")
+ install(FILES "misc/Info.plist" DESTINATION "${BUNDLE_PATH}/Contents")
+endif()
+
+# Library pack
+find_package(GMP REQUIRED)
+find_package(Json REQUIRED)
+find_package(Lua REQUIRED)
-#
# Subdirectories
# Be sure to add all relevant definitions above this
-#
add_subdirectory(src)
+
# CPack
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "An InfiniMiner/Minecraft inspired game")
@@ -75,31 +213,17 @@ set(CPACK_PACKAGE_VENDOR "celeron55")
set(CPACK_PACKAGE_CONTACT "Perttu Ahola <celeron55@gmail.com>")
if(WIN32)
- # For some reason these aren't copied otherwise
- if(BUILD_CLIENT)
- install(FILES bin/minetest.exe DESTINATION bin)
- endif()
- if(BUILD_SERVER)
- install(FILES bin/minetestserver.exe DESTINATION bin)
+ if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+ set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-win64")
+ else()
+ set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-win32")
endif()
- set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-win32")
-
set(CPACK_GENERATOR ZIP)
-
- # This might be needed for some installer
- #set(CPACK_PACKAGE_EXECUTABLES bin/minetest.exe "Minetest" bin/minetestserver.exe "Minetest Server")
elseif(APPLE)
- # TODO
- # see http://cmake.org/Wiki/CMake:CPackPackageGenerators#Bundle_.28OSX_only.29
- #
+ set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-osx")
- set(CPACK_PACKAGE_ICON "")
- set(CPACK_BUNDLE_NAME ${PROJECT_NAME})
- set(CPACK_BUNDLE_ICON "")
- set(CPACK_BUNDLE_PLIST "")
- set(CPACK_BUNDLE_STARTUP_COMMAND "Contents/MacOS/minetest")
- set(CPACK_GENERATOR BUNDLE)
+ set(CPACK_GENERATOR ZIP)
else()
set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION_STRING}-linux")
set(CPACK_GENERATOR TGZ)
@@ -108,3 +232,16 @@ endif()
include(CPack)
+
+# Add a target to generate API documentation with Doxygen
+find_package(Doxygen)
+if(DOXYGEN_FOUND)
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/doc/Doxyfile.in
+ ${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile @ONLY)
+ add_custom_target(doc
+ ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc
+ COMMENT "Generating API documentation with Doxygen" VERBATIM
+ )
+endif()
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..468ba0514
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,106 @@
+# Contributing
+
+Contributions are welcome! Here's how you can help:
+
+- [Contributing code](#code)
+- [Reporting issues](#issues)
+- [Requesting features](#feature-requests)
+- [Translating](#translations)
+- [Donating](#donations)
+
+## Code
+
+If you are planning to start some significant coding, you would benefit from asking first on [our IRC channel](http://www.minetest.net/irc/) before starting.
+
+1. [Fork](https://help.github.com/articles/fork-a-repo/) the repository and [clone](https://help.github.com/articles/cloning-a-repository/) your fork.
+
+2. Start coding!
+ - Refer to the [Lua API](https://github.com/minetest/minetest/blob/master/doc/lua_api.txt), [Developer Wiki](http://dev.minetest.net/Main_Page) and other [documentation](https://github.com/minetest/minetest/tree/master/doc).
+ - Follow the [C/C++](http://dev.minetest.net/Code_style_guidelines) and [Lua](http://dev.minetest.net/Lua_code_style_guidelines) code style guidelines.
+ - Check your code works as expected and document any changes to the Lua API.
+
+3. Commit & [push](https://help.github.com/articles/pushing-to-a-remote/) your changes to a new branch (not `master`, one change per branch)
+ - Commit messages should:
+ - Use the present tense
+ - Have a title which begins with a capital letter
+ - Be descriptive. (e.g. no `Update init.lua` or `Fix a problem`)
+ - Have a first line with less than *80 characters* and have a second line that is *empty*
+
+4. Once you are happy with your changes, submit a pull request.
+ - Open the [pull-request form](https://github.com/minetest/minetest/pull/new/master)
+ - Add a short description explaining briefly what you've done (or if it's a work-in-progress - what you need to do)
+
+##### A pull-request is considered merge-able when:
+
+1. It follows the [roadmap](https://forum.minetest.net/viewtopic.php?t=9177) in some way and fits the whole picture of the project.
+2. It works.
+3. It follows the code style for [C/C++](http://dev.minetest.net/Code_style_guidelines) or [Lua](http://dev.minetest.net/Lua_code_style_guidelines).
+4. The code's interfaces are well designed, regardless of other aspects that might need more work in the future.
+5. It uses protocols and formats which include the required compatibility.
+
+## Issues
+
+If you experience an issue, we would like to know the details - especially when a stable release is on the way.
+
+1. Do a quick search on GitHub to check if the issue has already been reported.
+2. Is it an issue with the Minetest *engine*? If not, report it [elsewhere](http://www.minetest.net/development/#reporting-issues).
+3. [Open an issue](https://github.com/minetest/minetest/issues/new) and describe the issue you are having - you could include:
+ - Error logs (check the bottom of the `debug.txt` file)
+ - Screenshots
+ - Ways you have tried to solve the issue, and whether they worked or not
+ - Your Minetest version and the content (subgames, mods or texture packs) you have installed
+ - Your platform (e.g. Windows 10 or Ubuntu 15.04 x64)
+
+After reporting you should aim to answer questions or clarifications as this helps pinpoint the cause of the issue (if you don't do this your issue may be closed after 1 month).
+
+## Feature requests
+
+Feature requests are welcome but take a moment to see if your idea follows the [roadmap](https://forum.minetest.net/viewtopic.php?t=9177) in some way and fits the whole picture of the project. You should provide a clear explanation with as much detail as possible.
+
+## Translations
+
+Translations of Minetest are performed using Weblate. You can access the project page with a list of current languages [here](https://hosted.weblate.org/projects/minetest/minetest/).
+
+### Donations
+
+If you'd like to monetarily support Minetest development, you can find donation methods on [our website](http://www.minetest.net/development/#donate).
+
+# Maintaining
+
+*This is a concise version of the [Rules & Guidelines](http://dev.minetest.net/Category:Rules_and_Guidelines) on the developer wiki.*
+
+These notes are for those who have push access Minetest (core developers / maintainers).
+
+- See the [project organisation](http://dev.minetest.net/Organisation) for the people involved.
+
+## Reviewing pull requests
+
+Pull requests should be reviewed and, if appropriate, checked if they achieve their intended purpose. You can show that you are in the process of, or will review the pull request by commenting *"Looks good"* or something similar.
+
+**If the pull-request is not [merge-able](#a-pull-request-is-considered-merge-able-when):**
+
+Submit a comment explaining to the author what they need to change to make the pull-request merge-able.
+
+- If the author comments or makes changes to the pull-request, it can be reviewed again.
+- If no response is made from the author within 1 month (when improvements are suggested or a question is asked), it can be closed.
+
+**If the pull-request is [merge-able](#a-pull-request-is-considered-merge-able-when):**
+
+Submit a :+1: (+1) or "Looks good" comment to show you believe the pull-request should be merged. "Looks good" comments often signify that the patch might require (more) testing.
+
+- Two core developers must agree to the merge before it is carried out and both should +1 the pull request.
+- Who intends to merge the pull-request should follow the commit rules:
+ - The title should follow the commit guidelines (title starts with a capital letter, present tense, descriptive).
+ - Don't modify history older than 10 minutes.
+ - Use rebase, not merge to get linear history:
+ - `curl https://github.com/minetest/minetest/pull/1.patch | git am`
+
+## Reviewing issues and feature requests
+
+- If an issue does not get a response from its author within 1 month (when requiring more details), it can be closed.
+- When an issue is a duplicate, refer to the first ones and close the later ones.
+- Tag issues with the appropriate [labels](https://github.com/minetest/minetest/labels) for devices, platforms etc.
+
+## Releasing a new version
+
+*Refer to [dev.minetest.net/Releasing_Minetest](http://dev.minetest.net/Releasing_Minetest)*
diff --git a/Makefile.bak b/Makefile.bak
deleted file mode 100644
index 8ba03f9bb..000000000
--- a/Makefile.bak
+++ /dev/null
@@ -1,90 +0,0 @@
-# Makefile for Irrlicht Examples
-# It's usually sufficient to change just the target name and source file list
-# and be sure that CXX is set to a valid compiler
-SOURCE_FILES = porting.cpp guiMessageMenu.cpp materials.cpp guiTextInputMenu.cpp guiInventoryMenu.cpp irrlichtwrapper.cpp guiPauseMenu.cpp defaultsettings.cpp mapnode.cpp tile.cpp voxel.cpp mapblockobject.cpp inventory.cpp debug.cpp serialization.cpp light.cpp filesys.cpp connection.cpp environment.cpp client.cpp server.cpp socket.cpp mapblock.cpp mapsector.cpp heightmap.cpp map.cpp player.cpp utility.cpp main.cpp test.cpp
-
-DEBUG_TARGET = debugtest
-DEBUG_SOURCES = $(addprefix src/, $(SOURCE_FILES))
-DEBUG_BUILD_DIR = debugbuild
-DEBUG_OBJECTS = $(addprefix $(DEBUG_BUILD_DIR)/, $(SOURCE_FILES:.cpp=.o))
-
-FAST_TARGET = fasttest
-FAST_SOURCES = $(addprefix src/, $(SOURCE_FILES))
-FAST_BUILD_DIR = fastbuild
-FAST_OBJECTS = $(addprefix $(FAST_BUILD_DIR)/, $(SOURCE_FILES:.cpp=.o))
-
-SERVER_TARGET = server
-SERVER_SOURCE_FILES = porting.cpp materials.cpp defaultsettings.cpp mapnode.cpp voxel.cpp mapblockobject.cpp inventory.cpp debug.cpp serialization.cpp light.cpp filesys.cpp connection.cpp environment.cpp server.cpp socket.cpp mapblock.cpp mapsector.cpp heightmap.cpp map.cpp player.cpp utility.cpp servermain.cpp test.cpp
-SERVER_SOURCES = $(addprefix src/, $(SERVER_SOURCE_FILES))
-SERVER_BUILD_DIR = serverbuild
-SERVER_OBJECTS = $(addprefix $(SERVER_BUILD_DIR)/, $(SERVER_SOURCE_FILES:.cpp=.o))
-
-IRRLICHTPATH = ../irrlicht/irrlicht-1.7.1
-JTHREADPATH = ../jthread/jthread-1.2.1
-
-
-all: fast
-
-ifeq ($(HOSTTYPE), x86_64)
-LIBSELECT=64
-endif
-
-debug: CXXFLAGS = -Wall -g -O1
-debug: CPPFLAGS = -I$(IRRLICHTPATH)/include -I/usr/X11R6/include -I$(JTHREADPATH)/src -DDEBUG -DRUN_IN_PLACE
-debug: LDFLAGS = -L/usr/X11R6/lib$(LIBSELECT) -L$(IRRLICHTPATH)/lib/Linux -L$(JTHREADPATH)/src/.libs -lIrrlicht -lGL -lXxf86vm -lXext -lX11 -ljthread -lz
-
-fast: CXXFLAGS = -O3 -ffast-math -Wall -fomit-frame-pointer -pipe -funroll-loops -mtune=i686
-fast: CPPFLAGS = -I$(IRRLICHTPATH)/include -I/usr/X11R6/include -I$(JTHREADPATH)/src -DUNITTEST_DISABLE -DRUN_IN_PLACE
-fast: LDFLAGS = -L/usr/X11R6/lib$(LIBSELECT) -L$(IRRLICHTPATH)/lib/Linux -L$(JTHREADPATH)/src/.libs -lIrrlicht -lGL -lXxf86vm -lXext -lX11 -ljthread -lz
-
-server: CXXFLAGS = -O3 -ffast-math -Wall -fomit-frame-pointer -pipe -funroll-loops -mtune=i686
-server: CPPFLAGS = -I$(IRRLICHTPATH)/include -I/usr/X11R6/include -I$(JTHREADPATH)/src -DSERVER -DUNITTEST_DISABLE -DRUN_IN_PLACE
-server: LDFLAGS = -L$(JTHREADPATH)/src/.libs -ljthread -lz -lpthread
-
-DEBUG_DESTPATH = bin/$(DEBUG_TARGET)
-FAST_DESTPATH = bin/$(FAST_TARGET)
-SERVER_DESTPATH = bin/$(SERVER_TARGET)
-
-# Build commands
-
-debug: $(DEBUG_BUILD_DIR) $(DEBUG_DESTPATH)
-fast: $(FAST_BUILD_DIR) $(FAST_DESTPATH)
-server: $(SERVER_BUILD_DIR) $(SERVER_DESTPATH)
-
-$(DEBUG_BUILD_DIR):
- mkdir -p $(DEBUG_BUILD_DIR)
-$(FAST_BUILD_DIR):
- mkdir -p $(FAST_BUILD_DIR)
-$(SERVER_BUILD_DIR):
- mkdir -p $(SERVER_BUILD_DIR)
-
-$(DEBUG_DESTPATH): $(DEBUG_OBJECTS)
- $(CXX) -o $@ $(DEBUG_OBJECTS) $(LDFLAGS)
-
-$(FAST_DESTPATH): $(FAST_OBJECTS)
- $(CXX) -o $@ $(FAST_OBJECTS) $(LDFLAGS)
-
-$(SERVER_DESTPATH): $(SERVER_OBJECTS)
- $(CXX) -o $@ $(SERVER_OBJECTS) $(LDFLAGS)
-
-$(DEBUG_BUILD_DIR)/%.o: src/%.cpp
- $(CXX) -c -o $@ $< $(CPPFLAGS) $(CXXFLAGS)
-
-$(FAST_BUILD_DIR)/%.o: src/%.cpp
- $(CXX) -c -o $@ $< $(CPPFLAGS) $(CXXFLAGS)
-
-$(SERVER_BUILD_DIR)/%.o: src/%.cpp
- $(CXX) -c -o $@ $< $(CPPFLAGS) $(CXXFLAGS)
-
-clean: clean_debug clean_fast clean_server
-
-clean_debug:
- @$(RM) $(DEBUG_OBJECTS) $(DEBUG_DESTPATH)
-
-clean_fast:
- @$(RM) $(FAST_OBJECTS) $(FAST_DESTPATH)
-
-clean_server:
- @$(RM) $(SERVER_OBJECTS) $(SERVER_DESTPATH)
-
-.PHONY: all all_win32 clean clean_debug clean_win32 clean_fast clean_server
diff --git a/README.txt b/README.txt
new file mode 100644
index 000000000..a627bde8d
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,557 @@
+Minetest
+========
+
+An InfiniMiner/Minecraft inspired game.
+
+Copyright (c) 2010-2017 Perttu Ahola <celeron55@gmail.com>
+and contributors (see source file comments and the version control log)
+
+In case you downloaded the source code:
+---------------------------------------
+If you downloaded the Minetest Engine source code in which this file is
+contained, you probably want to download the minetest_game project too:
+ https://github.com/minetest/minetest_game/
+See the README.txt in it.
+
+Further documentation
+----------------------
+- Website: http://minetest.net/
+- Wiki: http://wiki.minetest.net/
+- Developer wiki: http://dev.minetest.net/
+- Forum: http://forum.minetest.net/
+- Github: https://github.com/minetest/minetest/
+- doc/ directory of source distribution
+
+This game is not finished
+--------------------------
+- Don't expect it to work as well as a finished game will.
+- Please report any bugs. When doing that, debug.txt is useful.
+
+Default controls
+-----------------
+- Move mouse: Look around
+- W, A, S, D: Move
+- Space: Jump/move up
+- Shift: Sneak/move down
+- Q: Drop itemstack
+- Shift + Q: Drop single item
+- Left mouse button: Dig/punch/take item
+- Right mouse button: Place/use
+- Shift + right mouse button: Build (without using)
+- I: Inventory menu
+- Mouse wheel: Select item
+- 0-9: Select item
+- Z: Zoom (needs zoom privilege)
+- T: Chat
+- /: Command
+
+- Esc: Pause menu/abort/exit (pauses only singleplayer game)
+- R: Enable/disable full range view
+- +: Increase view range
+- -: Decrease view range
+- K: Enable/disable fly mode (needs fly privilege)
+- J: Enable/disable fast mode (needs fast privilege)
+- H: Enable/disable noclip mode (needs noclip privilege)
+
+- F1: Hide/show HUD
+- F2: Hide/show chat
+- F3: Disable/enable fog
+- F4: Disable/enable camera update (Mapblocks are not updated anymore when disabled, disabled in release builds)
+- F5: Cycle through debug info screens
+- F6: Cycle through profiler info screens
+- F7: Cycle through camera modes
+- F8: Toggle cinematic mode
+- F9: Cycle through minimap modes
+- Shift + F9: Change minimap orientation
+- F10: Show/hide console
+- F12: Take screenshot
+- P: Write stack traces into debug.txt
+
+Most controls are settable in the configuration file, see the section below.
+
+Paths
+------
+$bin - Compiled binaries
+$share - Distributed read-only data
+$user - User-created modifiable data
+
+Windows .zip / RUN_IN_PLACE source:
+$bin = bin
+$share = .
+$user = .
+
+Linux installed:
+$bin = /usr/bin
+$share = /usr/share/minetest
+$user = ~/.minetest
+
+macOS:
+$bin = Contents/MacOS
+$share = Contents/Resources
+$user = Contents/User OR ~/Library/Application Support/minetest
+
+World directory
+----------------
+- Worlds can be found as separate folders in:
+ $user/worlds/
+
+Configuration file:
+-------------------
+- Default location:
+ $user/minetest.conf
+- It is created by Minetest when it is ran the first time.
+- A specific file can be specified on the command line:
+ --config <path-to-file>
+- A run-in-place build will look for the configuration file in
+ $location_of_exe/../minetest.conf and also $location_of_exe/../../minetest.conf
+
+Command-line options:
+---------------------
+- Use --help
+
+Compiling on GNU/Linux:
+-----------------------
+
+Install dependencies. Here's an example for Debian/Ubuntu:
+$ sudo apt-get install build-essential libirrlicht-dev cmake libbz2-dev libpng-dev libjpeg-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev
+
+For Fedora users:
+$ sudo dnf install make automake gcc gcc-c++ kernel-devel cmake libcurl* openal* libvorbis* libXxf86vm-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel irrlicht-devel bzip2-libs gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel doxygen spatialindex-devel bzip2-devel
+
+You can install git for easily keeping your copy up to date.
+If you don’t want git, read below on how to get the source without git.
+This is an example for installing git on Debian/Ubuntu:
+$ sudo apt-get install git
+
+For Fedora users:
+$ sudo dnf install git
+
+Download source (this is the URL to the latest of source repository, which might not work at all times) using git:
+$ git clone --depth 1 https://github.com/minetest/minetest.git
+$ cd minetest
+
+Download minetest_game (otherwise only the "Minimal development test" game is available) using git:
+$ git clone --depth 1 https://github.com/minetest/minetest_game.git games/minetest_game
+
+Download source, without using git:
+$ wget https://github.com/minetest/minetest/archive/master.tar.gz
+$ tar xf master.tar.gz
+$ cd minetest-master
+
+Download minetest_game, without using git:
+$ cd games/
+$ wget https://github.com/minetest/minetest_game/archive/master.tar.gz
+$ tar xf master.tar.gz
+$ mv minetest_game-master minetest_game
+$ cd ..
+
+Build a version that runs directly from the source directory:
+$ cmake . -DRUN_IN_PLACE=TRUE
+$ make -j <number of processors>
+
+Run it:
+$ ./bin/minetest
+
+- Use cmake . -LH to see all CMake options and their current state
+- If you want to install it system-wide (or are making a distribution package),
+ you will want to use -DRUN_IN_PLACE=FALSE
+- You can build a bare server by specifying -DBUILD_SERVER=TRUE
+- You can disable the client build by specifying -DBUILD_CLIENT=FALSE
+- You can select between Release and Debug build by -DCMAKE_BUILD_TYPE=<Debug or Release>
+ - Debug build is slower, but gives much more useful output in a debugger
+- If you build a bare server, you don't need to have Irrlicht installed.
+ In that case use -DIRRLICHT_SOURCE_DIR=/the/irrlicht/source
+
+CMake options
+-------------
+General options:
+
+BUILD_CLIENT - Build Minetest client
+BUILD_SERVER - Build Minetest server
+CMAKE_BUILD_TYPE - Type of build (Release vs. Debug)
+ Release - Release build
+ Debug - Debug build
+ SemiDebug - Partially optimized debug build
+ RelWithDebInfo - Release build with Debug information
+ MinSizeRel - Release build with -Os passed to compiler to make executable as small as possible
+ENABLE_CURL - Build with cURL; Enables use of online mod repo, public serverlist and remote media fetching via http
+ENABLE_CURSES - Build with (n)curses; Enables a server side terminal (command line option: --terminal)
+ENABLE_FREETYPE - Build with FreeType2; Allows using TTF fonts
+ENABLE_GETTEXT - Build with Gettext; Allows using translations
+ENABLE_GLES - Search for Open GLES headers & libraries and use them
+ENABLE_LEVELDB - Build with LevelDB; Enables use of LevelDB map backend
+ENABLE_POSTGRESQL - Build with libpq; Enables use of PostgreSQL map backend (PostgreSQL 9.5 or greater recommended)
+ENABLE_REDIS - Build with libhiredis; Enables use of Redis map backend
+ENABLE_SPATIAL - Build with LibSpatial; Speeds up AreaStores
+ENABLE_SOUND - Build with OpenAL, libogg & libvorbis; in-game Sounds
+ENABLE_LUAJIT - Build with LuaJIT (much faster than non-JIT Lua)
+ENABLE_SYSTEM_GMP - Use GMP from system (much faster than bundled mini-gmp)
+RUN_IN_PLACE - Create a portable install (worlds, settings etc. in current directory)
+USE_GPROF - Enable profiling using GProf
+VERSION_EXTRA - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar)
+
+Library specific options:
+
+BZIP2_INCLUDE_DIR - Linux only; directory where bzlib.h is located
+BZIP2_LIBRARY - Linux only; path to libbz2.a/libbz2.so
+CURL_DLL - Only if building with cURL on Windows; path to libcurl.dll
+CURL_INCLUDE_DIR - Only if building with cURL; directory where curl.h is located
+CURL_LIBRARY - Only if building with cURL; path to libcurl.a/libcurl.so/libcurl.lib
+EGL_INCLUDE_DIR - Only if building with GLES; directory that contains egl.h
+EGL_LIBRARY - Only if building with GLES; path to libEGL.a/libEGL.so
+FREETYPE_INCLUDE_DIR_freetype2 - Only if building with Freetype2; directory that contains an freetype directory with files such as ftimage.h in it
+FREETYPE_INCLUDE_DIR_ft2build - Only if building with Freetype2; directory that contains ft2build.h
+FREETYPE_LIBRARY - Only if building with Freetype2; path to libfreetype.a/libfreetype.so/freetype.lib
+FREETYPE_DLL - Only if building with Freetype2 on Windows; path to libfreetype.dll
+GETTEXT_DLL - Only when building with Gettext on Windows; path to libintl3.dll
+GETTEXT_ICONV_DLL - Only when building with Gettext on Windows; path to libiconv2.dll
+GETTEXT_INCLUDE_DIR - Only when building with Gettext; directory that contains iconv.h
+GETTEXT_LIBRARY - Only when building with Gettext on Windows; path to libintl.dll.a
+GETTEXT_MSGFMT - Only when building with Gettext; path to msgfmt/msgfmt.exe
+IRRLICHT_DLL - Only on Windows; path to Irrlicht.dll
+IRRLICHT_INCLUDE_DIR - Directory that contains IrrCompileConfig.h
+IRRLICHT_LIBRARY - Path to libIrrlicht.a/libIrrlicht.so/libIrrlicht.dll.a/Irrlicht.lib
+LEVELDB_INCLUDE_DIR - Only when building with LevelDB; directory that contains db.h
+LEVELDB_LIBRARY - Only when building with LevelDB; path to libleveldb.a/libleveldb.so/libleveldb.dll.a
+LEVELDB_DLL - Only when building with LevelDB on Windows; path to libleveldb.dll
+PostgreSQL_INCLUDE_DIR - Only when building with PostgreSQL; directory that contains libpq-fe.h
+POSTGRESQL_LIBRARY - Only when building with PostgreSQL; path to libpq.a/libpq.so
+REDIS_INCLUDE_DIR - Only when building with Redis; directory that contains hiredis.h
+REDIS_LIBRARY - Only when building with Redis; path to libhiredis.a/libhiredis.so
+SPATIAL_INCLUDE_DIR - Only when building with LibSpatial; directory that contains spatialindex/SpatialIndex.h
+SPATIAL_LIBRARY - Only when building with LibSpatial; path to libspatialindex_c.so/spatialindex-32.lib
+LUA_INCLUDE_DIR - Only if you want to use LuaJIT; directory where luajit.h is located
+LUA_LIBRARY - Only if you want to use LuaJIT; path to libluajit.a/libluajit.so
+MINGWM10_DLL - Only if compiling with MinGW; path to mingwm10.dll
+OGG_DLL - Only if building with sound on Windows; path to libogg.dll
+OGG_INCLUDE_DIR - Only if building with sound; directory that contains an ogg directory which contains ogg.h
+OGG_LIBRARY - Only if building with sound; path to libogg.a/libogg.so/libogg.dll.a
+OPENAL_DLL - Only if building with sound on Windows; path to OpenAL32.dll
+OPENAL_INCLUDE_DIR - Only if building with sound; directory where al.h is located
+OPENAL_LIBRARY - Only if building with sound; path to libopenal.a/libopenal.so/OpenAL32.lib
+OPENGLES2_INCLUDE_DIR - Only if building with GLES; directory that contains gl2.h
+OPENGLES2_LIBRARY - Only if building with GLES; path to libGLESv2.a/libGLESv2.so
+SQLITE3_INCLUDE_DIR - Directory that contains sqlite3.h
+SQLITE3_LIBRARY - Path to libsqlite3.a/libsqlite3.so/sqlite3.lib
+VORBISFILE_DLL - Only if building with sound on Windows; path to libvorbisfile-3.dll
+VORBISFILE_LIBRARY - Only if building with sound; path to libvorbisfile.a/libvorbisfile.so/libvorbisfile.dll.a
+VORBIS_DLL - Only if building with sound on Windows; path to libvorbis-0.dll
+VORBIS_INCLUDE_DIR - Only if building with sound; directory that contains a directory vorbis with vorbisenc.h inside
+VORBIS_LIBRARY - Only if building with sound; path to libvorbis.a/libvorbis.so/libvorbis.dll.a
+XXF86VM_LIBRARY - Only on Linux; path to libXXf86vm.a/libXXf86vm.so
+ZLIB_DLL - Only on Windows; path to zlib1.dll
+ZLIBWAPI_DLL - Only on Windows; path to zlibwapi.dll
+ZLIB_INCLUDE_DIR - Directory that contains zlib.h
+ZLIB_LIBRARY - Path to libz.a/libz.so/zlibwapi.lib
+
+Compiling on Windows:
+---------------------
+- This section is outdated. In addition to what is described here:
+ - In addition to minetest, you need to download minetest_game.
+ - If you wish to have sound support, you need libogg, libvorbis and libopenal
+
+- You need:
+ * CMake:
+ http://www.cmake.org/cmake/resources/software.html
+ * MinGW or Visual Studio
+ http://www.mingw.org/
+ http://msdn.microsoft.com/en-us/vstudio/default
+ * Irrlicht SDK 1.7:
+ http://irrlicht.sourceforge.net/downloads.html
+ * Zlib headers (zlib125.zip)
+ http://www.winimage.com/zLibDll/index.html
+ * Zlib library (zlibwapi.lib and zlibwapi.dll from zlib125dll.zip):
+ http://www.winimage.com/zLibDll/index.html
+ * SQLite3 headers and library
+ https://www.sqlite.org/download.html
+ * Optional: gettext library and tools:
+ http://gnuwin32.sourceforge.net/downlinks/gettext.php
+ - This is used for other UI languages. Feel free to leave it out.
+ * And, of course, Minetest:
+ http://minetest.net/download
+- Steps:
+ - Select a directory called DIR hereafter in which you will operate.
+ - Make sure you have CMake and a compiler installed.
+ - Download all the other stuff to DIR and extract them into there.
+ ("extract here", not "extract to packagename/")
+ NOTE: zlib125dll.zip needs to be extracted into zlib125dll
+ NOTE: You need to extract sqlite3.h & sqlite3ext.h from sqlite3 source
+ and sqlite3.dll & sqlite3.def from sqlite3 precompiled binaries
+ into "sqlite3" directory, and generate sqlite3.lib using command
+ "LIB /DEF:sqlite3.def /OUT:sqlite3.lib"
+ - All those packages contain a nice base directory in them, which
+ should end up being the direct subdirectories of DIR.
+ - You will end up with a directory structure like this (+=dir, -=file):
+ -----------------
+ + DIR
+ - zlib-1.2.5.tar.gz
+ - zlib125dll.zip
+ - irrlicht-1.8.3.zip
+ - sqlite-amalgamation-3130000.zip (SQLite3 headers)
+ - sqlite-dll-win32-x86-3130000.zip (SQLite3 library for 32bit system)
+ - 110214175330.zip (or whatever, this is the minetest source)
+ + zlib-1.2.5
+ - zlib.h
+ + win32
+ ...
+ + zlib125dll
+ - readme.txt
+ + dll32
+ ...
+ + irrlicht-1.8.3
+ + lib
+ + include
+ ...
+ + sqlite3
+ sqlite3.h
+ sqlite3ext.h
+ sqlite3.lib
+ sqlite3.dll
+ + gettext (optional)
+ +bin
+ +include
+ +lib
+ + minetest
+ + src
+ + doc
+ - CMakeLists.txt
+ ...
+ -----------------
+ - Start up the CMake GUI
+ - Select "Browse Source..." and select DIR/minetest
+ - Now, if using MSVC:
+ - Select "Browse Build..." and select DIR/minetest-build
+ - Else if using MinGW:
+ - Select "Browse Build..." and select DIR/minetest
+ - Select "Configure"
+ - Select your compiler
+ - It will warn about missing stuff, ignore that at this point. (later don't)
+ - Make sure the configuration is as follows
+ (note that the versions may differ for you):
+ -----------------
+ BUILD_CLIENT [X]
+ BUILD_SERVER [ ]
+ CMAKE_BUILD_TYPE Release
+ CMAKE_INSTALL_PREFIX DIR/minetest-install
+ IRRLICHT_SOURCE_DIR DIR/irrlicht-1.8.3
+ RUN_IN_PLACE [X]
+ WARN_ALL [ ]
+ ZLIB_DLL DIR/zlib125dll/dll32/zlibwapi.dll
+ ZLIB_INCLUDE_DIR DIR/zlib-1.2.5
+ ZLIB_LIBRARIES DIR/zlib125dll/dll32/zlibwapi.lib
+ GETTEXT_BIN_DIR DIR/gettext/bin
+ GETTEXT_INCLUDE_DIR DIR/gettext/include
+ GETTEXT_LIBRARIES DIR/gettext/lib/intl.lib
+ GETTEXT_MSGFMT DIR/gettext/bin/msgfmt
+ -----------------
+ - If CMake complains it couldn't find SQLITE3, choose "Advanced" box on the
+ right top corner, then specify the location of SQLITE3_INCLUDE_DIR and
+ SQLITE3_LIBRARY manually.
+ - If you want to build 64-bit minetest, you will need to build 64-bit version
+ of irrlicht engine manually, as only 32-bit pre-built library is provided.
+ - Hit "Configure"
+ - Hit "Configure" once again 8)
+ - If something is still coloured red, you have a problem.
+ - Hit "Generate"
+ If using MSVC:
+ - Open the generated minetest.sln
+ - The project defaults to the "Debug" configuration. Make very sure to
+ select "Release", unless you want to debug some stuff (it's slower
+ and might not even work at all)
+ - Build the ALL_BUILD project
+ - Build the INSTALL project
+ - You should now have a working game with the executable in
+ DIR/minetest-install/bin/minetest.exe
+ - Additionally you may create a zip package by building the PACKAGE
+ project.
+ If using MinGW:
+ - Using the command line, browse to the build directory and run 'make'
+ (or mingw32-make or whatever it happens to be)
+ - You may need to copy some of the downloaded DLLs into bin/, see what
+ running the produced executable tells you it doesn't have.
+ - You should now have a working game with the executable in
+ DIR/minetest/bin/minetest.exe
+
+Windows releases of minetest are built using a bat script like this:
+--------------------------------------------------------------------
+
+set sourcedir=%CD%
+set installpath="C:\tmp\minetest_install"
+set irrlichtpath="C:\tmp\irrlicht-1.7.2"
+
+set builddir=%sourcedir%\bvc10
+mkdir %builddir%
+pushd %builddir%
+cmake %sourcedir% -G "Visual Studio 10" -DIRRLICHT_SOURCE_DIR=%irrlichtpath% -DRUN_IN_PLACE=TRUE -DCMAKE_INSTALL_PREFIX=%installpath%
+if %errorlevel% neq 0 goto fail
+"C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" ALL_BUILD.vcxproj /p:Configuration=Release
+if %errorlevel% neq 0 goto fail
+"C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" INSTALL.vcxproj /p:Configuration=Release
+if %errorlevel% neq 0 goto fail
+"C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" PACKAGE.vcxproj /p:Configuration=Release
+if %errorlevel% neq 0 goto fail
+popd
+echo Finished.
+exit /b 0
+
+:fail
+popd
+echo Failed.
+exit /b 1
+
+License of Minetest textures and sounds
+---------------------------------------
+
+This applies to textures and sounds contained in the main Minetest
+distribution.
+
+Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)
+http://creativecommons.org/licenses/by-sa/3.0/
+
+Authors of media files
+-----------------------
+Everything not listed in here:
+Copyright (C) 2010-2012 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+ShadowNinja:
+ textures/base/pack/smoke_puff.png
+
+Paramat:
+ textures/base/pack/menu_header.png
+
+erlehmann:
+ misc/minetest-icon-24x24.png
+ misc/minetest-icon.ico
+ misc/minetest.svg
+ textures/base/pack/logo.png
+
+License of Minetest source code
+-------------------------------
+
+Minetest
+Copyright (C) 2010-2017 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Irrlicht
+---------------
+
+This program uses the Irrlicht Engine. http://irrlicht.sourceforge.net/
+
+ The Irrlicht Engine License
+
+Copyright © 2002-2005 Nikolaus Gebhardt
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute
+it freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you
+ must not claim that you wrote the original software. If you use
+ this software in a product, an acknowledgment in the product
+ documentation would be appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must
+ not be misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source
+ distribution.
+
+
+JThread
+---------------
+
+This program uses the JThread library. License for JThread follows:
+
+Copyright (c) 2000-2006 Jori Liesenborgs (jori.liesenborgs@gmail.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
+
+Lua
+---------------
+
+Lua is licensed under the terms of the MIT license reproduced below.
+This means that Lua is free software and can be used for both academic
+and commercial purposes at absolutely no cost.
+
+For details and rationale, see https://www.lua.org/license.html .
+
+Copyright (C) 1994-2008 Lua.org, PUC-Rio.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Fonts
+---------------
+
+Bitstream Vera Fonts Copyright:
+
+ Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
+ a trademark of Bitstream, Inc.
+
+Arimo - Apache License, version 2.0
+ Digitized data copyright (c) 2010-2012 Google Corporation.
+
+Cousine - Apache License, version 2.0
+ Digitized data copyright (c) 2010-2012 Google Corporation.
+
+DroidSansFallBackFull:
+
+ Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/addlicensecomments.sh b/addlicensecomments.sh
deleted file mode 100644
index e5642e82d..000000000
--- a/addlicensecomments.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/sh
-for i in `grep -L 'This program is free software' src/*.{h,cpp}`
-do
- cat licensecomment.txt > tempfile
- cat $i >> tempfile
- cp tempfile $i
-done
-rm tempfile
-
diff --git a/build/android/Makefile b/build/android/Makefile
new file mode 100644
index 000000000..5fe0321b7
--- /dev/null
+++ b/build/android/Makefile
@@ -0,0 +1,836 @@
+# build options
+
+OS := $(shell uname)
+
+# compile with GPROF
+# GPROF = 1
+
+# build for build platform
+API = 14
+APP_PLATFORM = android-$(API)
+
+ANDR_ROOT = $(shell pwd)
+PROJ_ROOT = $(shell realpath $(ANDR_ROOT)/../..)
+APP_ROOT = $(ANDR_ROOT)/src/main
+
+GAMES_TO_COPY = minetest_game
+MODS_TO_COPY =
+
+
+VERSION_MAJOR := $(shell cat $(PROJ_ROOT)/CMakeLists.txt | \
+ grep ^set\(VERSION_MAJOR\ | sed 's/)/ /' | cut -f2 -d' ')
+VERSION_MINOR := $(shell cat $(PROJ_ROOT)/CMakeLists.txt | \
+ grep ^set\(VERSION_MINOR\ | sed 's/)/ /' | cut -f2 -d' ')
+VERSION_PATCH := $(shell cat $(PROJ_ROOT)/CMakeLists.txt | \
+ grep ^set\(VERSION_PATCH\ | sed 's/)/ /' | cut -f2 -d' ')
+
+################################################################################
+# toolchain config for arm new processors
+################################################################################
+TARGET_HOST = arm-linux
+TARGET_ABI = armeabi-v7a
+TARGET_LIBDIR = armeabi-v7a
+TARGET_TOOLCHAIN = arm-linux-androideabi-
+TARGET_CFLAGS_ADDON = -mfloat-abi=softfp -mfpu=vfpv3 -O3 -D__ANDROID_API__=$(API)
+TARGET_CXXFLAGS_ADDON = $(TARGET_CFLAGS_ADDON)
+TARGET_ARCH = armv7
+CROSS_PREFIX = arm-linux-androideabi-
+COMPILER_VERSION = 4.9
+HAVE_LEVELDB = 0
+
+################################################################################
+# toolchain config for little endian mips
+################################################################################
+#TARGET_HOST = mipsel-linux
+#TARGET_ABI = mips
+#TARGET_LIBDIR = mips
+#TARGET_TOOLCHAIN = mipsel-linux-android-
+#TARGET_ARCH = mips32
+#CROSS_PREFIX = mipsel-linux-android-
+#COMPILER_VERSION = 4.9
+#HAVE_LEVELDB = 0
+
+################################################################################
+# toolchain config for x86
+################################################################################
+#TARGET_HOST = x86-linux
+#TARGET_ABI = x86
+#TARGET_LIBDIR = x86
+#TARGET_TOOLCHAIN = x86-
+#CROSS_PREFIX = i686-linux-android-
+#TARGET_ARCH = x86
+#COMPILER_VERSION = 4.9
+#HAVE_LEVELDB = 0
+
+################################################################################
+ASSETS_TIMESTAMP = deps/assets_timestamp
+
+LEVELDB_DIR = $(ANDR_ROOT)/deps/leveldb/
+LEVELDB_LIB = $(LEVELDB_DIR)libleveldb.a
+LEVELDB_TIMESTAMP = $(LEVELDB_DIR)/timestamp
+LEVELDB_TIMESTAMP_INT = $(ANDR_ROOT)/deps/leveldb_timestamp
+LEVELDB_URL_GIT = https://github.com/google/leveldb
+LEVELDB_COMMIT = 2d0320a458d0e6a20fff46d5f80b18bfdcce7018
+
+OPENAL_DIR = $(ANDR_ROOT)/deps/openal-soft/
+OPENAL_LIB = $(OPENAL_DIR)libs/$(TARGET_ABI)/libopenal.so
+OPENAL_TIMESTAMP = $(OPENAL_DIR)/timestamp
+OPENAL_TIMESTAMP_INT = $(ANDR_ROOT)/deps/openal_timestamp
+OPENAL_URL_GIT = https://github.com/apportable/openal-soft
+
+OGG_DIR = $(ANDR_ROOT)/deps/libvorbis-libogg-android/
+OGG_LIB = $(OGG_DIR)libs/$(TARGET_ABI)/libogg.so
+VORBIS_LIB = $(OGG_DIR)libs/$(TARGET_ABI)/libogg.so
+OGG_TIMESTAMP = $(OGG_DIR)timestamp
+OGG_TIMESTAMP_INT = $(ANDR_ROOT)/deps/ogg_timestamp
+OGG_URL_GIT = https://gitlab.com/minetest/libvorbis-libogg-android
+
+IRRLICHT_REVISION = 5145
+IRRLICHT_DIR = $(ANDR_ROOT)/deps/irrlicht/
+IRRLICHT_LIB = $(IRRLICHT_DIR)lib/Android/libIrrlicht.a
+IRRLICHT_TIMESTAMP = $(IRRLICHT_DIR)timestamp
+IRRLICHT_TIMESTAMP_INT = $(ANDR_ROOT)/deps/irrlicht_timestamp
+IRRLICHT_URL_SVN = https://svn.code.sf.net/p/irrlicht/code/branches/ogl-es@$(IRRLICHT_REVISION)
+
+OPENSSL_VERSION = 1.0.2k
+OPENSSL_BASEDIR = openssl-$(OPENSSL_VERSION)
+OPENSSL_DIR = $(ANDR_ROOT)/deps/$(OPENSSL_BASEDIR)/
+OPENSSL_LIB = $(OPENSSL_DIR)/libssl.so.1.0.0
+OPENSSL_TIMESTAMP = $(OPENSSL_DIR)timestamp
+OPENSSL_TIMESTAMP_INT = $(ANDR_ROOT)/deps/openssl_timestamp
+OPENSSL_URL = https://www.openssl.org/source/openssl-$(OPENSSL_VERSION).tar.gz
+
+CURL_VERSION = 7.54.0
+CURL_DIR = $(ANDR_ROOT)/deps/curl-$(CURL_VERSION)
+CURL_LIB = $(CURL_DIR)/lib/.libs/libcurl.a
+CURL_TIMESTAMP = $(CURL_DIR)/timestamp
+CURL_TIMESTAMP_INT = $(ANDR_ROOT)/deps/curl_timestamp
+CURL_URL_HTTP = https://curl.haxx.se/download/curl-${CURL_VERSION}.tar.bz2
+
+GMP_VERSION = 6.1.2
+GMP_DIR = $(ANDR_ROOT)/deps/gmp-$(GMP_VERSION)
+GMP_LIB = $(GMP_DIR)/usr/lib/libgmp.so
+GMP_TIMESTAMP = $(GMP_DIR)/timestamp
+GMP_TIMESTAMP_INT = $(ANDR_ROOT)/deps/gmp_timestamp
+GMP_URL_HTTP = https://gmplib.org/download/gmp/gmp-$(GMP_VERSION).tar.bz2
+
+FREETYPE_DIR = $(ANDR_ROOT)/deps/freetype2-android/
+FREETYPE_LIB = $(FREETYPE_DIR)/Android/obj/local/$(TARGET_ABI)/libfreetype2-static.a
+FREETYPE_TIMESTAMP = $(FREETYPE_DIR)timestamp
+FREETYPE_TIMESTAMP_INT = $(ANDR_ROOT)/deps/freetype_timestamp
+FREETYPE_URL_GIT = https://github.com/cdave1/freetype2-android
+
+ICONV_VERSION = 1.14
+ICONV_DIR = $(ANDR_ROOT)/deps/libiconv/
+ICONV_LIB = $(ICONV_DIR)/lib/.libs/libiconv.so
+ICONV_TIMESTAMP = $(ICONV_DIR)timestamp
+ICONV_TIMESTAMP_INT = $(ANDR_ROOT)/deps/iconv_timestamp
+ICONV_URL_HTTP = https://ftp.gnu.org/pub/gnu/libiconv/libiconv-$(ICONV_VERSION).tar.gz
+
+SQLITE3_FOLDER = sqlite-amalgamation-3180000
+SQLITE3_URL = https://www.sqlite.org/2017/$(SQLITE3_FOLDER).zip
+
+ANDROID_SDK = $(shell grep '^sdk\.dir' local.properties | sed 's/^.*=[[:space:]]*//')
+ANDROID_NDK = $(shell grep '^ndk\.dir' local.properties | sed 's/^.*=[[:space:]]*//')
+
+#use interim target variable to switch leveldb on or off
+ifeq ($(HAVE_LEVELDB),1)
+ LEVELDB_TARGET = $(LEVELDB_LIB)
+endif
+
+.PHONY : debug release reconfig delconfig \
+ leveldb_download clean_leveldb leveldb\
+ irrlicht_download clean_irrlicht irrlicht \
+ clean_assets assets sqlite3_download \
+ freetype_download clean_freetype freetype \
+ apk clean_apk \
+ clean_all clean prep_srcdir \
+ install_debug install_release envpaths all \
+ $(ASSETS_TIMESTAMP) $(LEVELDB_TIMESTAMP) \
+ $(OPENAL_TIMESTAMP) $(OGG_TIMESTAMP) \
+ $(IRRLICHT_TIMESTAMP) $(CURL_TIMESTAMP) \
+ $(OPENSSL_TIMESTAMP) \
+ $(ANDR_ROOT)/jni/src/android_version.h \
+ $(ANDR_ROOT)/jni/src/android_version_githash.h
+
+debug : local.properties
+ export NDEBUG=; \
+ export BUILD_TYPE=debug; \
+ $(MAKE) apk
+
+all : debug release
+
+release : local.properties
+ @export NDEBUG=1; \
+ export BUILD_TYPE=release; \
+ $(MAKE) apk
+
+reconfig: delconfig
+ @$(MAKE) local.properties
+
+delconfig:
+ $(RM) local.properties
+
+local.properties:
+ @echo "Please specify path of ANDROID NDK"; \
+ echo "e.g. $$HOME/Android/ndk-r11c/"; \
+ read ANDROID_NDK ; \
+ if [ ! -d $$ANDROID_NDK ] ; then \
+ echo "$$ANDROID_NDK is not a valid folder"; \
+ exit 1; \
+ fi; \
+ echo "ndk.dir = $$ANDROID_NDK" > local.properties; \
+ echo "Please specify path of ANDROID SDK"; \
+ echo "e.g. $$HOME/Android/sdk/"; \
+ read SDKFLDR ; \
+ if [ ! -d $$SDKFLDR ] ; then \
+ echo "$$SDKFLDR is not a valid folder"; \
+ exit 1; \
+ fi; \
+ echo "sdk.dir = $$SDKFLDR" >> local.properties;
+
+
+$(OPENAL_TIMESTAMP) : openal_download
+ @LAST_MODIF=$$(find ${OPENAL_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
+ if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
+ touch ${OPENAL_TIMESTAMP}; \
+ fi
+
+openal_download :
+ @if [ ! -d ${OPENAL_DIR} ] ; then \
+ echo "openal sources missing, downloading..."; \
+ mkdir -p ${ANDR_ROOT}/deps; \
+ cd ${ANDR_ROOT}/deps ; \
+ git clone ${OPENAL_URL_GIT} || exit 1; \
+ fi
+
+openal : $(OPENAL_LIB)
+
+$(OPENAL_LIB): $(OPENAL_TIMESTAMP)
+ + @REFRESH=0; \
+ if [ ! -e ${OPENAL_TIMESTAMP_INT} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ ${OPENAL_TIMESTAMP} -nt ${OPENAL_TIMESTAMP_INT} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ $$REFRESH -ne 0 ] ; then \
+ echo "changed timestamp for openal detected building..."; \
+ cd ${OPENAL_DIR}; \
+ export APP_PLATFORM=${APP_PLATFORM}; \
+ export TARGET_ABI=${TARGET_ABI}; \
+ ${ANDROID_NDK}/ndk-build NDEBUG=${NDEBUG} \
+ NDK_APPLICATION_MK=${ANDR_ROOT}/jni/Deps.mk || exit 1; \
+ touch ${OPENAL_TIMESTAMP}; \
+ touch ${OPENAL_TIMESTAMP_INT}; \
+ else \
+ echo "nothing to be done for openal"; \
+ fi
+
+clean_openal :
+ $(RM) -rf ${OPENAL_DIR}
+
+$(OGG_TIMESTAMP) : ogg_download
+ @LAST_MODIF=$$(find ${OGG_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
+ if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
+ touch ${OGG_TIMESTAMP}; \
+ fi
+
+ogg_download :
+ @if [ ! -d ${OGG_DIR} ] ; then \
+ echo "ogg sources missing, downloading..."; \
+ mkdir -p ${ANDR_ROOT}/deps; \
+ cd ${ANDR_ROOT}/deps ; \
+ git clone ${OGG_URL_GIT}|| exit 1; \
+ cd libvorbis-libogg-android ; \
+ patch -p1 < ${ANDR_ROOT}/patches/libvorbis-libogg-fpu.patch || exit 1; \
+ fi
+
+ogg : $(OGG_LIB)
+
+$(OGG_LIB): $(OGG_TIMESTAMP)
+ + @REFRESH=0; \
+ if [ ! -e ${OGG_TIMESTAMP_INT} ] ; then \
+ echo "${OGG_TIMESTAMP_INT} doesn't exist"; \
+ REFRESH=1; \
+ fi; \
+ if [ ${OGG_TIMESTAMP} -nt ${OGG_TIMESTAMP_INT} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ $$REFRESH -ne 0 ] ; then \
+ echo "changed timestamp for ogg detected building..."; \
+ cd ${OGG_DIR}; \
+ export APP_PLATFORM=${APP_PLATFORM}; \
+ export TARGET_ABI=${TARGET_ABI}; \
+ ${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh \
+ --toolchain=${TARGET_TOOLCHAIN}${COMPILER_VERSION} \
+ --platform=${APP_PLATFORM} \
+ --install-dir=$${TOOLCHAIN}; \
+ touch ${OGG_TIMESTAMP}; \
+ touch ${OGG_TIMESTAMP_INT}; \
+ else \
+ echo "nothing to be done for libogg/libvorbis"; \
+ fi
+
+clean_ogg :
+ $(RM) -rf ${OGG_DIR}
+
+$(OPENSSL_TIMESTAMP) : openssl_download
+ @LAST_MODIF=$$(find ${OPENSSL_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
+ if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
+ touch ${OPENSSL_TIMESTAMP}; \
+ fi
+
+openssl_download :
+ @if [ ! -d ${OPENSSL_DIR} ] ; then \
+ echo "openssl sources missing, downloading..."; \
+ mkdir -p ${ANDR_ROOT}/deps; \
+ cd ${ANDR_ROOT}/deps ; \
+ wget ${OPENSSL_URL} || exit 1; \
+ tar -xzf ${OPENSSL_BASEDIR}.tar.gz; \
+ cd ${OPENSSL_BASEDIR}; \
+ patch -p1 < ${ANDR_ROOT}/patches/openssl_arch.patch; \
+ fi
+
+openssl : $(OPENSSL_LIB)
+
+$(OPENSSL_LIB): $(OPENSSL_TIMESTAMP) $(GMP_LIB)
+ @REFRESH=0; \
+ if [ ! -e ${OPENSSL_TIMESTAMP_INT} ] ; then \
+ echo "${OPENSSL_TIMESTAMP_INT} doesn't exist"; \
+ REFRESH=1; \
+ fi; \
+ if [ ${OPENSSL_TIMESTAMP} -nt ${OPENSSL_TIMESTAMP_INT} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ $$REFRESH -ne 0 ] ; then \
+ echo "changed timestamp for openssl detected building..."; \
+ cd ${OPENSSL_DIR}; \
+ ln -s ${OPENSSL_DIR} ../openssl; \
+ export TOOLCHAIN=/tmp/ndk-${TARGET_HOST}-openssl; \
+ ${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh \
+ --toolchain=${TARGET_TOOLCHAIN}${COMPILER_VERSION} \
+ --platform=${APP_PLATFORM} \
+ --install-dir=$${TOOLCHAIN}; \
+ export PATH="$${TOOLCHAIN}/bin:$${PATH}"; \
+ CC=${CROSS_PREFIX}gcc ./Configure enable-gmp -DL_ENDIAN -I${GMP_DIR} -L${GMP_DIR}/usr/lib android-${TARGET_ARCH};\
+ CC=${CROSS_PREFIX}gcc ANDROID_DEV=/tmp/ndk-${TARGET_HOST} make depend; \
+ CC=${CROSS_PREFIX}gcc ANDROID_DEV=/tmp/ndk-${TARGET_HOST} make build_libs; \
+ touch ${OPENSSL_TIMESTAMP}; \
+ touch ${OPENSSL_TIMESTAMP_INT}; \
+ $(RM) -rf $${TOOLCHAIN}; \
+ else \
+ echo "nothing to be done for openssl"; \
+ fi
+
+clean_openssl :
+ $(RM) -rf ${OPENSSL_DIR}; \
+ $(RM) -rf $(ANDR_ROOT)/deps/${OPENSSL_BASEDIR}.tar.gz; \
+ $(RM) -rf $(ANDR_ROOT)/deps/openssl
+
+$(LEVELDB_TIMESTAMP) : leveldb_download
+ @LAST_MODIF=$$(find ${LEVELDB_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
+ if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
+ touch ${LEVELDB_TIMESTAMP}; \
+ fi
+
+leveldb_download :
+ @if [ ! -d ${LEVELDB_DIR} ] ; then \
+ echo "leveldb sources missing, downloading..."; \
+ mkdir -p ${ANDR_ROOT}/deps; \
+ cd ${ANDR_ROOT}/deps ; \
+ git clone ${LEVELDB_URL_GIT} || exit 1; \
+ cd ${LEVELDB_DIR} || exit 1; \
+ git checkout ${LEVELDB_COMMIT} || exit 1; \
+ fi
+
+leveldb : $(LEVELDB_LIB)
+ifeq ($(HAVE_LEVELDB),1)
+$(LEVELDB_LIB): $(LEVELDB_TIMESTAMP)
+ @REFRESH=0; \
+ if [ ! -e ${LEVELDB_TIMESTAMP_INT} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ ${LEVELDB_TIMESTAMP} -nt ${LEVELDB_TIMESTAMP_INT} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ $$REFRESH -ne 0 ] ; then \
+ echo "changed timestamp for leveldb detected building..."; \
+ cd deps/leveldb; \
+ export CROSS_PREFIX=${CROSS_PREFIX}; \
+ export TOOLCHAIN=/tmp/ndk-${TARGET_HOST}-leveldb; \
+ ${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh \
+ --toolchain=${TARGET_TOOLCHAIN}${COMPILER_VERSION} \
+ --platform=${APP_PLATFORM} \
+ --install-dir=$${TOOLCHAIN}; \
+ export PATH="$${TOOLCHAIN}/bin:$${PATH}"; \
+ export CC=${CROSS_PREFIX}gcc; \
+ export CXX=${CROSS_PREFIX}g++; \
+ export CFLAGS="$${CFLAGS} ${TARGET_CFLAGS_ADDON}"; \
+ export CPPFLAGS="$${CPPFLAGS} ${TARGET_CXXFLAGS_ADDON}"; \
+ export LDFLAGS="$${LDFLAGS} ${TARGET_LDFLAGS_ADDON}"; \
+ export TARGET_OS=OS_ANDROID_CROSSCOMPILE; \
+ $(MAKE) || exit 1; \
+ touch ${LEVELDB_TIMESTAMP}; \
+ touch ${LEVELDB_TIMESTAMP_INT}; \
+ $(RM) -rf $${TOOLCHAIN}; \
+ else \
+ echo "nothing to be done for leveldb"; \
+ fi
+endif
+
+clean_leveldb :
+ $(RM) -rf deps/leveldb
+
+$(FREETYPE_TIMESTAMP) : freetype_download
+ @LAST_MODIF=$$(find ${FREETYPE_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
+ if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
+ touch ${FREETYPE_TIMESTAMP}; \
+ fi
+
+freetype_download :
+ @if [ ! -d ${FREETYPE_DIR} ] ; then \
+ echo "freetype sources missing, downloading..."; \
+ mkdir -p ${ANDR_ROOT}/deps; \
+ cd deps; \
+ git clone ${FREETYPE_URL_GIT} || exit 1; \
+ fi
+
+freetype : $(FREETYPE_LIB)
+
+$(FREETYPE_LIB) : $(FREETYPE_TIMESTAMP)
+ + @REFRESH=0; \
+ if [ ! -e ${FREETYPE_TIMESTAMP_INT} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ ! -e ${FREETYPE_LIB} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ ${FREETYPE_TIMESTAMP} -nt ${FREETYPE_TIMESTAMP_INT} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ $$REFRESH -ne 0 ] ; then \
+ mkdir -p ${FREETYPE_DIR}; \
+ echo "changed timestamp for freetype detected building..."; \
+ cd ${FREETYPE_DIR}/Android/jni; \
+ export APP_PLATFORM=${APP_PLATFORM}; \
+ export TARGET_ABI=${TARGET_ABI}; \
+ ${ANDROID_NDK}/ndk-build NDEBUG=${NDEBUG} \
+ NDK_APPLICATION_MK=${ANDR_ROOT}/jni/Deps.mk || exit 1; \
+ touch ${FREETYPE_TIMESTAMP}; \
+ touch ${FREETYPE_TIMESTAMP_INT}; \
+ else \
+ echo "nothing to be done for freetype"; \
+ fi
+
+clean_freetype :
+ $(RM) -rf ${FREETYPE_DIR}
+
+$(ICONV_TIMESTAMP) : iconv_download
+ @LAST_MODIF=$$(find ${ICONV_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
+ if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
+ touch ${ICONV_TIMESTAMP}; \
+ fi
+
+iconv_download :
+ @if [ ! -d ${ICONV_DIR} ] ; then \
+ echo "iconv sources missing, downloading..."; \
+ mkdir -p ${ANDR_ROOT}/deps; \
+ cd ${ANDR_ROOT}/deps; \
+ wget ${ICONV_URL_HTTP} || exit 1; \
+ tar -xzf libiconv-${ICONV_VERSION}.tar.gz || exit 1; \
+ rm libiconv-${ICONV_VERSION}.tar.gz; \
+ ln -s libiconv-${ICONV_VERSION} libiconv; \
+ cd ${ICONV_DIR}; \
+ patch -p1 < ${ANDR_ROOT}/patches/libiconv_android.patch; \
+ patch -p1 < ${ANDR_ROOT}/patches/libiconv_stdio.patch; \
+ fi
+
+iconv : $(ICONV_LIB)
+
+$(ICONV_LIB) : $(ICONV_TIMESTAMP)
+ @REFRESH=0; \
+ if [ ! -e ${ICONV_TIMESTAMP_INT} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ ! -e ${ICONV_LIB} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ ${ICONV_TIMESTAMP} -nt ${ICONV_TIMESTAMP_INT} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ $$REFRESH -ne 0 ] ; then \
+ mkdir -p ${ICONV_DIR}; \
+ echo "changed timestamp for iconv detected building..."; \
+ cd ${ICONV_DIR}; \
+ \
+ export TOOLCHAIN=/tmp/ndk-${TARGET_HOST}-iconv; \
+ ${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh \
+ --toolchain=${TARGET_TOOLCHAIN}${COMPILER_VERSION} \
+ --platform=${APP_PLATFORM} \
+ --install-dir=$${TOOLCHAIN}; \
+ export PATH="$${TOOLCHAIN}/bin:$${PATH}"; \
+ export CFLAGS="$${CFLAGS} ${TARGET_CFLAGS_ADDON}"; \
+ export CC=${CROSS_PREFIX}gcc; \
+ export CXX=${CROSS_PREFIX}g++; \
+ export TARGET_OS=OS_ANDROID_CROSSCOMPILE; \
+ ./configure --host=${TARGET_HOST} || exit 1; \
+ sed -i 's/LIBICONV_VERSION_INFO) /LIBICONV_VERSION_INFO) -avoid-version /g' lib/Makefile; \
+ grep "iconv_LDFLAGS" src/Makefile; \
+ $(MAKE) -s || exit 1; \
+ touch ${ICONV_TIMESTAMP}; \
+ touch ${ICONV_TIMESTAMP_INT}; \
+ rm -rf ${TOOLCHAIN}; \
+ else \
+ echo "nothing to be done for iconv"; \
+ fi
+
+clean_iconv :
+ $(RM) -rf ${ICONV_DIR}
+
+#Note: Texturehack patch is required for gpu's not supporting color format
+# correctly. Known bad GPU:
+# -geforce on emulator
+# -Vivante Corporation GC1000 core (e.g. Galaxy Tab 3)
+
+irrlicht_download :
+ @if [ ! -d "deps/irrlicht" ] ; then \
+ echo "irrlicht sources missing, downloading..."; \
+ mkdir -p ${ANDR_ROOT}/deps; \
+ cd deps; \
+ svn co ${IRRLICHT_URL_SVN} irrlicht || exit 1; \
+ cd irrlicht; \
+ patch -p1 < ${ANDR_ROOT}/patches/irrlicht-touchcount.patch || exit 1; \
+ patch -p1 < ${ANDR_ROOT}/patches/irrlicht-back_button.patch || exit 1; \
+ patch -p1 < ${ANDR_ROOT}/patches/irrlicht-texturehack.patch || exit 1; \
+ patch -p1 < ${ANDR_ROOT}/patches/irrlicht-native_activity.patch || exit 1; \
+ fi
+
+$(IRRLICHT_TIMESTAMP) : irrlicht_download
+ @LAST_MODIF=$$(find ${IRRLICHT_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
+ if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
+ touch ${IRRLICHT_TIMESTAMP}; \
+ fi
+
+irrlicht : $(IRRLICHT_LIB)
+
+$(IRRLICHT_LIB): $(IRRLICHT_TIMESTAMP) $(FREETYPE_LIB)
+ + @REFRESH=0; \
+ if [ ! -e ${IRRLICHT_TIMESTAMP_INT} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ ! -e ${IRRLICHT_LIB} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ ${IRRLICHT_TIMESTAMP} -nt ${IRRLICHT_TIMESTAMP_INT} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ $$REFRESH -ne 0 ] ; then \
+ mkdir -p ${IRRLICHT_DIR}; \
+ echo "changed timestamp for irrlicht detected building..."; \
+ cd deps/irrlicht/source/Irrlicht/Android; \
+ export APP_PLATFORM=${APP_PLATFORM}; \
+ export TARGET_ABI=${TARGET_ABI}; \
+ ${ANDROID_NDK}/ndk-build NDEBUG=${NDEBUG} \
+ NDK_APPLICATION_MK=${ANDR_ROOT}/jni/Irrlicht.mk || exit 1; \
+ touch ${IRRLICHT_TIMESTAMP}; \
+ touch ${IRRLICHT_TIMESTAMP_INT}; \
+ else \
+ echo "nothing to be done for irrlicht"; \
+ fi
+
+clean_irrlicht :
+ $(RM) -rf deps/irrlicht
+
+$(CURL_TIMESTAMP) : curl_download
+ @LAST_MODIF=$$(find ${CURL_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
+ if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
+ touch ${CURL_TIMESTAMP}; \
+ fi
+
+curl_download :
+ @if [ ! -d "deps/curl-${CURL_VERSION}" ] ; then \
+ echo "curl sources missing, downloading..."; \
+ mkdir -p ${ANDR_ROOT}/deps; \
+ cd deps; \
+ wget ${CURL_URL_HTTP} || exit 1; \
+ tar -xjf curl-${CURL_VERSION}.tar.bz2 || exit 1; \
+ rm curl-${CURL_VERSION}.tar.bz2; \
+ ln -s curl-${CURL_VERSION} curl; \
+ fi
+
+curl : $(CURL_LIB)
+
+$(CURL_LIB): $(CURL_TIMESTAMP) $(OPENSSL_LIB)
+ @REFRESH=0; \
+ if [ ! -e ${CURL_TIMESTAMP_INT} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ ! -e ${CURL_LIB} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ ${CURL_TIMESTAMP} -nt ${CURL_TIMESTAMP_INT} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ $$REFRESH -ne 0 ] ; then \
+ mkdir -p ${CURL_DIR}; \
+ echo "changed timestamp for curl detected building..."; \
+ cd deps/curl-${CURL_VERSION}; \
+ export CROSS_PREFIX=${CROSS_PREFIX}; \
+ export TOOLCHAIN=/tmp/ndk-${TARGET_HOST}-curl; \
+ ${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh \
+ --toolchain=${TARGET_TOOLCHAIN}${COMPILER_VERSION} \
+ --platform=${APP_PLATFORM} \
+ --install-dir=$${TOOLCHAIN}; \
+ export PATH="$${TOOLCHAIN}/bin:$${PATH}"; \
+ export CC=${CROSS_PREFIX}gcc; \
+ export CXX=${CROSS_PREFIX}g++; \
+ export TARGET_OS=OS_ANDROID_CROSSCOMPILE; \
+ export CPPFLAGS="$${CPPFLAGS} -I${OPENSSL_DIR}/include ${TARGET_CFLAGS_ADDON}"; \
+ export CFLAGS="$${CFLAGS} ${TARGET_CFLAGS_ADDON}"; \
+ export LDFLAGS="$${LDFLAGS} -L${OPENSSL_DIR} ${TARGET_LDFLAGS_ADDON}"; \
+ ./configure --host=${TARGET_HOST} --disable-shared --enable-static --with-ssl; \
+ $(MAKE) -s || exit 1; \
+ touch ${CURL_TIMESTAMP}; \
+ touch ${CURL_TIMESTAMP_INT}; \
+ $(RM) -rf $${TOOLCHAIN}; \
+ else \
+ echo "nothing to be done for curl"; \
+ fi
+
+clean_curl :
+ $(RM) -rf deps/curl-${CURL_VERSION} \
+ $(RM) -f deps/curl
+
+$(GMP_TIMESTAMP) : gmp_download
+ @LAST_MODIF=$$(find ${GMP_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
+ if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
+ touch ${GMP_TIMESTAMP}; \
+ fi
+
+gmp_download :
+ @if [ ! -d "${GMP_DIR}" ] ; then \
+ echo "gmp sources missing, downloading..."; \
+ mkdir -p ${ANDR_ROOT}/deps; \
+ cd deps; \
+ wget ${GMP_URL_HTTP} || exit 1; \
+ tar -xjf gmp-${GMP_VERSION}.tar.bz2 || exit 1; \
+ rm gmp-${GMP_VERSION}.tar.bz2; \
+ ln -s gmp-${GMP_VERSION} gmp; \
+ fi
+
+gmp : $(GMP_LIB)
+
+$(GMP_LIB): $(GMP_TIMESTAMP)
+ @REFRESH=0; \
+ if [ ! -e ${GMP_TIMESTAMP_INT} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ ! -e ${GMP_LIB} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ ${GMP_TIMESTAMP} -nt ${GMP_TIMESTAMP_INT} ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ $$REFRESH -ne 0 ] ; then \
+ mkdir -p ${GMP_DIR}; \
+ echo "changed timestamp for gmp detected building..."; \
+ cd deps/gmp-${GMP_VERSION}; \
+ export CROSS_PREFIX=${CROSS_PREFIX}; \
+ export TOOLCHAIN=/tmp/ndk-${TARGET_HOST}-gmp; \
+ ${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh \
+ --toolchain=${TARGET_TOOLCHAIN}${COMPILER_VERSION} \
+ --platform=${APP_PLATFORM} \
+ --install-dir=$${TOOLCHAIN}; \
+ export PATH="$${TOOLCHAIN}/bin:$${PATH}"; \
+ export CC=${CROSS_PREFIX}gcc; \
+ export CXX=${CROSS_PREFIX}g++; \
+ export LIBGMP_LDFLAGS="-avoid-version"; \
+ export LIBGMPXX_LDFLAGS="-avoid-version"; \
+ ./configure --disable-static --host=${TARGET_HOST} --prefix=/usr; \
+ $(MAKE) install DESTDIR=/${GMP_DIR} || exit 1; \
+ touch ${GMP_TIMESTAMP}; \
+ touch ${GMP_TIMESTAMP_INT}; \
+ $(RM) -rf $${TOOLCHAIN}; \
+ else \
+ echo "nothing to be done for gmp"; \
+ fi
+
+clean_gmp:
+ $(RM) -rf deps/gmp-${GMP_VERSION} \
+ $(RM) -f deps/gmp
+
+sqlite3_download: deps/${SQLITE3_FOLDER}/sqlite3.c
+
+deps/${SQLITE3_FOLDER}/sqlite3.c :
+ cd deps; \
+ wget ${SQLITE3_URL}; \
+ unzip ${SQLITE3_FOLDER}.zip; \
+ ln -s ${SQLITE3_FOLDER} sqlite; \
+ cd ${SQLITE3_FOLDER};
+
+clean_sqlite3:
+ cd deps && $(RM) -rf ${SQLITE3_FOLDER} && $(RM) -f ${SQLITE3_FOLDER}.zip && \
+ $(RM) -f sqlite
+
+$(ASSETS_TIMESTAMP) : $(IRRLICHT_LIB)
+ @mkdir -p ${ANDR_ROOT}/deps; \
+ for DIRNAME in {builtin,client,doc,fonts,games,mods,po,textures}; do \
+ LAST_MODIF=$$(find ${PROJ_ROOT}/${DIRNAME} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
+ if [ $$(basename $$LAST_MODIF) != "timestamp" ]; then \
+ touch ${PROJ_ROOT}/${DIRNAME}/timestamp; \
+ touch ${ASSETS_TIMESTAMP}; \
+ echo ${DIRNAME} changed $$LAST_MODIF; \
+ fi; \
+ done; \
+ LAST_MODIF=$$(find ${IRRLICHT_DIR}/media -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \
+ if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \
+ touch ${IRRLICHT_DIR}/media/timestamp; \
+ touch ${ASSETS_TIMESTAMP}; \
+ fi; \
+ if [ ${PROJ_ROOT}/minetest.conf.example -nt ${ASSETS_TIMESTAMP} ] ; then \
+ echo "conf changed"; \
+ touch ${ASSETS_TIMESTAMP}; \
+ fi; \
+ if [ ${PROJ_ROOT}/README.txt -nt ${ASSETS_TIMESTAMP} ] ; then \
+ touch ${ASSETS_TIMESTAMP}; \
+ fi; \
+ if [ ! -e $(ASSETS_TIMESTAMP) ] ; then \
+ touch $(ASSETS_TIMESTAMP); \
+ fi
+
+assets : $(ASSETS_TIMESTAMP)
+ @REFRESH=0; \
+ if [ ! -e ${ASSETS_TIMESTAMP}.old ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ ${ASSETS_TIMESTAMP} -nt ${ASSETS_TIMESTAMP}.old ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ ! -d ${APP_ROOT}/assets ] ; then \
+ REFRESH=1; \
+ fi; \
+ if [ $$REFRESH -ne 0 ] ; then \
+ echo "assets changed, refreshing..."; \
+ $(MAKE) clean_assets; \
+ mkdir -p ${APP_ROOT}/assets/Minetest; \
+ cp ${PROJ_ROOT}/minetest.conf.example ${APP_ROOT}/assets/Minetest; \
+ cp ${PROJ_ROOT}/README.txt ${APP_ROOT}/assets/Minetest; \
+ cp -r ${PROJ_ROOT}/builtin ${APP_ROOT}/assets/Minetest; \
+ mkdir -p ${APP_ROOT}/assets/Minetest/client; \
+ cp -r ${PROJ_ROOT}/client/shaders ${APP_ROOT}/assets/Minetest/client; \
+ cp ${PROJ_ROOT}/doc/lgpl-2.1.txt ${APP_ROOT}/assets/Minetest/LICENSE.txt; \
+ mkdir -p ${APP_ROOT}/assets/Minetest/fonts; \
+ cp -r ${PROJ_ROOT}/fonts/*.ttf ${APP_ROOT}/assets/Minetest/fonts/; \
+ mkdir -p ${APP_ROOT}/assets/Minetest/games; \
+ for game in ${GAMES_TO_COPY}; do \
+ cp -r ${PROJ_ROOT}/games/$$game ${APP_ROOT}/assets/Minetest/games/; \
+ done; \
+ mkdir -p ${APP_ROOT}/assets/Minetest/mods; \
+ for mod in ${MODS_TO_COPY}; do \
+ cp -r ${PROJ_ROOT}/mods/$$mod ${APP_ROOT}/assets/Minetest/mods/; \
+ done; \
+ cp -r ${PROJ_ROOT}/po ${APP_ROOT}/assets/Minetest; \
+ cp -r ${PROJ_ROOT}/textures ${APP_ROOT}/assets/Minetest; \
+ mkdir -p ${APP_ROOT}/assets/Minetest/media; \
+ cp -r ${IRRLICHT_DIR}/media/Shaders ${APP_ROOT}/assets/Minetest/media; \
+ cd ${APP_ROOT}/assets || exit 1; \
+ find . -name "timestamp" -exec rm {} \; ; \
+ find . -name "*.blend" -exec rm {} \; ; \
+ find . -name "*~" -exec rm {} \; ; \
+ find . -type d -path "*.git" -exec rm -rf {} \; ; \
+ find . -type d -path "*.svn" -exec rm -rf {} \; ; \
+ find . -type f -path "*.gitignore" -exec rm -rf {} \; ; \
+ ls -R | grep ":$$" | sed -e 's/:$$//' -e 's/\.//' -e 's/^\///' > "index.txt"; \
+ find -L Minetest > filelist.txt; \
+ cp ${ANDR_ROOT}/${ASSETS_TIMESTAMP} ${ANDR_ROOT}/${ASSETS_TIMESTAMP}.old; \
+ else \
+ echo "nothing to be done for assets"; \
+ fi
+
+clean_assets :
+ @$(RM) -r assets
+
+apk: local.properties assets $(ICONV_LIB) $(IRRLICHT_LIB) $(CURL_LIB) $(GMP_LIB) $(LEVELDB_TARGET) \
+ $(OPENAL_LIB) $(OGG_LIB) prep_srcdir $(ANDR_ROOT)/jni/src/android_version.h \
+ $(ANDR_ROOT)/jni/src/android_version_githash.h sqlite3_download
+ + @export TARGET_LIBDIR=${TARGET_LIBDIR}; \
+ export HAVE_LEVELDB=${HAVE_LEVELDB}; \
+ export APP_PLATFORM=${APP_PLATFORM}; \
+ export TARGET_ABI=${TARGET_ABI}; \
+ ${ANDROID_NDK}/ndk-build || exit 1; \
+ if [ ! -e ${APP_ROOT}/jniLibs ]; then \
+ ln -s ${ANDR_ROOT}/libs ${APP_ROOT}/jniLibs || exit 1; \
+ fi; \
+ export VERSION_STR="${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}" && \
+ export BUILD_TYPE_C=$$(echo "$${BUILD_TYPE}" | sed 's/./\U&/') && \
+ ./gradlew assemble$$BUILD_TYPE_C && \
+ echo "APK stored at: build/outputs/apk/Minetest-$$BUILD_TYPE.apk" && \
+ echo "You can install it with \`make install_$$BUILD_TYPE\`"
+
+# These Intentionally doesn't depend on their respective build steps,
+# because it takes a while to verify that everything's up-to-date.
+install_debug:
+ ${ANDROID_SDK}/platform-tools/adb install -r build/outputs/apk/Minetest-debug.apk
+
+install_release:
+ ${ANDROID_SDK}/platform-tools/adb install -r build/outputs/apk/Minetest-release.apk
+
+prep_srcdir :
+ @if [ ! -e ${ANDR_ROOT}/jni/src ]; then \
+ ln -s ${PROJ_ROOT}/src ${ANDR_ROOT}/jni/src; \
+ fi; \
+ if [ ! -e ${ANDR_ROOT}/jni/lib ]; then \
+ ln -s ${PROJ_ROOT}/lib ${ANDR_ROOT}/jni/lib; \
+ fi
+
+clean_apk :
+ ./gradlew clean
+
+clean_all :
+ @$(MAKE) clean_apk; \
+ $(MAKE) clean_assets clean_iconv clean_irrlicht clean_leveldb clean_curl \
+ clean_openssl clean_openal clean_ogg clean_gmp; \
+ sleep 1; \
+ $(RM) -r gen libs obj deps bin Debug and_env
+
+$(ANDR_ROOT)/jni/src/android_version_githash.h : prep_srcdir
+ @export VERSION_FILE=${ANDR_ROOT}/jni/src/android_version_githash.h; \
+ export VERSION_FILE_NEW=$${VERSION_FILE}.new; \
+ { \
+ echo "#ifndef ANDROID_MT_VERSION_GITHASH_H"; \
+ echo "#define ANDROID_MT_VERSION_GITHASH_H"; \
+ export GITHASH=$$(git rev-parse --short=8 HEAD); \
+ export VERSION_STR="${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}"; \
+ echo "#define VERSION_GITHASH \"$$VERSION_STR-$$GITHASH-Android\""; \
+ echo "#endif"; \
+ } > "$${VERSION_FILE_NEW}"; \
+ if ! cmp -s $${VERSION_FILE} $${VERSION_FILE_NEW}; then \
+ echo "android_version_githash.h changed, updating..."; \
+ mv "$${VERSION_FILE_NEW}" "$${VERSION_FILE}"; \
+ else \
+ rm "$${VERSION_FILE_NEW}"; \
+ fi
+
+
+$(ANDR_ROOT)/jni/src/android_version.h : prep_srcdir
+ @export VERSION_FILE=${ANDR_ROOT}/jni/src/android_version.h; \
+ export VERSION_FILE_NEW=$${VERSION_FILE}.new; \
+ { \
+ echo "#ifndef ANDROID_MT_VERSION_H"; \
+ echo "#define ANDROID_MT_VERSION_H"; \
+ echo "#define VERSION_MAJOR ${VERSION_MAJOR}"; \
+ echo "#define VERSION_MINOR ${VERSION_MINOR}"; \
+ echo "#define VERSION_PATCH ${VERSION_PATCH}"; \
+ echo "#define VERSION_STRING STR(VERSION_MAJOR) \".\" STR(VERSION_MINOR) \
+ \".\" STR(VERSION_PATCH)"; \
+ echo "#endif"; \
+ } > $${VERSION_FILE_NEW}; \
+ if ! cmp -s $${VERSION_FILE} $${VERSION_FILE_NEW}; then \
+ echo "android_version.h changed, updating..."; \
+ mv "$${VERSION_FILE_NEW}" "$${VERSION_FILE}"; \
+ else \
+ rm "$${VERSION_FILE_NEW}"; \
+ fi
+
+clean : clean_apk clean_assets
diff --git a/build/android/build.gradle b/build/android/build.gradle
new file mode 100644
index 000000000..51751732e
--- /dev/null
+++ b/build/android/build.gradle
@@ -0,0 +1,48 @@
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:1.5.0"
+ }
+}
+
+apply plugin: "com.android.application"
+
+android {
+ compileSdkVersion 25
+ buildToolsVersion "25.0.3"
+
+ defaultConfig {
+ versionCode 18
+ versionName "${System.env.VERSION_STR}.${versionCode}"
+ minSdkVersion 14
+ targetSdkVersion 14
+ applicationId "net.minetest.minetest"
+ manifestPlaceholders = [ package: "net.minetest.minetest", project: project.name ]
+ }
+
+ lintOptions {
+ disable "OldTargetApi", "GoogleAppIndexingWarning"
+ }
+
+ Properties props = new Properties()
+ props.load(new FileInputStream(file("local.properties")))
+
+ if (props.getProperty("keystore") != null) {
+ signingConfigs {
+ release {
+ storeFile file(props["keystore"])
+ storePassword props["keystore.password"]
+ keyAlias props["key"]
+ keyPassword props["key.password"]
+ }
+ }
+
+ buildTypes {
+ release {
+ signingConfig signingConfigs.release
+ }
+ }
+ }
+}
diff --git a/build/android/gradle/wrapper/gradle-wrapper.jar b/build/android/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..8c0fb64a8
--- /dev/null
+++ b/build/android/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/build/android/gradle/wrapper/gradle-wrapper.properties b/build/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..980438b75
--- /dev/null
+++ b/build/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Aug 27 20:10:09 CEST 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
diff --git a/build/android/gradlew b/build/android/gradlew
new file mode 100755
index 000000000..91a7e269e
--- /dev/null
+++ b/build/android/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/build/android/gradlew.bat b/build/android/gradlew.bat
new file mode 100644
index 000000000..8a0b282aa
--- /dev/null
+++ b/build/android/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk
new file mode 100644
index 000000000..7b741e04b
--- /dev/null
+++ b/build/android/jni/Android.mk
@@ -0,0 +1,402 @@
+LOCAL_PATH := $(call my-dir)/..
+
+#LOCAL_ADDRESS_SANITIZER:=true
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := Irrlicht
+LOCAL_SRC_FILES := deps/irrlicht/lib/Android/libIrrlicht.a
+include $(PREBUILT_STATIC_LIBRARY)
+
+ifeq ($(HAVE_LEVELDB), 1)
+ include $(CLEAR_VARS)
+ LOCAL_MODULE := LevelDB
+ LOCAL_SRC_FILES := deps/leveldb/libleveldb.a
+ include $(PREBUILT_STATIC_LIBRARY)
+endif
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := curl
+LOCAL_SRC_FILES := deps/curl/lib/.libs/libcurl.a
+include $(PREBUILT_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := freetype
+LOCAL_SRC_FILES := deps/freetype2-android/Android/obj/local/$(TARGET_ARCH_ABI)/libfreetype2-static.a
+include $(PREBUILT_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := iconv
+LOCAL_SRC_FILES := deps/libiconv/lib/.libs/libiconv.so
+include $(PREBUILT_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := openal
+LOCAL_SRC_FILES := deps/openal-soft/libs/$(TARGET_LIBDIR)/libopenal.so
+include $(PREBUILT_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := ogg
+LOCAL_SRC_FILES := deps/libvorbis-libogg-android/libs/$(TARGET_LIBDIR)/libogg.so
+include $(PREBUILT_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := vorbis
+LOCAL_SRC_FILES := deps/libvorbis-libogg-android/libs/$(TARGET_LIBDIR)/libvorbis.so
+include $(PREBUILT_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := gmp
+LOCAL_SRC_FILES := deps/gmp/usr/lib/libgmp.so
+include $(PREBUILT_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := ssl
+LOCAL_SRC_FILES := deps/openssl/libssl.a
+include $(PREBUILT_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := crypto
+LOCAL_SRC_FILES := deps/openssl/libcrypto.a
+include $(PREBUILT_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := minetest
+
+LOCAL_CPP_FEATURES += exceptions
+
+ifdef GPROF
+GPROF_DEF=-DGPROF
+endif
+
+LOCAL_CFLAGS := -D_IRR_ANDROID_PLATFORM_ \
+ -DHAVE_TOUCHSCREENGUI \
+ -DUSE_CURL=1 \
+ -DUSE_SOUND=1 \
+ -DUSE_FREETYPE=1 \
+ -DUSE_LEVELDB=$(HAVE_LEVELDB) \
+ $(GPROF_DEF) \
+ -pipe -fstrict-aliasing
+
+ifndef NDEBUG
+LOCAL_CFLAGS += -g -D_DEBUG -O0 -fno-omit-frame-pointer
+else
+LOCAL_CFLAGS += -O3
+endif
+
+ifdef GPROF
+PROFILER_LIBS := android-ndk-profiler
+LOCAL_CFLAGS += -pg
+endif
+
+# LOCAL_CFLAGS += -fsanitize=address
+# LOCAL_LDFLAGS += -fsanitize=address
+
+ifeq ($(TARGET_ARCH_ABI),x86)
+LOCAL_CFLAGS += -fno-stack-protector
+endif
+
+LOCAL_C_INCLUDES := \
+ jni/src \
+ jni/src/script \
+ jni/lib/lua/src \
+ jni/lib/jsoncpp \
+ jni/src/cguittfont \
+ deps/irrlicht/include \
+ deps/libiconv/include \
+ deps/freetype2-android/include \
+ deps/curl/include \
+ deps/openal-soft/jni/OpenAL/include \
+ deps/libvorbis-libogg-android/jni/include \
+ deps/gmp/usr/include \
+ deps/leveldb/include \
+ deps/sqlite/
+
+LOCAL_SRC_FILES := \
+ jni/src/ban.cpp \
+ jni/src/camera.cpp \
+ jni/src/cavegen.cpp \
+ jni/src/chat.cpp \
+ jni/src/client.cpp \
+ jni/src/clientenvironment.cpp \
+ jni/src/clientiface.cpp \
+ jni/src/clientmap.cpp \
+ jni/src/clientmedia.cpp \
+ jni/src/clientobject.cpp \
+ jni/src/clouds.cpp \
+ jni/src/collision.cpp \
+ jni/src/content_abm.cpp \
+ jni/src/content_cao.cpp \
+ jni/src/content_cso.cpp \
+ jni/src/content_mapblock.cpp \
+ jni/src/content_mapnode.cpp \
+ jni/src/content_nodemeta.cpp \
+ jni/src/content_sao.cpp \
+ jni/src/convert_json.cpp \
+ jni/src/craftdef.cpp \
+ jni/src/database-dummy.cpp \
+ jni/src/database-files.cpp \
+ jni/src/database-sqlite3.cpp \
+ jni/src/database.cpp \
+ jni/src/debug.cpp \
+ jni/src/defaultsettings.cpp \
+ jni/src/drawscene.cpp \
+ jni/src/dungeongen.cpp \
+ jni/src/emerge.cpp \
+ jni/src/environment.cpp \
+ jni/src/face_position_cache.cpp \
+ jni/src/filecache.cpp \
+ jni/src/filesys.cpp \
+ jni/src/fontengine.cpp \
+ jni/src/game.cpp \
+ jni/src/genericobject.cpp \
+ jni/src/gettext.cpp \
+ jni/src/guiChatConsole.cpp \
+ jni/src/guiEngine.cpp \
+ jni/src/guiFileSelectMenu.cpp \
+ jni/src/guiFormSpecMenu.cpp \
+ jni/src/guiKeyChangeMenu.cpp \
+ jni/src/guiPasswordChange.cpp \
+ jni/src/guiTable.cpp \
+ jni/src/guiscalingfilter.cpp \
+ jni/src/guiVolumeChange.cpp \
+ jni/src/httpfetch.cpp \
+ jni/src/hud.cpp \
+ jni/src/imagefilters.cpp \
+ jni/src/intlGUIEditBox.cpp \
+ jni/src/inventory.cpp \
+ jni/src/inventorymanager.cpp \
+ jni/src/itemdef.cpp \
+ jni/src/itemstackmetadata.cpp \
+ jni/src/keycode.cpp \
+ jni/src/light.cpp \
+ jni/src/localplayer.cpp \
+ jni/src/log.cpp \
+ jni/src/main.cpp \
+ jni/src/map.cpp \
+ jni/src/map_settings_manager.cpp \
+ jni/src/mapblock.cpp \
+ jni/src/mapblock_mesh.cpp \
+ jni/src/mapgen.cpp \
+ jni/src/mapgen_flat.cpp \
+ jni/src/mapgen_fractal.cpp \
+ jni/src/mapgen_singlenode.cpp \
+ jni/src/mapgen_v5.cpp \
+ jni/src/mapgen_v6.cpp \
+ jni/src/mapgen_v7.cpp \
+ jni/src/mapgen_valleys.cpp \
+ jni/src/mapnode.cpp \
+ jni/src/mapsector.cpp \
+ jni/src/mesh.cpp \
+ jni/src/mesh_generator_thread.cpp \
+ jni/src/metadata.cpp \
+ jni/src/mg_biome.cpp \
+ jni/src/mg_decoration.cpp \
+ jni/src/mg_ore.cpp \
+ jni/src/mg_schematic.cpp \
+ jni/src/minimap.cpp \
+ jni/src/mods.cpp \
+ jni/src/nameidmapping.cpp \
+ jni/src/nodedef.cpp \
+ jni/src/nodemetadata.cpp \
+ jni/src/nodetimer.cpp \
+ jni/src/noise.cpp \
+ jni/src/objdef.cpp \
+ jni/src/object_properties.cpp \
+ jni/src/particles.cpp \
+ jni/src/pathfinder.cpp \
+ jni/src/player.cpp \
+ jni/src/porting_android.cpp \
+ jni/src/porting.cpp \
+ jni/src/profiler.cpp \
+ jni/src/quicktune.cpp \
+ jni/src/raycast.cpp \
+ jni/src/reflowscan.cpp \
+ jni/src/remoteplayer.cpp \
+ jni/src/rollback.cpp \
+ jni/src/rollback_interface.cpp \
+ jni/src/serialization.cpp \
+ jni/src/server.cpp \
+ jni/src/serverenvironment.cpp \
+ jni/src/serverlist.cpp \
+ jni/src/serverobject.cpp \
+ jni/src/shader.cpp \
+ jni/src/sky.cpp \
+ jni/src/socket.cpp \
+ jni/src/sound.cpp \
+ jni/src/sound_openal.cpp \
+ jni/src/staticobject.cpp \
+ jni/src/subgame.cpp \
+ jni/src/tileanimation.cpp \
+ jni/src/tool.cpp \
+ jni/src/treegen.cpp \
+ jni/src/version.cpp \
+ jni/src/voxel.cpp \
+ jni/src/voxelalgorithms.cpp \
+ jni/src/util/areastore.cpp \
+ jni/src/util/auth.cpp \
+ jni/src/util/base64.cpp \
+ jni/src/util/directiontables.cpp \
+ jni/src/util/enriched_string.cpp \
+ jni/src/util/numeric.cpp \
+ jni/src/util/pointedthing.cpp \
+ jni/src/util/serialize.cpp \
+ jni/src/util/sha1.cpp \
+ jni/src/util/string.cpp \
+ jni/src/util/srp.cpp \
+ jni/src/util/timetaker.cpp \
+ jni/src/unittest/test.cpp \
+ jni/src/unittest/test_collision.cpp \
+ jni/src/unittest/test_compression.cpp \
+ jni/src/unittest/test_connection.cpp \
+ jni/src/unittest/test_filepath.cpp \
+ jni/src/unittest/test_inventory.cpp \
+ jni/src/unittest/test_map_settings_manager.cpp \
+ jni/src/unittest/test_mapnode.cpp \
+ jni/src/unittest/test_nodedef.cpp \
+ jni/src/unittest/test_noderesolver.cpp \
+ jni/src/unittest/test_noise.cpp \
+ jni/src/unittest/test_objdef.cpp \
+ jni/src/unittest/test_profiler.cpp \
+ jni/src/unittest/test_random.cpp \
+ jni/src/unittest/test_schematic.cpp \
+ jni/src/unittest/test_serialization.cpp \
+ jni/src/unittest/test_settings.cpp \
+ jni/src/unittest/test_socket.cpp \
+ jni/src/unittest/test_utilities.cpp \
+ jni/src/unittest/test_voxelalgorithms.cpp \
+ jni/src/unittest/test_voxelmanipulator.cpp \
+ jni/src/touchscreengui.cpp \
+ jni/src/database-leveldb.cpp \
+ jni/src/settings.cpp \
+ jni/src/wieldmesh.cpp \
+ jni/src/client/clientlauncher.cpp \
+ jni/src/client/inputhandler.cpp \
+ jni/src/client/tile.cpp \
+ jni/src/client/joystick_controller.cpp \
+ jni/src/irrlicht_changes/static_text.cpp
+
+# intentionally kept out (we already build openssl itself): jni/src/util/sha256.c
+
+# Network
+LOCAL_SRC_FILES += \
+ jni/src/network/connection.cpp \
+ jni/src/network/networkpacket.cpp \
+ jni/src/network/clientopcodes.cpp \
+ jni/src/network/clientpackethandler.cpp \
+ jni/src/network/serveropcodes.cpp \
+ jni/src/network/serverpackethandler.cpp \
+
+# lua api
+LOCAL_SRC_FILES += \
+ jni/src/script/common/c_content.cpp \
+ jni/src/script/common/c_converter.cpp \
+ jni/src/script/common/c_internal.cpp \
+ jni/src/script/common/c_types.cpp \
+ jni/src/script/cpp_api/s_async.cpp \
+ jni/src/script/cpp_api/s_base.cpp \
+ jni/src/script/cpp_api/s_client.cpp \
+ jni/src/script/cpp_api/s_entity.cpp \
+ jni/src/script/cpp_api/s_env.cpp \
+ jni/src/script/cpp_api/s_inventory.cpp \
+ jni/src/script/cpp_api/s_item.cpp \
+ jni/src/script/cpp_api/s_mainmenu.cpp \
+ jni/src/script/cpp_api/s_node.cpp \
+ jni/src/script/cpp_api/s_nodemeta.cpp \
+ jni/src/script/cpp_api/s_player.cpp \
+ jni/src/script/cpp_api/s_security.cpp \
+ jni/src/script/cpp_api/s_server.cpp \
+ jni/src/script/lua_api/l_areastore.cpp \
+ jni/src/script/lua_api/l_base.cpp \
+ jni/src/script/lua_api/l_camera.cpp \
+ jni/src/script/lua_api/l_client.cpp \
+ jni/src/script/lua_api/l_craft.cpp \
+ jni/src/script/lua_api/l_env.cpp \
+ jni/src/script/lua_api/l_inventory.cpp \
+ jni/src/script/lua_api/l_item.cpp \
+ jni/src/script/lua_api/l_itemstackmeta.cpp\
+ jni/src/script/lua_api/l_localplayer.cpp \
+ jni/src/script/lua_api/l_mainmenu.cpp \
+ jni/src/script/lua_api/l_mapgen.cpp \
+ jni/src/script/lua_api/l_metadata.cpp \
+ jni/src/script/lua_api/l_minimap.cpp \
+ jni/src/script/lua_api/l_nodemeta.cpp \
+ jni/src/script/lua_api/l_nodetimer.cpp \
+ jni/src/script/lua_api/l_noise.cpp \
+ jni/src/script/lua_api/l_object.cpp \
+ jni/src/script/lua_api/l_particles.cpp \
+ jni/src/script/lua_api/l_rollback.cpp \
+ jni/src/script/lua_api/l_server.cpp \
+ jni/src/script/lua_api/l_settings.cpp \
+ jni/src/script/lua_api/l_sound.cpp \
+ jni/src/script/lua_api/l_http.cpp \
+ jni/src/script/lua_api/l_storage.cpp \
+ jni/src/script/lua_api/l_util.cpp \
+ jni/src/script/lua_api/l_vmanip.cpp \
+ jni/src/script/scripting_client.cpp \
+ jni/src/script/scripting_server.cpp \
+ jni/src/script/scripting_mainmenu.cpp
+
+#freetype2 support
+LOCAL_SRC_FILES += jni/src/cguittfont/xCGUITTFont.cpp
+
+# Lua
+LOCAL_SRC_FILES += \
+ jni/lib/lua/src/lapi.c \
+ jni/lib/lua/src/lauxlib.c \
+ jni/lib/lua/src/lbaselib.c \
+ jni/lib/lua/src/lcode.c \
+ jni/lib/lua/src/ldblib.c \
+ jni/lib/lua/src/ldebug.c \
+ jni/lib/lua/src/ldo.c \
+ jni/lib/lua/src/ldump.c \
+ jni/lib/lua/src/lfunc.c \
+ jni/lib/lua/src/lgc.c \
+ jni/lib/lua/src/linit.c \
+ jni/lib/lua/src/liolib.c \
+ jni/lib/lua/src/llex.c \
+ jni/lib/lua/src/lmathlib.c \
+ jni/lib/lua/src/lmem.c \
+ jni/lib/lua/src/loadlib.c \
+ jni/lib/lua/src/lobject.c \
+ jni/lib/lua/src/lopcodes.c \
+ jni/lib/lua/src/loslib.c \
+ jni/lib/lua/src/lparser.c \
+ jni/lib/lua/src/lstate.c \
+ jni/lib/lua/src/lstring.c \
+ jni/lib/lua/src/lstrlib.c \
+ jni/lib/lua/src/ltable.c \
+ jni/lib/lua/src/ltablib.c \
+ jni/lib/lua/src/ltm.c \
+ jni/lib/lua/src/lundump.c \
+ jni/lib/lua/src/lvm.c \
+ jni/lib/lua/src/lzio.c \
+ jni/lib/lua/src/print.c
+
+# SQLite3
+LOCAL_SRC_FILES += deps/sqlite/sqlite3.c
+
+# Threading
+LOCAL_SRC_FILES += \
+ jni/src/threading/event.cpp \
+ jni/src/threading/mutex.cpp \
+ jni/src/threading/semaphore.cpp \
+ jni/src/threading/thread.cpp
+
+# JSONCPP
+LOCAL_SRC_FILES += jni/lib/jsoncpp/jsoncpp.cpp
+
+LOCAL_SHARED_LIBRARIES := iconv openal ogg vorbis gmp
+LOCAL_STATIC_LIBRARIES := Irrlicht freetype curl ssl crypto android_native_app_glue $(PROFILER_LIBS)
+
+ifeq ($(HAVE_LEVELDB), 1)
+ LOCAL_STATIC_LIBRARIES += LevelDB
+endif
+LOCAL_LDLIBS := -lEGL -llog -lGLESv1_CM -lGLESv2 -lz -landroid
+
+include $(BUILD_SHARED_LIBRARY)
+
+# at the end of Android.mk
+ifdef GPROF
+$(call import-module,android-ndk-profiler)
+endif
+$(call import-module,android/native_app_glue)
diff --git a/build/android/jni/Application.mk b/build/android/jni/Application.mk
new file mode 100644
index 000000000..f6e2f6b22
--- /dev/null
+++ b/build/android/jni/Application.mk
@@ -0,0 +1,6 @@
+APP_PLATFORM := ${APP_PLATFORM}
+APP_ABI := ${TARGET_ABI}
+APP_STL := c++_static
+APP_MODULES := minetest
+
+APP_CPPFLAGS += -fexceptions -frtti
diff --git a/build/android/jni/Deps.mk b/build/android/jni/Deps.mk
new file mode 100644
index 000000000..6a58c5428
--- /dev/null
+++ b/build/android/jni/Deps.mk
@@ -0,0 +1,7 @@
+APP_PLATFORM := ${APP_PLATFORM}
+APP_ABI := ${TARGET_ABI}
+APP_STL := c++_static
+APP_DEPRECATED_HEADERS := true
+
+APP_CFLAGS += -mfloat-abi=softfp -mfpu=vfpv3 -O3
+APP_CPPFLAGS += -fexceptions
diff --git a/build/android/jni/Irrlicht.mk b/build/android/jni/Irrlicht.mk
new file mode 100644
index 000000000..b6a48acb8
--- /dev/null
+++ b/build/android/jni/Irrlicht.mk
@@ -0,0 +1,8 @@
+APP_PLATFORM := ${APP_PLATFORM}
+APP_ABI := ${TARGET_ABI}
+APP_STL := c++_static
+APP_DEPRECATED_HEADERS := true
+APP_MODULES := Irrlicht
+
+APP_CFLAGS += -mfloat-abi=softfp -mfpu=vfpv3 -O3
+APP_CPPFLAGS += -fexceptions
diff --git a/build/android/patches/irrlicht-back_button.patch b/build/android/patches/irrlicht-back_button.patch
new file mode 100644
index 000000000..e17b81347
--- /dev/null
+++ b/build/android/patches/irrlicht-back_button.patch
@@ -0,0 +1,20 @@
+--- irrlicht/source/Irrlicht/Android/CIrrDeviceAndroid.cpp.orig 2015-08-29 15:43:09.000000000 +0300
++++ irrlicht/source/Irrlicht/Android/CIrrDeviceAndroid.cpp 2016-05-13 21:36:22.880388505 +0300
+@@ -486,7 +486,7 @@
+ event.KeyInput.Char = 0;
+ }
+
+- device->postEventFromUser(event);
++ status = device->postEventFromUser(event);
+ }
+ break;
+ default:
+@@ -543,7 +543,7 @@
+ KeyMap[1] = KEY_LBUTTON; // AKEYCODE_SOFT_LEFT
+ KeyMap[2] = KEY_RBUTTON; // AKEYCODE_SOFT_RIGHT
+ KeyMap[3] = KEY_HOME; // AKEYCODE_HOME
+- KeyMap[4] = KEY_BACK; // AKEYCODE_BACK
++ KeyMap[4] = KEY_CANCEL; // AKEYCODE_BACK
+ KeyMap[5] = KEY_UNKNOWN; // AKEYCODE_CALL
+ KeyMap[6] = KEY_UNKNOWN; // AKEYCODE_ENDCALL
+ KeyMap[7] = KEY_KEY_0; // AKEYCODE_0
diff --git a/build/android/patches/irrlicht-native_activity.patch b/build/android/patches/irrlicht-native_activity.patch
new file mode 100644
index 000000000..83c837886
--- /dev/null
+++ b/build/android/patches/irrlicht-native_activity.patch
@@ -0,0 +1,13 @@
+--- irrlicht/source/Irrlicht/CEGLManager.cpp.orig 2018-06-10 16:58:11.357709173 +0200
++++ irrlicht/source/Irrlicht/CEGLManager.cpp 2018-06-10 16:58:25.100709843 +0200
+@@ -9,6 +9,10 @@
+ #include "irrString.h"
+ #include "os.h"
+
++#if defined(_IRR_COMPILE_WITH_ANDROID_DEVICE_)
++#include <android/native_activity.h>
++#endif
++
+ namespace irr
+ {
+ namespace video
diff --git a/build/android/patches/irrlicht-texturehack.patch b/build/android/patches/irrlicht-texturehack.patch
new file mode 100644
index 000000000..a458ede72
--- /dev/null
+++ b/build/android/patches/irrlicht-texturehack.patch
@@ -0,0 +1,240 @@
+--- irrlicht/source/Irrlicht/COGLESTexture.cpp.orig 2014-06-22 17:01:13.266568869 +0200
++++ irrlicht/source/Irrlicht/COGLESTexture.cpp 2014-06-22 17:03:59.298572810 +0200
+@@ -366,112 +366,140 @@
+ void(*convert)(const void*, s32, void*) = 0;
+ getFormatParameters(ColorFormat, InternalFormat, filtering, PixelFormat, PixelType, convert);
+
+- // make sure we don't change the internal format of existing images
+- if (!newTexture)
+- InternalFormat = oldInternalFormat;
+-
+- Driver->setActiveTexture(0, this);
+-
+- if (Driver->testGLError())
+- os::Printer::log("Could not bind Texture", ELL_ERROR);
+-
+- // mipmap handling for main texture
+- if (!level && newTexture)
+- {
+- // auto generate if possible and no mipmap data is given
+- if (!IsCompressed && HasMipMaps && !mipmapData && Driver->queryFeature(EVDF_MIP_MAP_AUTO_UPDATE))
+- {
+- if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED))
+- glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
+- else if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_QUALITY))
+- glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
+- else
+- glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE);
++ bool retry = false;
++
++ do {
++ if (retry) {
++ InternalFormat = GL_RGBA;
++ PixelFormat = GL_RGBA;
++ convert = CColorConverter::convert_A8R8G8B8toA8B8G8R8;
++ }
++ // make sure we don't change the internal format of existing images
++ if (!newTexture)
++ InternalFormat = oldInternalFormat;
+
+- glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
+- AutomaticMipmapUpdate=true;
+- }
++ Driver->setActiveTexture(0, this);
+
+- // enable bilinear filter without mipmaps
+- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
+- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
+- }
++ if (Driver->testGLError())
++ os::Printer::log("Could not bind Texture", ELL_ERROR);
+
+- // now get image data and upload to GPU
++ // mipmap handling for main texture
++ if (!level && newTexture)
++ {
++ // auto generate if possible and no mipmap data is given
++ if (!IsCompressed && HasMipMaps && !mipmapData && Driver->queryFeature(EVDF_MIP_MAP_AUTO_UPDATE))
++ {
++ if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_SPEED))
++ glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
++ else if (Driver->getTextureCreationFlag(ETCF_OPTIMIZED_FOR_QUALITY))
++ glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
++ else
++ glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE);
++
++ glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
++ AutomaticMipmapUpdate=true;
++ }
++
++ // enable bilinear filter without mipmaps
++ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
++ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
++ }
+
+- u32 compressedImageSize = IImage::getCompressedImageSize(ColorFormat, image->getDimension().Width, image->getDimension().Height);
++ // now get image data and upload to GPU
+
+- void* source = image->lock();
++ u32 compressedImageSize = IImage::getCompressedImageSize(ColorFormat, image->getDimension().Width, image->getDimension().Height);
+
+- IImage* tmpImage = 0;
++ void* source = image->lock();
+
+- if (convert)
+- {
+- tmpImage = new CImage(image->getColorFormat(), image->getDimension());
+- void* dest = tmpImage->lock();
+- convert(source, image->getDimension().getArea(), dest);
+- image->unlock();
+- source = dest;
+- }
++ IImage* tmpImage = 0;
+
+- if (newTexture)
+- {
+- if (IsCompressed)
++ if (convert)
+ {
+- glCompressedTexImage2D(GL_TEXTURE_2D, 0, InternalFormat, image->getDimension().Width,
+- image->getDimension().Height, 0, compressedImageSize, source);
++ tmpImage = new CImage(image->getColorFormat(), image->getDimension());
++ void* dest = tmpImage->lock();
++ convert(source, image->getDimension().getArea(), dest);
++ image->unlock();
++ source = dest;
+ }
+- else
+- glTexImage2D(GL_TEXTURE_2D, level, InternalFormat, image->getDimension().Width,
+- image->getDimension().Height, 0, PixelFormat, PixelType, source);
+- }
+- else
+- {
+- if (IsCompressed)
++
++ if (newTexture)
+ {
+- glCompressedTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width,
+- image->getDimension().Height, PixelFormat, compressedImageSize, source);
++ if (IsCompressed)
++ {
++ glCompressedTexImage2D(GL_TEXTURE_2D, 0, InternalFormat, image->getDimension().Width,
++ image->getDimension().Height, 0, compressedImageSize, source);
++ }
++ else
++ glTexImage2D(GL_TEXTURE_2D, level, InternalFormat, image->getDimension().Width,
++ image->getDimension().Height, 0, PixelFormat, PixelType, source);
+ }
+ else
+- glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width,
+- image->getDimension().Height, PixelFormat, PixelType, source);
+- }
+-
+- if (convert)
+- {
+- tmpImage->unlock();
+- tmpImage->drop();
+- }
+- else
+- image->unlock();
+-
+- if (!level && newTexture)
+- {
+- if (IsCompressed && !mipmapData)
+ {
+- if (image->hasMipMaps())
+- mipmapData = static_cast<u8*>(image->lock())+compressedImageSize;
++ if (IsCompressed)
++ {
++ glCompressedTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width,
++ image->getDimension().Height, PixelFormat, compressedImageSize, source);
++ }
+ else
+- HasMipMaps = false;
++ glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width,
++ image->getDimension().Height, PixelFormat, PixelType, source);
+ }
+
+- regenerateMipMapLevels(mipmapData);
+-
+- if (HasMipMaps) // might have changed in regenerateMipMapLevels
++ if (convert)
+ {
+- // enable bilinear mipmap filter
+- GLint filteringMipMaps = GL_LINEAR_MIPMAP_NEAREST;
+-
+- if (filtering != GL_LINEAR)
+- filteringMipMaps = GL_NEAREST_MIPMAP_NEAREST;
++ tmpImage->unlock();
++ tmpImage->drop();
++ }
++ else
++ image->unlock();
++
++ if (glGetError() != GL_NO_ERROR) {
++ static bool warned = false;
++ if ((!retry) && (ColorFormat == ECF_A8R8G8B8)) {
++
++ if (!warned) {
++ os::Printer::log("Your driver claims to support GL_BGRA but fails on trying to upload a texture, converting to GL_RGBA and trying again", ELL_ERROR);
++ warned = true;
++ }
++ }
++ else if (retry) {
++ os::Printer::log("Neither uploading texture as GL_BGRA nor, converted one using GL_RGBA succeeded", ELL_ERROR);
++ }
++ retry = !retry;
++ continue;
++ } else {
++ retry = false;
++ }
+
+- glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filteringMipMaps);
+- glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
++ if (!level && newTexture)
++ {
++ if (IsCompressed && !mipmapData)
++ {
++ if (image->hasMipMaps())
++ mipmapData = static_cast<u8*>(image->lock())+compressedImageSize;
++ else
++ HasMipMaps = false;
++ }
++
++ regenerateMipMapLevels(mipmapData);
++
++ if (HasMipMaps) // might have changed in regenerateMipMapLevels
++ {
++ // enable bilinear mipmap filter
++ GLint filteringMipMaps = GL_LINEAR_MIPMAP_NEAREST;
++
++ if (filtering != GL_LINEAR)
++ filteringMipMaps = GL_NEAREST_MIPMAP_NEAREST;
++
++ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filteringMipMaps);
++ glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
++ }
+ }
+- }
+
+- if (Driver->testGLError())
+- os::Printer::log("Could not glTexImage2D", ELL_ERROR);
++ if (Driver->testGLError())
++ os::Printer::log("Could not glTexImage2D", ELL_ERROR);
++ }
++ while(retry);
+ }
+
+
+--- irrlicht/source/Irrlicht/COGLESTexture.cpp.orig 2014-06-25 00:28:50.820501856 +0200
++++ irrlicht/source/Irrlicht/COGLESTexture.cpp 2014-06-25 00:08:37.712544692 +0200
+@@ -422,6 +422,9 @@
+ source = dest;
+ }
+
++ //clear old error
++ glGetError();
++
+ if (newTexture)
+ {
+ if (IsCompressed)
diff --git a/build/android/patches/irrlicht-touchcount.patch b/build/android/patches/irrlicht-touchcount.patch
new file mode 100644
index 000000000..d4e4b9c3e
--- /dev/null
+++ b/build/android/patches/irrlicht-touchcount.patch
@@ -0,0 +1,30 @@
+--- irrlicht.orig/include/IEventReceiver.h 2014-06-03 19:43:50.433713133 +0200
++++ irrlicht/include/IEventReceiver.h 2014-06-03 19:44:36.993711489 +0200
+@@ -375,6 +375,9 @@
+ // Y position of simple touch.
+ s32 Y;
+
++ // number of current touches
++ s32 touchedCount;
++
+ //! Type of touch event.
+ ETOUCH_INPUT_EVENT Event;
+ };
+--- irrlicht.orig/source/Irrlicht/Android/CIrrDeviceAndroid.cpp 2014-06-03 19:43:50.505713130 +0200
++++ irrlicht/source/Irrlicht/Android/CIrrDeviceAndroid.cpp 2014-06-03 19:45:37.265709359 +0200
+@@ -315,6 +315,7 @@
+ event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, i);
+ event.TouchInput.X = AMotionEvent_getX(androidEvent, i);
+ event.TouchInput.Y = AMotionEvent_getY(androidEvent, i);
++ event.TouchInput.touchedCount = AMotionEvent_getPointerCount(androidEvent);
+
+ device->postEventFromUser(event);
+ }
+@@ -326,6 +327,7 @@
+ event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, pointerIndex);
+ event.TouchInput.X = AMotionEvent_getX(androidEvent, pointerIndex);
+ event.TouchInput.Y = AMotionEvent_getY(androidEvent, pointerIndex);
++ event.TouchInput.touchedCount = AMotionEvent_getPointerCount(androidEvent);
+
+ device->postEventFromUser(event);
+ }
diff --git a/build/android/patches/libiconv_android.patch b/build/android/patches/libiconv_android.patch
new file mode 100644
index 000000000..4eca0a4ef
--- /dev/null
+++ b/build/android/patches/libiconv_android.patch
@@ -0,0 +1,39 @@
+--- a/libcharset/lib/localcharset.c 2015-06-10 11:55:25.933870724 +0200
++++ b/libcharset/lib/localcharset.c 2015-06-10 11:55:39.578063493 +0200
+@@ -47,7 +47,7 @@
+
+ #if !defined WIN32_NATIVE
+ # include <unistd.h>
+-# if HAVE_LANGINFO_CODESET
++# if HAVE_LANGINFO_CODESET && !(defined __ANDROID__)
+ # include <langinfo.h>
+ # else
+ # if 0 /* see comment below */
+@@ -124,7 +124,7 @@ get_charset_aliases (void)
+ cp = charset_aliases;
+ if (cp == NULL)
+ {
+-#if !(defined DARWIN7 || defined VMS || defined WIN32_NATIVE || defined __CYGWIN__)
++#if !(defined DARWIN7 || defined VMS || defined WIN32_NATIVE || defined __CYGWIN__ || defined __ANDROID__)
+ const char *dir;
+ const char *base = "charset.alias";
+ char *file_name;
+@@ -338,6 +338,9 @@ get_charset_aliases (void)
+ "CP54936" "\0" "GB18030" "\0"
+ "CP65001" "\0" "UTF-8" "\0";
+ # endif
++# if defined __ANDROID__
++ cp = "*" "\0" "UTF-8" "\0";
++# endif
+ #endif
+
+ charset_aliases = cp;
+@@ -361,7 +364,7 @@ locale_charset (void)
+ const char *codeset;
+ const char *aliases;
+
+-#if !(defined WIN32_NATIVE || defined OS2)
++#if !(defined WIN32_NATIVE || defined OS2 || defined __ANDROID__)
+
+ # if HAVE_LANGINFO_CODESET
+
diff --git a/build/android/patches/libiconv_stdio.patch b/build/android/patches/libiconv_stdio.patch
new file mode 100644
index 000000000..9fa50f79a
--- /dev/null
+++ b/build/android/patches/libiconv_stdio.patch
@@ -0,0 +1,13 @@
+--- a/srclib/stdio.in.h 2011-08-07 15:42:06.000000000 +0200
++++ b/srclib/stdio.in.h 2015-06-10 09:27:58.129056262 +0200
+@@ -695,8 +696,9 @@ _GL_CXXALIASWARN (gets);
+ /* It is very rare that the developer ever has full control of stdin,
+ so any use of gets warrants an unconditional warning. Assume it is
+ always declared, since it is required by C89. */
+-_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");
++/*_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");*/
++#define gets(a) fgets( a, sizeof(*(a)), stdin)
+ #endif
+
+
+#if @GNULIB_OBSTACK_PRINTF@ || @GNULIB_OBSTACK_PRINTF_POSIX@
diff --git a/build/android/patches/libvorbis-libogg-fpu.patch b/build/android/patches/libvorbis-libogg-fpu.patch
new file mode 100644
index 000000000..52ab397ac
--- /dev/null
+++ b/build/android/patches/libvorbis-libogg-fpu.patch
@@ -0,0 +1,37 @@
+--- libvorbis-libogg-android/jni/libvorbis-jni/Android.mk.orig 2014-06-17 19:22:50.621559073 +0200
++++ libvorbis-libogg-android/jni/libvorbis-jni/Android.mk 2014-06-17 19:38:20.641581140 +0200
+@@ -4,9 +4,6 @@
+
+ LOCAL_MODULE := vorbis-jni
+ LOCAL_CFLAGS += -I$(LOCAL_PATH)/../include -fsigned-char
+-ifeq ($(TARGET_ARCH),arm)
+- LOCAL_CFLAGS += -march=armv6 -marm -mfloat-abi=softfp -mfpu=vfp
+-endif
+
+ LOCAL_SHARED_LIBRARIES := libogg libvorbis
+
+--- libvorbis-libogg-android/jni/libvorbis/Android.mk.orig 2014-06-17 19:22:39.077558797 +0200
++++ libvorbis-libogg-android/jni/libvorbis/Android.mk 2014-06-17 19:38:52.121581887 +0200
+@@ -4,9 +4,6 @@
+
+ LOCAL_MODULE := libvorbis
+ LOCAL_CFLAGS += -I$(LOCAL_PATH)/../include -ffast-math -fsigned-char
+-ifeq ($(TARGET_ARCH),arm)
+- LOCAL_CFLAGS += -march=armv6 -marm -mfloat-abi=softfp -mfpu=vfp
+-endif
+ LOCAL_SHARED_LIBRARIES := libogg
+
+ LOCAL_SRC_FILES := \
+--- libvorbis-libogg-android/jni/libogg/Android.mk.orig 2014-06-17 19:22:33.965558675 +0200
++++ libvorbis-libogg-android/jni/libogg/Android.mk 2014-06-17 19:38:25.337581252 +0200
+@@ -4,10 +4,6 @@
+
+ LOCAL_MODULE := libogg
+ LOCAL_CFLAGS += -I$(LOCAL_PATH)/../include -ffast-math -fsigned-char
+-ifeq ($(TARGET_ARCH),arm)
+- LOCAL_CFLAGS += -march=armv6 -marm -mfloat-abi=softfp -mfpu=vfp
+-endif
+-
+
+ LOCAL_SRC_FILES := \
+ bitwise.c \
diff --git a/build/android/patches/openssl_arch.patch b/build/android/patches/openssl_arch.patch
new file mode 100644
index 000000000..d15e2b137
--- /dev/null
+++ b/build/android/patches/openssl_arch.patch
@@ -0,0 +1,13 @@
+--- openssl-1.0.2e.orig/Configure 2015-12-03 15:04:23.000000000 +0100
++++ openssl-1.0.2e/Configure 2015-12-14 21:01:40.351265968 +0100
+@@ -464,8 +464,10 @@
+ # Android: linux-* but without pointers to headers and libs.
+ "android","gcc:-mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -fomit-frame-pointer -Wall::-D_REENTRANT::-ldl:BN_LLONG RC4_CHAR RC4_CHUNK DES_INT DES_UNROLL BF_PTR:${no_asm}:dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)",
+ "android-x86","gcc:-mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -fomit-frame-pointer -Wall::-D_REENTRANT::-ldl:BN_LLONG ${x86_gcc_des} ${x86_gcc_opts}:".eval{my $asm=${x86_elf_asm};$asm=~s/:elf/:android/;$asm}.":dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)",
++"android-arm","gcc:-march=armv4 -mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -fomit-frame-pointer -Wall::-D_REENTRANT::-ldl:BN_LLONG RC4_CHAR RC4_CHUNK DES_INT DES_UNROLL BF_PTR:${armv4_asm}:dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)",
+ "android-armv7","gcc:-march=armv7-a -mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -fomit-frame-pointer -Wall::-D_REENTRANT::-ldl:BN_LLONG RC4_CHAR RC4_CHUNK DES_INT DES_UNROLL BF_PTR:${armv4_asm}:dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)",
+ "android-mips","gcc:-mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -Wall::-D_REENTRANT::-ldl:BN_LLONG RC4_CHAR RC4_CHUNK DES_INT DES_UNROLL BF_PTR:${mips32_asm}:o32:dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)",
++"android-mips32","gcc:-mandroid -I\$(ANDROID_DEV)/include -B\$(ANDROID_DEV)/lib -O3 -Wall::-D_REENTRANT::-ldl:BN_LLONG RC4_CHAR RC4_CHUNK DES_INT DES_UNROLL BF_PTR:${mips32_asm}:o32:dlfcn:linux-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)",
+
+ #### *BSD [do see comment about ${BSDthreads} above!]
+ "BSD-generic32","gcc:-O3 -fomit-frame-pointer -Wall::${BSDthreads}:::BN_LLONG RC2_CHAR RC4_INDEX DES_INT DES_UNROLL:${no_asm}:dlfcn:bsd-gcc-shared:-fPIC::.so.\$(SHLIB_MAJOR).\$(SHLIB_MINOR)",
diff --git a/build/android/settings.gradle b/build/android/settings.gradle
new file mode 100644
index 000000000..a6e60c439
--- /dev/null
+++ b/build/android/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = "Minetest"
+
diff --git a/build/android/src/debug/AndroidManifest.xml b/build/android/src/debug/AndroidManifest.xml
new file mode 100644
index 000000000..a3815b9f8
--- /dev/null
+++ b/build/android/src/debug/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-permission android:name="android.permission.SET_DEBUG_APP" />
+</manifest>
diff --git a/build/android/src/main/AndroidManifest.xml b/build/android/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..df218fb33
--- /dev/null
+++ b/build/android/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="net.minetest.minetest"
+ android:installLocation="auto">
+ <uses-feature android:glEsVersion="0x00010000" android:required="true"/>
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <application android:icon="@drawable/irr_icon"
+ android:label="${project}"
+ android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+ android:allowBackup="true">
+ <activity android:name=".MtNativeActivity"
+ android:label="${project}"
+ android:launchMode="singleTask"
+ android:configChanges="orientation|keyboard|keyboardHidden|navigation"
+ android:screenOrientation="sensorLandscape"
+ android:clearTaskOnLaunch="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <meta-data android:name="android.app.lib_name" android:value="minetest" />
+ </activity>
+ <activity android:name=".MinetestTextEntry"
+ android:theme="@style/Theme.Transparent"
+ android:excludeFromRecents="true">
+ </activity>
+ <activity android:name=".MinetestAssetCopy"
+ android:theme="@style/Theme.Transparent"
+ android:excludeFromRecents="true">
+ </activity>
+ </application>
+</manifest>
diff --git a/build/android/src/main/java/net.minetest.minetest/MinetestAssetCopy.java b/build/android/src/main/java/net.minetest.minetest/MinetestAssetCopy.java
new file mode 100644
index 000000000..eb92acb63
--- /dev/null
+++ b/build/android/src/main/java/net.minetest.minetest/MinetestAssetCopy.java
@@ -0,0 +1,416 @@
+package net.minetest.minetest;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.util.Vector;
+import java.util.Iterator;
+import java.lang.Object;
+
+import android.app.Activity;
+import android.content.res.AssetFileDescriptor;
+
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+import android.view.Display;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.graphics.Rect;
+import android.graphics.Paint;
+import android.text.TextPaint;
+
+public class MinetestAssetCopy extends Activity
+{
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.assetcopy);
+
+ m_ProgressBar = (ProgressBar) findViewById(R.id.progressBar1);
+ m_Filename = (TextView) findViewById(R.id.textView1);
+
+ Display display = getWindowManager().getDefaultDisplay();
+ m_ProgressBar.getLayoutParams().width = (int) (display.getWidth() * 0.8);
+ m_ProgressBar.invalidate();
+
+ /* check if there's already a copy in progress and reuse in case it is*/
+ MinetestAssetCopy prevActivity =
+ (MinetestAssetCopy) getLastNonConfigurationInstance();
+ if(prevActivity!= null) {
+ m_AssetCopy = prevActivity.m_AssetCopy;
+ }
+ else {
+ m_AssetCopy = new copyAssetTask();
+ m_AssetCopy.execute();
+ }
+ }
+
+ /* preserve asset copy background task to prevent restart of copying */
+ /* this way of doing it is not recommended for latest android version */
+ /* but the recommended way isn't available on android 2.x */
+ public Object onRetainNonConfigurationInstance()
+ {
+ return this;
+ }
+
+ ProgressBar m_ProgressBar;
+ TextView m_Filename;
+
+ copyAssetTask m_AssetCopy;
+
+ private class copyAssetTask extends AsyncTask<String, Integer, String>
+ {
+ private long getFullSize(String filename)
+ {
+ long size = 0;
+ try {
+ InputStream src = getAssets().open(filename);
+ byte[] buf = new byte[4096];
+
+ int len = 0;
+ while ((len = src.read(buf)) > 0)
+ {
+ size += len;
+ }
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ return size;
+ }
+
+ @Override
+ protected String doInBackground(String... files)
+ {
+ m_foldernames = new Vector<String>();
+ m_filenames = new Vector<String>();
+ m_tocopy = new Vector<String>();
+ m_asset_size_unknown = new Vector<String>();
+ String baseDir =
+ Environment.getExternalStorageDirectory().getAbsolutePath()
+ + "/";
+
+
+ // prepare temp folder
+ File TempFolder = new File(baseDir + "Minetest/tmp/");
+
+ if (!TempFolder.exists())
+ {
+ TempFolder.mkdir();
+ }
+ else {
+ File[] todel = TempFolder.listFiles();
+
+ for(int i=0; i < todel.length; i++)
+ {
+ Log.v("MinetestAssetCopy","deleting: " + todel[i].getAbsolutePath());
+ todel[i].delete();
+ }
+ }
+
+ // add a .nomedia file
+ try {
+ OutputStream dst = new FileOutputStream(baseDir + "Minetest/.nomedia");
+ dst.close();
+ } catch (IOException e) {
+ Log.e("MinetestAssetCopy","Failed to create .nomedia file");
+ e.printStackTrace();
+ }
+
+
+ // build lists from prepared data
+ BuildFolderList();
+ BuildFileList();
+
+ // scan filelist
+ ProcessFileList();
+
+ // doing work
+ m_copy_started = true;
+ m_ProgressBar.setMax(m_tocopy.size());
+
+ for (int i = 0; i < m_tocopy.size(); i++)
+ {
+ try
+ {
+ String filename = m_tocopy.get(i);
+ publishProgress(i);
+
+ boolean asset_size_unknown = false;
+ long filesize = -1;
+
+ if (m_asset_size_unknown.contains(filename))
+ {
+ File testme = new File(baseDir + "/" + filename);
+
+ if(testme.exists())
+ {
+ filesize = testme.length();
+ }
+ asset_size_unknown = true;
+ }
+
+ InputStream src;
+ try
+ {
+ src = getAssets().open(filename);
+ } catch (IOException e) {
+ Log.e("MinetestAssetCopy","Copying file: " + filename + " FAILED (not in assets)");
+ e.printStackTrace();
+ continue;
+ }
+
+ // Transfer bytes from in to out
+ byte[] buf = new byte[1*1024];
+ int len = src.read(buf, 0, 1024);
+
+ /* following handling is crazy but we need to deal with */
+ /* compressed assets.Flash chips limited livetime due to */
+ /* write operations, we can't allow large files to destroy */
+ /* users flash. */
+ if (asset_size_unknown)
+ {
+ if ( (len > 0) && (len < buf.length) && (len == filesize))
+ {
+ src.close();
+ continue;
+ }
+
+ if (len == buf.length)
+ {
+ src.close();
+ long size = getFullSize(filename);
+ if ( size == filesize)
+ {
+ continue;
+ }
+ src = getAssets().open(filename);
+ len = src.read(buf, 0, 1024);
+ }
+ }
+ if (len > 0)
+ {
+ int total_filesize = 0;
+ OutputStream dst;
+ try
+ {
+ dst = new FileOutputStream(baseDir + "/" + filename);
+ } catch (IOException e) {
+ Log.e("MinetestAssetCopy","Copying file: " + baseDir +
+ "/" + filename + " FAILED (couldn't open output file)");
+ e.printStackTrace();
+ src.close();
+ continue;
+ }
+ dst.write(buf, 0, len);
+ total_filesize += len;
+
+ while ((len = src.read(buf)) > 0)
+ {
+ dst.write(buf, 0, len);
+ total_filesize += len;
+ }
+
+ dst.close();
+ Log.v("MinetestAssetCopy","Copied file: " +
+ m_tocopy.get(i) + " (" + total_filesize +
+ " bytes)");
+ }
+ else if (len < 0)
+ {
+ Log.e("MinetestAssetCopy","Copying file: " +
+ m_tocopy.get(i) + " failed, size < 0");
+ }
+ src.close();
+ }
+ catch (IOException e)
+ {
+ Log.e("MinetestAssetCopy","Copying file: " +
+ m_tocopy.get(i) + " failed");
+ e.printStackTrace();
+ }
+ }
+ return "";
+ }
+
+
+ /**
+ * update progress bar
+ */
+ protected void onProgressUpdate(Integer... progress)
+ {
+
+ if (m_copy_started)
+ {
+ boolean shortened = false;
+ String todisplay = m_tocopy.get(progress[0]);
+ m_ProgressBar.setProgress(progress[0]);
+ m_Filename.setText(todisplay);
+ }
+ else
+ {
+ boolean shortened = false;
+ String todisplay = m_Foldername;
+ String full_text = "scanning " + todisplay + " ...";
+ m_Filename.setText(full_text);
+ }
+ }
+
+ /**
+ * check al files and folders in filelist
+ */
+ protected void ProcessFileList()
+ {
+ String FlashBaseDir =
+ Environment.getExternalStorageDirectory().getAbsolutePath();
+
+ Iterator itr = m_filenames.iterator();
+
+ while (itr.hasNext())
+ {
+ String current_path = (String) itr.next();
+ String FlashPath = FlashBaseDir + "/" + current_path;
+
+ if (isAssetFolder(current_path))
+ {
+ /* store information and update gui */
+ m_Foldername = current_path;
+ publishProgress(0);
+
+ /* open file in order to check if it's a folder */
+ File current_folder = new File(FlashPath);
+ if (!current_folder.exists())
+ {
+ if (!current_folder.mkdirs())
+ {
+ Log.e("MinetestAssetCopy","\t failed create folder: " +
+ FlashPath);
+ }
+ else
+ {
+ Log.v("MinetestAssetCopy","\t created folder: " +
+ FlashPath);
+ }
+ }
+
+ continue;
+ }
+
+ /* if it's not a folder it's most likely a file */
+ boolean refresh = true;
+
+ File testme = new File(FlashPath);
+
+ long asset_filesize = -1;
+ long stored_filesize = -1;
+
+ if (testme.exists())
+ {
+ try
+ {
+ AssetFileDescriptor fd = getAssets().openFd(current_path);
+ asset_filesize = fd.getLength();
+ fd.close();
+ }
+ catch (IOException e)
+ {
+ refresh = true;
+ m_asset_size_unknown.add(current_path);
+ Log.e("MinetestAssetCopy","Failed to open asset file \"" +
+ FlashPath + "\" for size check");
+ }
+
+ stored_filesize = testme.length();
+
+ if (asset_filesize == stored_filesize)
+ {
+ refresh = false;
+ }
+
+ }
+
+ if (refresh)
+ {
+ m_tocopy.add(current_path);
+ }
+ }
+ }
+
+ /**
+ * read list of folders prepared on package build
+ */
+ protected void BuildFolderList()
+ {
+ try
+ {
+ InputStream is = getAssets().open("index.txt");
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+
+ String line = reader.readLine();
+ while (line != null)
+ {
+ m_foldernames.add(line);
+ line = reader.readLine();
+ }
+ is.close();
+ } catch (IOException e1)
+ {
+ Log.e("MinetestAssetCopy","Error on processing index.txt");
+ e1.printStackTrace();
+ }
+ }
+
+ /**
+ * read list of asset files prepared on package build
+ */
+ protected void BuildFileList()
+ {
+ long entrycount = 0;
+ try
+ {
+ InputStream is = getAssets().open("filelist.txt");
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+
+ String line = reader.readLine();
+ while (line != null)
+ {
+ m_filenames.add(line);
+ line = reader.readLine();
+ entrycount ++;
+ }
+ is.close();
+ }
+ catch (IOException e1)
+ {
+ Log.e("MinetestAssetCopy","Error on processing filelist.txt");
+ e1.printStackTrace();
+ }
+ }
+
+ protected void onPostExecute (String result)
+ {
+ finish();
+ }
+
+ protected boolean isAssetFolder(String path)
+ {
+ return m_foldernames.contains(path);
+ }
+
+ boolean m_copy_started = false;
+ String m_Foldername = "media";
+ Vector<String> m_foldernames;
+ Vector<String> m_filenames;
+ Vector<String> m_tocopy;
+ Vector<String> m_asset_size_unknown;
+ }
+}
diff --git a/build/android/src/main/java/net.minetest.minetest/MinetestTextEntry.java b/build/android/src/main/java/net.minetest.minetest/MinetestTextEntry.java
new file mode 100644
index 000000000..68dc73274
--- /dev/null
+++ b/build/android/src/main/java/net.minetest.minetest/MinetestTextEntry.java
@@ -0,0 +1,91 @@
+package net.minetest.minetest;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.InputType;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnKeyListener;
+import android.widget.EditText;
+
+public class MinetestTextEntry extends Activity {
+ public AlertDialog mTextInputDialog;
+ public EditText mTextInputWidget;
+
+ private final int MultiLineTextInput = 1;
+ private final int SingleLineTextInput = 2;
+ private final int SingleLinePasswordInput = 3;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle b = getIntent().getExtras();
+ String acceptButton = b.getString("EnterButton");
+ String hint = b.getString("hint");
+ String current = b.getString("current");
+ int editType = b.getInt("editType");
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ mTextInputWidget = new EditText(this);
+ mTextInputWidget.setHint(hint);
+ mTextInputWidget.setText(current);
+ mTextInputWidget.setMinWidth(300);
+ if (editType == SingleLinePasswordInput) {
+ mTextInputWidget.setInputType(InputType.TYPE_CLASS_TEXT |
+ InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ }
+ else {
+ mTextInputWidget.setInputType(InputType.TYPE_CLASS_TEXT);
+ }
+
+
+ builder.setView(mTextInputWidget);
+
+ if (editType == MultiLineTextInput) {
+ builder.setPositiveButton(acceptButton, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton)
+ { pushResult(mTextInputWidget.getText().toString()); }
+ });
+ }
+
+ builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ cancelDialog();
+ }
+ });
+
+ mTextInputWidget.setOnKeyListener(new OnKeyListener() {
+ @Override
+ public boolean onKey(View view, int KeyCode, KeyEvent event) {
+ if ( KeyCode == KeyEvent.KEYCODE_ENTER){
+
+ pushResult(mTextInputWidget.getText().toString());
+ return true;
+ }
+ return false;
+ }
+ });
+
+ mTextInputDialog = builder.create();
+ mTextInputDialog.show();
+ }
+
+ public void pushResult(String text) {
+ Intent resultData = new Intent();
+ resultData.putExtra("text", text);
+ setResult(Activity.RESULT_OK,resultData);
+ mTextInputDialog.dismiss();
+ finish();
+ }
+
+ public void cancelDialog() {
+ setResult(Activity.RESULT_CANCELED);
+ mTextInputDialog.dismiss();
+ finish();
+ }
+}
diff --git a/build/android/src/main/java/net.minetest.minetest/MtNativeActivity.java b/build/android/src/main/java/net.minetest.minetest/MtNativeActivity.java
new file mode 100644
index 000000000..159521a50
--- /dev/null
+++ b/build/android/src/main/java/net.minetest.minetest/MtNativeActivity.java
@@ -0,0 +1,99 @@
+package net.minetest.minetest;
+
+import android.app.NativeActivity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowManager;
+
+public class MtNativeActivity extends NativeActivity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ m_MessagReturnCode = -1;
+ m_MessageReturnValue = "";
+
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+
+ public void copyAssets() {
+ Intent intent = new Intent(this, MinetestAssetCopy.class);
+ startActivity(intent);
+ }
+
+ public void showDialog(String acceptButton, String hint, String current,
+ int editType) {
+
+ Intent intent = new Intent(this, MinetestTextEntry.class);
+ Bundle params = new Bundle();
+ params.putString("acceptButton", acceptButton);
+ params.putString("hint", hint);
+ params.putString("current", current);
+ params.putInt("editType", editType);
+ intent.putExtras(params);
+ startActivityForResult(intent, 101);
+ m_MessageReturnValue = "";
+ m_MessagReturnCode = -1;
+ }
+
+ public static native void putMessageBoxResult(String text);
+
+ /* ugly code to workaround putMessageBoxResult not beeing found */
+ public int getDialogState() {
+ return m_MessagReturnCode;
+ }
+
+ public String getDialogValue() {
+ m_MessagReturnCode = -1;
+ return m_MessageReturnValue;
+ }
+
+ public float getDensity() {
+ return getResources().getDisplayMetrics().density;
+ }
+
+ public int getDisplayWidth() {
+ return getResources().getDisplayMetrics().widthPixels;
+ }
+
+ public int getDisplayHeight() {
+ return getResources().getDisplayMetrics().heightPixels;
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode,
+ Intent data) {
+ if (requestCode == 101) {
+ if (resultCode == RESULT_OK) {
+ String text = data.getStringExtra("text");
+ m_MessagReturnCode = 0;
+ m_MessageReturnValue = text;
+ }
+ else {
+ m_MessagReturnCode = 1;
+ }
+ }
+ }
+
+ static {
+ System.loadLibrary("openal");
+ System.loadLibrary("ogg");
+ System.loadLibrary("vorbis");
+ System.loadLibrary("ssl");
+ System.loadLibrary("crypto");
+ System.loadLibrary("gmp");
+ System.loadLibrary("iconv");
+
+ // We don't have to load libminetest.so ourselves,
+ // but if we do, we get nicer logcat errors when
+ // loading fails.
+ System.loadLibrary("minetest");
+ }
+
+ private int m_MessagReturnCode;
+ private String m_MessageReturnValue;
+}
diff --git a/build/android/src/main/res/drawable-hdpi/irr_icon.png b/build/android/src/main/res/drawable-hdpi/irr_icon.png
new file mode 100644
index 000000000..0b6861a0d
--- /dev/null
+++ b/build/android/src/main/res/drawable-hdpi/irr_icon.png
Binary files differ
diff --git a/build/android/src/main/res/drawable-ldpi/irr_icon.png b/build/android/src/main/res/drawable-ldpi/irr_icon.png
new file mode 100644
index 000000000..b8c5d0177
--- /dev/null
+++ b/build/android/src/main/res/drawable-ldpi/irr_icon.png
Binary files differ
diff --git a/build/android/src/main/res/drawable-mdpi/irr_icon.png b/build/android/src/main/res/drawable-mdpi/irr_icon.png
new file mode 100644
index 000000000..951a7f8c1
--- /dev/null
+++ b/build/android/src/main/res/drawable-mdpi/irr_icon.png
Binary files differ
diff --git a/build/android/src/main/res/drawable-xhdpi/irr_icon.png b/build/android/src/main/res/drawable-xhdpi/irr_icon.png
new file mode 100644
index 000000000..2ec528ef7
--- /dev/null
+++ b/build/android/src/main/res/drawable-xhdpi/irr_icon.png
Binary files differ
diff --git a/build/android/src/main/res/layout/assetcopy.xml b/build/android/src/main/res/layout/assetcopy.xml
new file mode 100644
index 000000000..1fcfffd65
--- /dev/null
+++ b/build/android/src/main/res/layout/assetcopy.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <ProgressBar
+ android:id="@+id/progressBar1"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical" />
+
+ <TextView
+ android:id="@+id/textView1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="middle"
+ android:singleLine="true"
+ android:layout_gravity="center_horizontal"
+ android:text="@string/preparing_media"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+</LinearLayout>
diff --git a/build/android/src/main/res/values/strings.xml b/build/android/src/main/res/values/strings.xml
new file mode 100644
index 000000000..b407a77c6
--- /dev/null
+++ b/build/android/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="preparing_media">Preparing media...</string>
+</resources>
+
diff --git a/build/android/src/main/res/values/styles.xml b/build/android/src/main/res/values/styles.xml
new file mode 100644
index 000000000..25b8df5a3
--- /dev/null
+++ b/build/android/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="Theme.Transparent" parent="android:Theme">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowIsFloating">true</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/builtin/async/init.lua b/builtin/async/init.lua
new file mode 100644
index 000000000..1b2549685
--- /dev/null
+++ b/builtin/async/init.lua
@@ -0,0 +1,17 @@
+
+core.log("info", "Initializing Asynchronous environment")
+
+function core.job_processor(serialized_func, serialized_param)
+ local func = loadstring(serialized_func)
+ local param = core.deserialize(serialized_param)
+ local retval = nil
+
+ if type(func) == "function" then
+ retval = core.serialize(func(param))
+ else
+ core.log("error", "ASYNC WORKER: Unable to deserialize function")
+ end
+
+ return retval or core.serialize(nil)
+end
+
diff --git a/builtin/client/chatcommands.lua b/builtin/client/chatcommands.lua
new file mode 100644
index 000000000..2b8cc4acd
--- /dev/null
+++ b/builtin/client/chatcommands.lua
@@ -0,0 +1,65 @@
+-- Minetest: builtin/client/chatcommands.lua
+
+
+core.register_on_sending_chat_messages(function(message)
+ if message:sub(1,2) == ".." then
+ return false
+ end
+
+ local first_char = message:sub(1,1)
+ if first_char == "/" or first_char == "." then
+ core.display_chat_message(core.gettext("issued command: ") .. message)
+ end
+
+ if first_char ~= "." then
+ return false
+ end
+
+ local cmd, param = string.match(message, "^%.([^ ]+) *(.*)")
+ param = param or ""
+
+ if not cmd then
+ core.display_chat_message(core.gettext("-!- Empty command"))
+ return true
+ end
+
+ local cmd_def = core.registered_chatcommands[cmd]
+ if cmd_def then
+ core.set_last_run_mod(cmd_def.mod_origin)
+ local _, message = cmd_def.func(param)
+ if message then
+ core.display_chat_message(message)
+ end
+ else
+ core.display_chat_message(core.gettext("-!- Invalid command: ") .. cmd)
+ end
+
+ return true
+end)
+
+core.register_chatcommand("list_players", {
+ description = core.gettext("List online players"),
+ func = function(param)
+ local players = table.concat(core.get_player_names(), ", ")
+ core.display_chat_message(core.gettext("Online players: ") .. players)
+ end
+})
+
+core.register_chatcommand("disconnect", {
+ description = core.gettext("Exit to main menu"),
+ func = function(param)
+ core.disconnect()
+ end,
+})
+
+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")
+ end,
+})
+
+function core.run_server_chatcommand(cmd, param)
+ core.send_chat_message("/" .. cmd .. " " .. param)
+end
diff --git a/builtin/client/init.lua b/builtin/client/init.lua
new file mode 100644
index 000000000..3ac34d845
--- /dev/null
+++ b/builtin/client/init.lua
@@ -0,0 +1,23 @@
+-- Minetest: builtin/client/init.lua
+local scriptpath = core.get_builtin_path()..DIR_DELIM
+local clientpath = scriptpath.."client"..DIR_DELIM
+local commonpath = scriptpath.."common"..DIR_DELIM
+
+dofile(clientpath .. "register.lua")
+dofile(commonpath .. "after.lua")
+dofile(commonpath .. "chatcommands.lua")
+dofile(clientpath .. "chatcommands.lua")
+dofile(commonpath .. "vector.lua")
+
+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") .."]"
+ core.show_formspec("bultin:death", formspec)
+end)
+
+core.register_on_formspec_input(function(formname, fields)
+ if formname == "bultin:death" then
+ core.send_respawn()
+ end
+end)
diff --git a/builtin/client/register.lua b/builtin/client/register.lua
new file mode 100644
index 000000000..6b12ddec8
--- /dev/null
+++ b/builtin/client/register.lua
@@ -0,0 +1,73 @@
+
+core.callback_origins = {}
+
+local getinfo = debug.getinfo
+debug.getinfo = nil
+
+function core.run_callbacks(callbacks, mode, ...)
+ assert(type(callbacks) == "table")
+ local cb_len = #callbacks
+ if cb_len == 0 then
+ if mode == 2 or mode == 3 then
+ return true
+ elseif mode == 4 or mode == 5 then
+ return false
+ end
+ end
+ local ret
+ for i = 1, cb_len do
+ local cb_ret = callbacks[i](...)
+
+ if mode == 0 and i == 1 or mode == 1 and i == cb_len then
+ ret = cb_ret
+ elseif mode == 2 then
+ if not cb_ret or i == 1 then
+ ret = cb_ret
+ end
+ elseif mode == 3 then
+ if cb_ret then
+ return cb_ret
+ end
+ ret = cb_ret
+ elseif mode == 4 then
+ if (cb_ret and not ret) or i == 1 then
+ ret = cb_ret
+ end
+ elseif mode == 5 and cb_ret then
+ return cb_ret
+ end
+ end
+ return ret
+end
+
+--
+-- Callback registration
+--
+
+local function make_registration()
+ local t = {}
+ local registerfunc = function(func)
+ t[#t + 1] = func
+ core.callback_origins[func] = {
+ mod = core.get_current_modname() or "??",
+ name = getinfo(1, "n").name or "??"
+ }
+ --local origin = core.callback_origins[func]
+ --print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func))
+ end
+ return t, registerfunc
+end
+
+core.registered_globalsteps, core.register_globalstep = make_registration()
+core.registered_on_shutdown, core.register_on_shutdown = make_registration()
+core.registered_on_connect, core.register_on_connect = make_registration()
+core.registered_on_receiving_chat_messages, core.register_on_receiving_chat_messages = make_registration()
+core.registered_on_sending_chat_messages, core.register_on_sending_chat_messages = make_registration()
+core.registered_on_death, core.register_on_death = make_registration()
+core.registered_on_hp_modification, core.register_on_hp_modification = make_registration()
+core.registered_on_damage_taken, core.register_on_damage_taken = make_registration()
+core.registered_on_formspec_input, core.register_on_formspec_input = make_registration()
+core.registered_on_dignode, core.register_on_dignode = make_registration()
+core.registered_on_punchnode, core.register_on_punchnode = make_registration()
+core.registered_on_placenode, core.register_on_placenode = make_registration()
+core.registered_on_item_use, core.register_on_item_use = make_registration()
diff --git a/builtin/common/after.lua b/builtin/common/after.lua
new file mode 100644
index 000000000..cdfaaab86
--- /dev/null
+++ b/builtin/common/after.lua
@@ -0,0 +1,33 @@
+local jobs = {}
+local time = 0.0
+
+core.register_globalstep(function(dtime)
+ time = time + dtime
+
+ if #jobs < 1 then
+ return
+ end
+
+ -- Iterate backwards so that we miss any new timers added by
+ -- a timer callback, and so that we don't skip the next timer
+ -- in the list if we remove one.
+ for i = #jobs, 1, -1 do
+ local job = jobs[i]
+ if time >= job.expire then
+ core.set_last_run_mod(job.mod_origin)
+ job.func(unpack(job.arg))
+ table.remove(jobs, i)
+ end
+ end
+end)
+
+function core.after(after, func, ...)
+ assert(tonumber(after) and type(func) == "function",
+ "Invalid core.after invocation")
+ jobs[#jobs + 1] = {
+ func = func,
+ expire = time + after,
+ arg = {...},
+ mod_origin = core.get_last_run_mod()
+ }
+end
diff --git a/builtin/common/async_event.lua b/builtin/common/async_event.lua
new file mode 100644
index 000000000..988af79b9
--- /dev/null
+++ b/builtin/common/async_event.lua
@@ -0,0 +1,40 @@
+
+core.async_jobs = {}
+
+local function handle_job(jobid, serialized_retval)
+ local retval = core.deserialize(serialized_retval)
+ assert(type(core.async_jobs[jobid]) == "function")
+ core.async_jobs[jobid](retval)
+ core.async_jobs[jobid] = nil
+end
+
+if core.register_globalstep then
+ core.register_globalstep(function(dtime)
+ for i, job in ipairs(core.get_finished_jobs()) do
+ handle_job(job.jobid, job.retval)
+ end
+ end)
+else
+ core.async_event_handler = handle_job
+end
+
+function core.handle_async(func, parameter, callback)
+ -- Serialize function
+ local serialized_func = string.dump(func)
+
+ assert(serialized_func ~= nil)
+
+ -- Serialize parameters
+ local serialized_param = core.serialize(parameter)
+
+ if serialized_param == nil then
+ return false
+ end
+
+ local jobid = core.do_async_callback(serialized_func, serialized_param)
+
+ core.async_jobs[jobid] = callback
+
+ return true
+end
+
diff --git a/builtin/common/chatcommands.lua b/builtin/common/chatcommands.lua
new file mode 100644
index 000000000..e8955c6b4
--- /dev/null
+++ b/builtin/common/chatcommands.lua
@@ -0,0 +1,112 @@
+-- Minetest: builtin/common/chatcommands.lua
+
+core.registered_chatcommands = {}
+
+function core.register_chatcommand(cmd, def)
+ def = def or {}
+ def.params = def.params or ""
+ def.description = def.description or ""
+ def.privs = def.privs or {}
+ def.mod_origin = core.get_current_modname() or "??"
+ core.registered_chatcommands[cmd] = def
+end
+
+function core.unregister_chatcommand(name)
+ if core.registered_chatcommands[name] then
+ core.registered_chatcommands[name] = nil
+ else
+ core.log("warning", "Not unregistering chatcommand " ..name..
+ " because it doesn't exist.")
+ end
+end
+
+function core.override_chatcommand(name, redefinition)
+ local chatcommand = core.registered_chatcommands[name]
+ assert(chatcommand, "Attempt to override non-existent chatcommand "..name)
+ for k, v in pairs(redefinition) do
+ rawset(chatcommand, k, v)
+ end
+ core.registered_chatcommands[name] = chatcommand
+end
+
+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
+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
+ end
+ if param == "" then
+ local cmds = {}
+ for cmd, def in pairs(core.registered_chatcommands) do
+ if INIT == "client" or core.check_player_privs(name, def.privs) then
+ cmds[#cmds + 1] = cmd
+ 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 cmds = {}
+ for cmd, def in pairs(core.registered_chatcommands) do
+ if INIT == "client" or core.check_player_privs(name, def.privs) then
+ cmds[#cmds + 1] = format_help_line(cmd, def)
+ end
+ end
+ table.sort(cmds)
+ return true, gettext("Available commands:").."\n"..table.concat(cmds, "\n")
+ elseif INIT == "game" and param == "privs" then
+ local privs = {}
+ for priv, def in pairs(core.registered_privileges) do
+ privs[#privs + 1] = priv .. ": " .. def.description
+ end
+ table.sort(privs)
+ return true, "Available privileges:\n"..table.concat(privs, "\n")
+ else
+ local cmd = param
+ local def = core.registered_chatcommands[cmd]
+ if not def then
+ return false, gettext("Command not available: ")..cmd
+ else
+ return true, format_help_line(cmd, def)
+ end
+ end
+end
+
+if INIT == "client" then
+ core.register_chatcommand("help", {
+ params = gettext("[all/<cmd>]"),
+ description = 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",
+ func = do_help_cmd,
+ })
+end
diff --git a/builtin/common/filterlist.lua b/builtin/common/filterlist.lua
new file mode 100644
index 000000000..562231192
--- /dev/null
+++ b/builtin/common/filterlist.lua
@@ -0,0 +1,320 @@
+--Minetest
+--Copyright (C) 2013 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+--------------------------------------------------------------------------------
+-- TODO improve doc --
+-- TODO code cleanup --
+-- Generic implementation of a filter/sortable list --
+-- Usage: --
+-- Filterlist needs to be initialized on creation. To achieve this you need to --
+-- pass following functions: --
+-- raw_fct() (mandatory): --
+-- function returning a table containing the elements to be filtered --
+-- compare_fct(element1,element2) (mandatory): --
+-- function returning true/false if element1 is same element as element2 --
+-- uid_match_fct(element1,uid) (optional) --
+-- function telling if uid is attached to element1 --
+-- filter_fct(element,filtercriteria) (optional) --
+-- function returning true/false if filtercriteria met to element --
+-- fetch_param (optional) --
+-- parameter passed to raw_fct to aquire correct raw data --
+-- --
+--------------------------------------------------------------------------------
+filterlist = {}
+
+--------------------------------------------------------------------------------
+function filterlist.refresh(self)
+ self.m_raw_list = self.m_raw_list_fct(self.m_fetch_param)
+ filterlist.process(self)
+end
+
+--------------------------------------------------------------------------------
+function filterlist.create(raw_fct,compare_fct,uid_match_fct,filter_fct,fetch_param)
+
+ assert((raw_fct ~= nil) and (type(raw_fct) == "function"))
+ assert((compare_fct ~= nil) and (type(compare_fct) == "function"))
+
+ local self = {}
+
+ self.m_raw_list_fct = raw_fct
+ self.m_compare_fct = compare_fct
+ self.m_filter_fct = filter_fct
+ self.m_uid_match_fct = uid_match_fct
+
+ self.m_filtercriteria = nil
+ self.m_fetch_param = fetch_param
+
+ self.m_sortmode = "none"
+ self.m_sort_list = {}
+
+ self.m_processed_list = nil
+ self.m_raw_list = self.m_raw_list_fct(self.m_fetch_param)
+
+ self.add_sort_mechanism = filterlist.add_sort_mechanism
+ self.set_filtercriteria = filterlist.set_filtercriteria
+ self.get_filtercriteria = filterlist.get_filtercriteria
+ self.set_sortmode = filterlist.set_sortmode
+ self.get_list = filterlist.get_list
+ self.get_raw_list = filterlist.get_raw_list
+ self.get_raw_element = filterlist.get_raw_element
+ self.get_raw_index = filterlist.get_raw_index
+ self.get_current_index = filterlist.get_current_index
+ self.size = filterlist.size
+ self.uid_exists_raw = filterlist.uid_exists_raw
+ self.raw_index_by_uid = filterlist.raw_index_by_uid
+ self.refresh = filterlist.refresh
+
+ filterlist.process(self)
+
+ return self
+end
+
+--------------------------------------------------------------------------------
+function filterlist.add_sort_mechanism(self,name,fct)
+ self.m_sort_list[name] = fct
+end
+
+--------------------------------------------------------------------------------
+function filterlist.set_filtercriteria(self,criteria)
+ if criteria == self.m_filtercriteria and
+ type(criteria) ~= "table" then
+ return
+ end
+ self.m_filtercriteria = criteria
+ filterlist.process(self)
+end
+
+--------------------------------------------------------------------------------
+function filterlist.get_filtercriteria(self)
+ return self.m_filtercriteria
+end
+
+--------------------------------------------------------------------------------
+--supported sort mode "alphabetic|none"
+function filterlist.set_sortmode(self,mode)
+ if (mode == self.m_sortmode) then
+ return
+ end
+ self.m_sortmode = mode
+ filterlist.process(self)
+end
+
+--------------------------------------------------------------------------------
+function filterlist.get_list(self)
+ return self.m_processed_list
+end
+
+--------------------------------------------------------------------------------
+function filterlist.get_raw_list(self)
+ return self.m_raw_list
+end
+
+--------------------------------------------------------------------------------
+function filterlist.get_raw_element(self,idx)
+ if type(idx) ~= "number" then
+ idx = tonumber(idx)
+ end
+
+ if idx ~= nil and idx > 0 and idx <= #self.m_raw_list then
+ return self.m_raw_list[idx]
+ end
+
+ return nil
+end
+
+--------------------------------------------------------------------------------
+function filterlist.get_raw_index(self,listindex)
+ assert(self.m_processed_list ~= nil)
+
+ if listindex ~= nil and listindex > 0 and
+ listindex <= #self.m_processed_list then
+ local entry = self.m_processed_list[listindex]
+
+ for i,v in ipairs(self.m_raw_list) do
+
+ if self.m_compare_fct(v,entry) then
+ return i
+ end
+ end
+ end
+
+ return 0
+end
+
+--------------------------------------------------------------------------------
+function filterlist.get_current_index(self,listindex)
+ assert(self.m_processed_list ~= nil)
+
+ if listindex ~= nil and listindex > 0 and
+ listindex <= #self.m_raw_list then
+ local entry = self.m_raw_list[listindex]
+
+ for i,v in ipairs(self.m_processed_list) do
+
+ if self.m_compare_fct(v,entry) then
+ return i
+ end
+ end
+ end
+
+ return 0
+end
+
+--------------------------------------------------------------------------------
+function filterlist.process(self)
+ assert(self.m_raw_list ~= nil)
+
+ if self.m_sortmode == "none" and
+ self.m_filtercriteria == nil then
+ self.m_processed_list = self.m_raw_list
+ return
+ end
+
+ self.m_processed_list = {}
+
+ for k,v in pairs(self.m_raw_list) do
+ if self.m_filtercriteria == nil or
+ self.m_filter_fct(v,self.m_filtercriteria) then
+ self.m_processed_list[#self.m_processed_list + 1] = v
+ end
+ end
+
+ if self.m_sortmode == "none" then
+ return
+ end
+
+ if self.m_sort_list[self.m_sortmode] ~= nil and
+ type(self.m_sort_list[self.m_sortmode]) == "function" then
+
+ self.m_sort_list[self.m_sortmode](self)
+ end
+end
+
+--------------------------------------------------------------------------------
+function filterlist.size(self)
+ if self.m_processed_list == nil then
+ return 0
+ end
+
+ return #self.m_processed_list
+end
+
+--------------------------------------------------------------------------------
+function filterlist.uid_exists_raw(self,uid)
+ for i,v in ipairs(self.m_raw_list) do
+ if self.m_uid_match_fct(v,uid) then
+ return true
+ end
+ end
+ return false
+end
+
+--------------------------------------------------------------------------------
+function filterlist.raw_index_by_uid(self, uid)
+ local elementcount = 0
+ local elementidx = 0
+ for i,v in ipairs(self.m_raw_list) do
+ if self.m_uid_match_fct(v,uid) then
+ elementcount = elementcount +1
+ elementidx = i
+ end
+ end
+
+
+ -- If there are more elements than one with same name uid can't decide which
+ -- one is meant. self shouldn't be possible but just for sure.
+ if elementcount > 1 then
+ elementidx=0
+ end
+
+ return elementidx
+end
+
+--------------------------------------------------------------------------------
+-- COMMON helper functions --
+--------------------------------------------------------------------------------
+
+--------------------------------------------------------------------------------
+function compare_worlds(world1,world2)
+
+ if world1.path ~= world2.path then
+ return false
+ end
+
+ if world1.name ~= world2.name then
+ return false
+ end
+
+ if world1.gameid ~= world2.gameid then
+ return false
+ end
+
+ return true
+end
+
+--------------------------------------------------------------------------------
+function sort_worlds_alphabetic(self)
+
+ table.sort(self.m_processed_list, function(a, b)
+ --fixes issue #857 (crash due to sorting nil in worldlist)
+ if a == nil or b == nil then
+ if a == nil and b ~= nil then return false end
+ if b == nil and a ~= nil then return true end
+ return false
+ end
+ if a.name:lower() == b.name:lower() then
+ return a.name < b.name
+ end
+ return a.name:lower() < b.name:lower()
+ end)
+end
+
+--------------------------------------------------------------------------------
+function sort_mod_list(self)
+
+ table.sort(self.m_processed_list, function(a, b)
+ -- Show game mods at bottom
+ if a.typ ~= b.typ then
+ if b.typ == "game" then
+ return a.typ ~= "game_mod"
+ end
+ return b.typ == "game_mod"
+ end
+ -- If in same or no modpack, sort by name
+ if a.modpack == b.modpack then
+ if a.name:lower() == b.name:lower() then
+ return a.name < b.name
+ end
+ return a.name:lower() < b.name:lower()
+ -- Else compare name to modpack name
+ else
+ -- Always show modpack pseudo-mod on top of modpack mod list
+ if a.name == b.modpack then
+ return true
+ elseif b.name == a.modpack then
+ return false
+ end
+
+ local name_a = a.modpack or a.name
+ local name_b = b.modpack or b.name
+ if name_a:lower() == name_b:lower() then
+ return name_a < name_b
+ end
+ return name_a:lower() < name_b:lower()
+ end
+ end)
+end
diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua
new file mode 100644
index 000000000..aad5f2d21
--- /dev/null
+++ b/builtin/common/misc_helpers.lua
@@ -0,0 +1,658 @@
+-- Minetest: builtin/misc_helpers.lua
+
+--------------------------------------------------------------------------------
+-- Localize functions to avoid table lookups (better performance).
+local string_sub, string_find = string.sub, string.find
+
+--------------------------------------------------------------------------------
+function basic_dump(o)
+ local tp = type(o)
+ if tp == "number" then
+ return tostring(o)
+ elseif tp == "string" then
+ return string.format("%q", o)
+ elseif tp == "boolean" then
+ return tostring(o)
+ elseif tp == "nil" then
+ return "nil"
+ -- Uncomment for full function dumping support.
+ -- Not currently enabled because bytecode isn't very human-readable and
+ -- dump's output is intended for humans.
+ --elseif tp == "function" then
+ -- return string.format("loadstring(%q)", string.dump(o))
+ else
+ return string.format("<%s>", tp)
+ end
+end
+
+local keywords = {
+ ["and"] = true,
+ ["break"] = true,
+ ["do"] = true,
+ ["else"] = true,
+ ["elseif"] = true,
+ ["end"] = true,
+ ["false"] = true,
+ ["for"] = true,
+ ["function"] = true,
+ ["goto"] = true, -- Lua 5.2
+ ["if"] = true,
+ ["in"] = true,
+ ["local"] = true,
+ ["nil"] = true,
+ ["not"] = true,
+ ["or"] = true,
+ ["repeat"] = true,
+ ["return"] = true,
+ ["then"] = true,
+ ["true"] = true,
+ ["until"] = true,
+ ["while"] = true,
+}
+local function is_valid_identifier(str)
+ if not str:find("^[a-zA-Z_][a-zA-Z0-9_]*$") or keywords[str] then
+ return false
+ end
+ return true
+end
+
+--------------------------------------------------------------------------------
+-- Dumps values in a line-per-value format.
+-- For example, {test = {"Testing..."}} becomes:
+-- _["test"] = {}
+-- _["test"][1] = "Testing..."
+-- This handles tables as keys and circular references properly.
+-- It also handles multiple references well, writing the table only once.
+-- The dumped argument is internal-only.
+
+function dump2(o, name, dumped)
+ name = name or "_"
+ -- "dumped" is used to keep track of serialized tables to handle
+ -- multiple references and circular tables properly.
+ -- It only contains tables as keys. The value is the name that
+ -- the table has in the dump, eg:
+ -- {x = {"y"}} -> dumped[{"y"}] = '_["x"]'
+ dumped = dumped or {}
+ if type(o) ~= "table" then
+ return string.format("%s = %s\n", name, basic_dump(o))
+ end
+ if dumped[o] then
+ return string.format("%s = %s\n", name, dumped[o])
+ end
+ dumped[o] = name
+ -- This contains a list of strings to be concatenated later (because
+ -- Lua is slow at individual concatenation).
+ local t = {}
+ for k, v in pairs(o) do
+ local keyStr
+ if type(k) == "table" then
+ if dumped[k] then
+ keyStr = dumped[k]
+ else
+ -- Key tables don't have a name, so use one of
+ -- the form _G["table: 0xFFFFFFF"]
+ keyStr = string.format("_G[%q]", tostring(k))
+ -- Dump key table
+ t[#t + 1] = dump2(k, keyStr, dumped)
+ end
+ else
+ keyStr = basic_dump(k)
+ end
+ local vname = string.format("%s[%s]", name, keyStr)
+ t[#t + 1] = dump2(v, vname, dumped)
+ end
+ return string.format("%s = {}\n%s", name, table.concat(t))
+end
+
+--------------------------------------------------------------------------------
+-- This dumps values in a one-statement format.
+-- For example, {test = {"Testing..."}} becomes:
+-- [[{
+-- test = {
+-- "Testing..."
+-- }
+-- }]]
+-- This supports tables as keys, but not circular references.
+-- It performs poorly with multiple references as it writes out the full
+-- table each time.
+-- The indent field specifies a indentation string, it defaults to a tab.
+-- Use the empty string to disable indentation.
+-- The dumped and level arguments are internal-only.
+
+function dump(o, indent, nested, level)
+ local t = type(o)
+ if not level and t == "userdata" then
+ -- when userdata (e.g. player) is passed directly, print its metatable:
+ return "userdata metatable: " .. dump(getmetatable(o))
+ end
+ if t ~= "table" then
+ return basic_dump(o)
+ end
+ -- Contains table -> true/nil of currently nested tables
+ nested = nested or {}
+ if nested[o] then
+ return "<circular reference>"
+ end
+ nested[o] = true
+ indent = indent or "\t"
+ level = level or 1
+ local t = {}
+ local dumped_indexes = {}
+ for i, v in ipairs(o) do
+ t[#t + 1] = dump(v, indent, nested, level + 1)
+ dumped_indexes[i] = true
+ end
+ for k, v in pairs(o) do
+ if not dumped_indexes[k] then
+ if type(k) ~= "string" or not is_valid_identifier(k) then
+ k = "["..dump(k, indent, nested, level + 1).."]"
+ end
+ v = dump(v, indent, nested, level + 1)
+ t[#t + 1] = k.." = "..v
+ end
+ end
+ nested[o] = nil
+ if indent ~= "" then
+ local indent_str = "\n"..string.rep(indent, level)
+ local end_indent_str = "\n"..string.rep(indent, level - 1)
+ return string.format("{%s%s%s}",
+ indent_str,
+ table.concat(t, ","..indent_str),
+ end_indent_str)
+ end
+ return "{"..table.concat(t, ", ").."}"
+end
+
+--------------------------------------------------------------------------------
+function string.split(str, delim, include_empty, max_splits, sep_is_pattern)
+ delim = delim or ","
+ max_splits = max_splits or -1
+ local items = {}
+ local pos, len, seplen = 1, #str, #delim
+ local plain = not sep_is_pattern
+ max_splits = max_splits + 1
+ repeat
+ local np, npe = string_find(str, delim, pos, plain)
+ np, npe = (np or (len+1)), (npe or (len+1))
+ if (not np) or (max_splits == 1) then
+ np = len + 1
+ npe = np
+ end
+ local s = string_sub(str, pos, np - 1)
+ if include_empty or (s ~= "") then
+ max_splits = max_splits - 1
+ items[#items + 1] = s
+ end
+ pos = npe + 1
+ until (max_splits == 0) or (pos > (len + 1))
+ return items
+end
+
+--------------------------------------------------------------------------------
+function table.indexof(list, val)
+ for i, v in ipairs(list) do
+ if v == val then
+ return i
+ end
+ end
+ return -1
+end
+
+assert(table.indexof({"foo", "bar"}, "foo") == 1)
+assert(table.indexof({"foo", "bar"}, "baz") == -1)
+
+--------------------------------------------------------------------------------
+if INIT ~= "client" then
+ function file_exists(filename)
+ local f = io.open(filename, "r")
+ if f == nil then
+ return false
+ else
+ f:close()
+ return true
+ end
+ end
+end
+--------------------------------------------------------------------------------
+function string:trim()
+ return (self:gsub("^%s*(.-)%s*$", "%1"))
+end
+
+assert(string.trim("\n \t\tfoo bar\t ") == "foo bar")
+
+--------------------------------------------------------------------------------
+function math.hypot(x, y)
+ local t
+ x = math.abs(x)
+ y = math.abs(y)
+ t = math.min(x, y)
+ x = math.max(x, y)
+ if x == 0 then return 0 end
+ t = t / x
+ return x * math.sqrt(1 + t * t)
+end
+
+--------------------------------------------------------------------------------
+function math.sign(x, tolerance)
+ tolerance = tolerance or 0
+ if x > tolerance then
+ return 1
+ elseif x < -tolerance then
+ return -1
+ end
+ return 0
+end
+
+--------------------------------------------------------------------------------
+function get_last_folder(text,count)
+ local parts = text:split(DIR_DELIM)
+
+ if count == nil then
+ return parts[#parts]
+ end
+
+ local retval = ""
+ for i=1,count,1 do
+ retval = retval .. parts[#parts - (count-i)] .. DIR_DELIM
+ end
+
+ return retval
+end
+
+--------------------------------------------------------------------------------
+function cleanup_path(temppath)
+
+ local parts = temppath:split("-")
+ temppath = ""
+ for i=1,#parts,1 do
+ if temppath ~= "" then
+ temppath = temppath .. "_"
+ end
+ temppath = temppath .. parts[i]
+ end
+
+ parts = temppath:split(".")
+ temppath = ""
+ for i=1,#parts,1 do
+ if temppath ~= "" then
+ temppath = temppath .. "_"
+ end
+ temppath = temppath .. parts[i]
+ end
+
+ parts = temppath:split("'")
+ temppath = ""
+ for i=1,#parts,1 do
+ if temppath ~= "" then
+ temppath = temppath .. ""
+ end
+ temppath = temppath .. parts[i]
+ end
+
+ parts = temppath:split(" ")
+ temppath = ""
+ for i=1,#parts,1 do
+ if temppath ~= "" then
+ temppath = temppath
+ end
+ temppath = temppath .. parts[i]
+ end
+
+ return temppath
+end
+
+function core.formspec_escape(text)
+ if text ~= nil then
+ text = string.gsub(text,"\\","\\\\")
+ text = string.gsub(text,"%]","\\]")
+ text = string.gsub(text,"%[","\\[")
+ text = string.gsub(text,";","\\;")
+ text = string.gsub(text,",","\\,")
+ end
+ return text
+end
+
+
+function core.wrap_text(text, max_length, as_table)
+ local result = {}
+ local line = {}
+ if #text <= max_length then
+ return as_table and {text} or text
+ end
+
+ for word in text:gmatch('%S+') do
+ local cur_length = #table.concat(line, ' ')
+ if cur_length > 0 and cur_length + #word + 1 >= max_length then
+ -- word wouldn't fit on current line, move to next line
+ table.insert(result, table.concat(line, ' '))
+ line = {}
+ end
+ table.insert(line, word)
+ end
+
+ table.insert(result, table.concat(line, ' '))
+ return as_table and result or table.concat(result, '\n')
+end
+
+--------------------------------------------------------------------------------
+
+if INIT == "game" then
+ local dirs1 = {9, 18, 7, 12}
+ local dirs2 = {20, 23, 22, 21}
+
+ function core.rotate_and_place(itemstack, placer, pointed_thing,
+ infinitestacks, orient_flags, prevent_after_place)
+ orient_flags = orient_flags or {}
+
+ local unode = core.get_node_or_nil(pointed_thing.under)
+ if not unode then
+ return
+ end
+ local undef = core.registered_nodes[unode.name]
+ if undef and undef.on_rightclick then
+ return undef.on_rightclick(pointed_thing.under, unode, placer,
+ itemstack, pointed_thing)
+ end
+ local fdir = placer and core.dir_to_facedir(placer:get_look_dir()) or 0
+
+ local above = pointed_thing.above
+ local under = pointed_thing.under
+ local iswall = (above.y == under.y)
+ local isceiling = not iswall and (above.y < under.y)
+
+ if undef and undef.buildable_to then
+ iswall = false
+ end
+
+ if orient_flags.force_floor then
+ iswall = false
+ isceiling = false
+ elseif orient_flags.force_ceiling then
+ iswall = false
+ isceiling = true
+ elseif orient_flags.force_wall then
+ iswall = true
+ isceiling = false
+ elseif orient_flags.invert_wall then
+ iswall = not iswall
+ end
+
+ local param2 = fdir
+ if iswall then
+ param2 = dirs1[fdir + 1]
+ elseif isceiling then
+ if orient_flags.force_facedir then
+ cparam2 = 20
+ else
+ param2 = dirs2[fdir + 1]
+ end
+ else -- place right side up
+ if orient_flags.force_facedir then
+ param2 = 0
+ end
+ end
+
+ local old_itemstack = ItemStack(itemstack)
+ local new_itemstack, removed = core.item_place_node(
+ itemstack, placer, pointed_thing, param2, prevent_after_place
+ )
+ return infinitestacks and old_itemstack or new_itemstack
+ end
+
+
+--------------------------------------------------------------------------------
+--Wrapper for rotate_and_place() to check for sneak and assume Creative mode
+--implies infinite stacks when performing a 6d rotation.
+--------------------------------------------------------------------------------
+ local creative_mode_cache = core.settings:get_bool("creative_mode")
+ local function is_creative(name)
+ return creative_mode_cache or
+ core.check_player_privs(name, {creative = true})
+ end
+
+ core.rotate_node = function(itemstack, placer, pointed_thing)
+ local name = placer and placer:get_player_name() or ""
+ local invert_wall = placer and placer:get_player_control().sneak or false
+ core.rotate_and_place(itemstack, placer, pointed_thing,
+ is_creative(name),
+ {invert_wall = invert_wall}, true)
+ return itemstack
+ end
+end
+
+--------------------------------------------------------------------------------
+function core.explode_table_event(evt)
+ if evt ~= nil then
+ local parts = evt:split(":")
+ if #parts == 3 then
+ local t = parts[1]:trim()
+ local r = tonumber(parts[2]:trim())
+ local c = tonumber(parts[3]:trim())
+ if type(r) == "number" and type(c) == "number"
+ and t ~= "INV" then
+ return {type=t, row=r, column=c}
+ end
+ end
+ end
+ return {type="INV", row=0, column=0}
+end
+
+--------------------------------------------------------------------------------
+function core.explode_textlist_event(evt)
+ if evt ~= nil then
+ local parts = evt:split(":")
+ if #parts == 2 then
+ local t = parts[1]:trim()
+ local r = tonumber(parts[2]:trim())
+ if type(r) == "number" and t ~= "INV" then
+ return {type=t, index=r}
+ end
+ end
+ end
+ return {type="INV", index=0}
+end
+
+--------------------------------------------------------------------------------
+function core.explode_scrollbar_event(evt)
+ local retval = core.explode_textlist_event(evt)
+
+ retval.value = retval.index
+ retval.index = nil
+
+ return retval
+end
+
+--------------------------------------------------------------------------------
+function core.pos_to_string(pos, decimal_places)
+ local x = pos.x
+ local y = pos.y
+ local z = pos.z
+ if decimal_places ~= nil then
+ x = string.format("%." .. decimal_places .. "f", x)
+ y = string.format("%." .. decimal_places .. "f", y)
+ z = string.format("%." .. decimal_places .. "f", z)
+ end
+ return "(" .. x .. "," .. y .. "," .. z .. ")"
+end
+
+--------------------------------------------------------------------------------
+function core.string_to_pos(value)
+ if value == nil then
+ return nil
+ end
+
+ local p = {}
+ p.x, p.y, p.z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
+ if p.x and p.y and p.z then
+ p.x = tonumber(p.x)
+ p.y = tonumber(p.y)
+ p.z = tonumber(p.z)
+ return p
+ end
+ local p = {}
+ p.x, p.y, p.z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$")
+ if p.x and p.y and p.z then
+ p.x = tonumber(p.x)
+ p.y = tonumber(p.y)
+ p.z = tonumber(p.z)
+ return p
+ end
+ return nil
+end
+
+assert(core.string_to_pos("10.0, 5, -2").x == 10)
+assert(core.string_to_pos("( 10.0, 5, -2)").z == -2)
+assert(core.string_to_pos("asd, 5, -2)") == nil)
+
+--------------------------------------------------------------------------------
+function core.string_to_area(value)
+ local p1, p2 = unpack(value:split(") ("))
+ if p1 == nil or p2 == nil then
+ return nil
+ end
+
+ p1 = core.string_to_pos(p1 .. ")")
+ p2 = core.string_to_pos("(" .. p2)
+ if p1 == nil or p2 == nil then
+ return nil
+ end
+
+ return p1, p2
+end
+
+local function test_string_to_area()
+ local p1, p2 = core.string_to_area("(10.0, 5, -2) ( 30.2, 4, -12.53)")
+ assert(p1.x == 10.0 and p1.y == 5 and p1.z == -2)
+ assert(p2.x == 30.2 and p2.y == 4 and p2.z == -12.53)
+
+ p1, p2 = core.string_to_area("(10.0, 5, -2 30.2, 4, -12.53")
+ assert(p1 == nil and p2 == nil)
+
+ p1, p2 = core.string_to_area("(10.0, 5,) -2 fgdf2, 4, -12.53")
+ assert(p1 == nil and p2 == nil)
+end
+
+test_string_to_area()
+
+--------------------------------------------------------------------------------
+function table.copy(t, seen)
+ local n = {}
+ seen = seen or {}
+ seen[t] = n
+ for k, v in pairs(t) do
+ n[(type(k) == "table" and (seen[k] or table.copy(k, seen))) or k] =
+ (type(v) == "table" and (seen[v] or table.copy(v, seen))) or v
+ end
+ return n
+end
+--------------------------------------------------------------------------------
+-- mainmenu only functions
+--------------------------------------------------------------------------------
+if INIT == "mainmenu" then
+ function core.get_game(index)
+ local games = game.get_games()
+
+ if index > 0 and index <= #games then
+ return games[index]
+ end
+
+ return nil
+ end
+end
+
+if INIT == "client" or INIT == "mainmenu" then
+ function fgettext_ne(text, ...)
+ text = core.gettext(text)
+ local arg = {n=select('#', ...), ...}
+ if arg.n >= 1 then
+ -- Insert positional parameters ($1, $2, ...)
+ local result = ''
+ local pos = 1
+ while pos <= text:len() do
+ local newpos = text:find('[$]', pos)
+ if newpos == nil then
+ result = result .. text:sub(pos)
+ pos = text:len() + 1
+ else
+ local paramindex =
+ tonumber(text:sub(newpos+1, newpos+1))
+ result = result .. text:sub(pos, newpos-1)
+ .. tostring(arg[paramindex])
+ pos = newpos + 2
+ end
+ end
+ text = result
+ end
+ return text
+ end
+
+ function fgettext(text, ...)
+ return core.formspec_escape(fgettext_ne(text, ...))
+ end
+end
+
+local ESCAPE_CHAR = string.char(0x1b)
+
+function core.get_color_escape_sequence(color)
+ return ESCAPE_CHAR .. "(c@" .. color .. ")"
+end
+
+function core.get_background_escape_sequence(color)
+ return ESCAPE_CHAR .. "(b@" .. color .. ")"
+end
+
+function core.colorize(color, message)
+ local lines = tostring(message):split("\n", true)
+ local color_code = core.get_color_escape_sequence(color)
+
+ for i, line in ipairs(lines) do
+ lines[i] = color_code .. line
+ end
+
+ return table.concat(lines, "\n") .. core.get_color_escape_sequence("#ffffff")
+end
+
+
+function core.strip_foreground_colors(str)
+ return (str:gsub(ESCAPE_CHAR .. "%(c@[^)]+%)", ""))
+end
+
+function core.strip_background_colors(str)
+ return (str:gsub(ESCAPE_CHAR .. "%(b@[^)]+%)", ""))
+end
+
+function core.strip_colors(str)
+ return (str:gsub(ESCAPE_CHAR .. "%([bc]@[^)]+%)", ""))
+end
+
+--------------------------------------------------------------------------------
+-- Returns the exact coordinate of a pointed surface
+--------------------------------------------------------------------------------
+function core.pointed_thing_to_face_pos(placer, pointed_thing)
+ local eye_offset_first = placer:get_eye_offset()
+ local node_pos = pointed_thing.under
+ local camera_pos = placer:get_pos()
+ local pos_off = vector.multiply(
+ vector.subtract(pointed_thing.above, node_pos), 0.5)
+ local look_dir = placer:get_look_dir()
+ local offset, nc
+ local oc = {}
+
+ for c, v in pairs(pos_off) do
+ if nc or v == 0 then
+ oc[#oc + 1] = c
+ else
+ offset = v
+ nc = c
+ end
+ end
+
+ local fine_pos = {[nc] = node_pos[nc] + offset}
+ camera_pos.y = camera_pos.y + 1.625 + eye_offset_first.y / 10
+ local f = (node_pos[nc] + offset - camera_pos[nc]) / look_dir[nc]
+
+ for i = 1, #oc do
+ fine_pos[oc[i]] = camera_pos[oc[i]] + look_dir[oc[i]] * f
+ end
+ return fine_pos
+end
diff --git a/builtin/common/serialize.lua b/builtin/common/serialize.lua
new file mode 100644
index 000000000..692ddd5f0
--- /dev/null
+++ b/builtin/common/serialize.lua
@@ -0,0 +1,221 @@
+--- Lua module to serialize values as Lua code.
+-- From: https://github.com/fab13n/metalua/blob/no-dll/src/lib/serialize.lua
+-- License: MIT
+-- @copyright 2006-2997 Fabien Fleutot <metalua@gmail.com>
+-- @author Fabien Fleutot <metalua@gmail.com>
+-- @author ShadowNinja <shadowninja@minetest.net>
+--------------------------------------------------------------------------------
+
+--- Serialize an object into a source code string. This string, when passed as
+-- an argument to deserialize(), returns an object structurally identical to
+-- the original one. The following are currently supported:
+-- * Booleans, numbers, strings, and nil.
+-- * Functions; uses interpreter-dependent (and sometimes platform-dependent) bytecode!
+-- * Tables; they can cantain multiple references and can be recursive, but metatables aren't saved.
+-- This works in two phases:
+-- 1. Recursively find and record multiple references and recursion.
+-- 2. Recursively dump the value into a string.
+-- @param x Value to serialize (nil is allowed).
+-- @return load()able string containing the value.
+function core.serialize(x)
+ local local_index = 1 -- Top index of the "_" local table in the dump
+ -- table->nil/1/2 set of tables seen.
+ -- nil = not seen, 1 = seen once, 2 = seen multiple times.
+ local seen = {}
+
+ -- nest_points are places where a table appears within itself, directly
+ -- or not. For instance, all of these chunks create nest points in
+ -- table x: "x = {}; x[x] = 1", "x = {}; x[1] = x",
+ -- "x = {}; x[1] = {y = {x}}".
+ -- To handle those, two tables are used by mark_nest_point:
+ -- * nested - Transient set of tables being currently traversed.
+ -- Used for detecting nested tables.
+ -- * nest_points - parent->{key=value, ...} table cantaining the nested
+ -- keys and values in the parent. They're all dumped after all the
+ -- other table operations have been performed.
+ --
+ -- mark_nest_point(p, k, v) fills nest_points with information required
+ -- to remember that key/value (k, v) creates a nest point in table
+ -- parent. It also marks "parent" and the nested item(s) as occuring
+ -- multiple times, since several references to it will be required in
+ -- order to patch the nest points.
+ local nest_points = {}
+ local nested = {}
+ local function mark_nest_point(parent, k, v)
+ local nk, nv = nested[k], nested[v]
+ local np = nest_points[parent]
+ if not np then
+ np = {}
+ nest_points[parent] = np
+ end
+ np[k] = v
+ seen[parent] = 2
+ if nk then seen[k] = 2 end
+ if nv then seen[v] = 2 end
+ end
+
+ -- First phase, list the tables and functions which appear more than
+ -- once in x.
+ local function mark_multiple_occurences(x)
+ local tp = type(x)
+ if tp ~= "table" and tp ~= "function" then
+ -- No identity (comparison is done by value, not by instance)
+ return
+ end
+ if seen[x] == 1 then
+ seen[x] = 2
+ elseif seen[x] ~= 2 then
+ seen[x] = 1
+ end
+
+ if tp == "table" then
+ nested[x] = true
+ for k, v in pairs(x) do
+ if nested[k] or nested[v] then
+ mark_nest_point(x, k, v)
+ else
+ mark_multiple_occurences(k)
+ mark_multiple_occurences(v)
+ end
+ end
+ nested[x] = nil
+ end
+ end
+
+ local dumped = {} -- object->varname set
+ local local_defs = {} -- Dumped local definitions as source code lines
+
+ -- Mutually recursive local functions:
+ local dump_val, dump_or_ref_val
+
+ -- If x occurs multiple times, dump the local variable rather than
+ -- the value. If it's the first time it's dumped, also dump the
+ -- content in local_defs.
+ function dump_or_ref_val(x)
+ if seen[x] ~= 2 then
+ return dump_val(x)
+ end
+ local var = dumped[x]
+ if var then -- Already referenced
+ return var
+ end
+ -- First occurence, create and register reference
+ local val = dump_val(x)
+ local i = local_index
+ local_index = local_index + 1
+ var = "_["..i.."]"
+ local_defs[#local_defs + 1] = var.." = "..val
+ dumped[x] = var
+ return var
+ end
+
+ -- Second phase. Dump the object; subparts occuring multiple times
+ -- are dumped in local variables which can be referenced multiple
+ -- times. Care is taken to dump local vars in a sensible order.
+ function dump_val(x)
+ local tp = type(x)
+ if x == nil then return "nil"
+ elseif tp == "string" then return string.format("%q", x)
+ elseif tp == "boolean" then return x and "true" or "false"
+ elseif tp == "function" then
+ return string.format("loadstring(%q)", string.dump(x))
+ elseif tp == "number" then
+ -- Serialize integers with string.format to prevent
+ -- scientific notation, which doesn't preserve
+ -- precision and breaks things like node position
+ -- hashes. Serialize floats normally.
+ if math.floor(x) == x then
+ return string.format("%d", x)
+ else
+ return tostring(x)
+ end
+ elseif tp == "table" then
+ local vals = {}
+ local idx_dumped = {}
+ local np = nest_points[x]
+ for i, v in ipairs(x) do
+ if not np or not np[i] then
+ vals[#vals + 1] = dump_or_ref_val(v)
+ end
+ idx_dumped[i] = true
+ end
+ for k, v in pairs(x) do
+ if (not np or not np[k]) and
+ not idx_dumped[k] then
+ vals[#vals + 1] = "["..dump_or_ref_val(k).."] = "
+ ..dump_or_ref_val(v)
+ end
+ end
+ return "{"..table.concat(vals, ", ").."}"
+ else
+ error("Can't serialize data of type "..tp)
+ end
+ end
+
+ local function dump_nest_points()
+ for parent, vals in pairs(nest_points) do
+ for k, v in pairs(vals) do
+ local_defs[#local_defs + 1] = dump_or_ref_val(parent)
+ .."["..dump_or_ref_val(k).."] = "
+ ..dump_or_ref_val(v)
+ end
+ end
+ end
+
+ mark_multiple_occurences(x)
+ local top_level = dump_or_ref_val(x)
+ dump_nest_points()
+
+ if next(local_defs) then
+ return "local _ = {}\n"
+ ..table.concat(local_defs, "\n")
+ .."\nreturn "..top_level
+ else
+ return "return "..top_level
+ end
+end
+
+-- Deserialization
+
+local env = {
+ loadstring = loadstring,
+}
+
+local safe_env = {
+ loadstring = function() end,
+}
+
+function core.deserialize(str, safe)
+ if type(str) ~= "string" then
+ return nil, "Cannot deserialize type '"..type(str)
+ .."'. Argument must be a string."
+ end
+ if str:byte(1) == 0x1B then
+ return nil, "Bytecode prohibited"
+ end
+ local f, err = loadstring(str)
+ if not f then return nil, err end
+ setfenv(f, safe and safe_env or env)
+
+ local good, data = pcall(f)
+ if good then
+ return data
+ else
+ return nil, data
+ end
+end
+
+
+-- Unit tests
+local test_in = {cat={sound="nyan", speed=400}, dog={sound="woof"}}
+local test_out = core.deserialize(core.serialize(test_in))
+
+assert(test_in.cat.sound == test_out.cat.sound)
+assert(test_in.cat.speed == test_out.cat.speed)
+assert(test_in.dog.sound == test_out.dog.sound)
+
+test_in = {escape_chars="\n\r\t\v\\\"\'", non_european="θשׁ٩∂"}
+test_out = core.deserialize(core.serialize(test_in))
+assert(test_in.escape_chars == test_out.escape_chars)
+assert(test_in.non_european == test_out.non_european)
+
diff --git a/builtin/common/strict.lua b/builtin/common/strict.lua
new file mode 100644
index 000000000..ccde9676b
--- /dev/null
+++ b/builtin/common/strict.lua
@@ -0,0 +1,57 @@
+
+-- Always warn when creating a global variable, even outside of a function.
+-- This ignores mod namespaces (variables with the same name as the current mod).
+local WARN_INIT = false
+
+local getinfo = debug.getinfo
+
+function core.global_exists(name)
+ if type(name) ~= "string" then
+ error("core.global_exists: " .. tostring(name) .. " is not a string")
+ end
+ return rawget(_G, name) ~= nil
+end
+
+
+local meta = {}
+local declared = {}
+-- Key is source file, line, and variable name; seperated by NULs
+local warned = {}
+
+function meta:__newindex(name, value)
+ local info = getinfo(2, "Sl")
+ local desc = ("%s:%d"):format(info.short_src, info.currentline)
+ if not declared[name] then
+ local warn_key = ("%s\0%d\0%s"):format(info.source,
+ info.currentline, name)
+ if not warned[warn_key] and info.what ~= "main" and
+ info.what ~= "C" then
+ core.log("warning", ("Assignment to undeclared "..
+ "global %q inside a function at %s.")
+ :format(name, desc))
+ warned[warn_key] = true
+ end
+ declared[name] = true
+ end
+ -- Ignore mod namespaces
+ if WARN_INIT and name ~= core.get_current_modname() then
+ core.log("warning", ("Global variable %q created at %s.")
+ :format(name, desc))
+ end
+ rawset(self, name, value)
+end
+
+
+function meta:__index(name)
+ local info = getinfo(2, "Sl")
+ local warn_key = ("%s\0%d\0%s"):format(info.source, info.currentline, name)
+ if not declared[name] and not warned[warn_key] and info.what ~= "C" then
+ core.log("warning", ("Undeclared global variable %q accessed at %s:%s")
+ :format(name, info.short_src, info.currentline))
+ warned[warn_key] = true
+ end
+ return rawget(self, name)
+end
+
+setmetatable(_G, meta)
+
diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua
new file mode 100644
index 000000000..0549f9a56
--- /dev/null
+++ b/builtin/common/vector.lua
@@ -0,0 +1,145 @@
+
+vector = {}
+
+function vector.new(a, b, c)
+ if type(a) == "table" then
+ assert(a.x and a.y and a.z, "Invalid vector passed to vector.new()")
+ return {x=a.x, y=a.y, z=a.z}
+ elseif a then
+ assert(b and c, "Invalid arguments for vector.new()")
+ return {x=a, y=b, z=c}
+ end
+ return {x=0, y=0, z=0}
+end
+
+function vector.equals(a, b)
+ return a.x == b.x and
+ a.y == b.y and
+ a.z == b.z
+end
+
+function vector.length(v)
+ return math.hypot(v.x, math.hypot(v.y, v.z))
+end
+
+function vector.normalize(v)
+ local len = vector.length(v)
+ if len == 0 then
+ return {x=0, y=0, z=0}
+ else
+ return vector.divide(v, len)
+ end
+end
+
+function vector.floor(v)
+ return {
+ x = math.floor(v.x),
+ y = math.floor(v.y),
+ z = math.floor(v.z)
+ }
+end
+
+function vector.round(v)
+ return {
+ x = math.floor(v.x + 0.5),
+ y = math.floor(v.y + 0.5),
+ z = math.floor(v.z + 0.5)
+ }
+end
+
+function vector.apply(v, func)
+ return {
+ x = func(v.x),
+ y = func(v.y),
+ z = func(v.z)
+ }
+end
+
+function vector.distance(a, b)
+ local x = a.x - b.x
+ local y = a.y - b.y
+ local z = a.z - b.z
+ return math.hypot(x, math.hypot(y, z))
+end
+
+function vector.direction(pos1, pos2)
+ local x_raw = pos2.x - pos1.x
+ local y_raw = pos2.y - pos1.y
+ local z_raw = pos2.z - pos1.z
+ local x_abs = math.abs(x_raw)
+ local y_abs = math.abs(y_raw)
+ local z_abs = math.abs(z_raw)
+ if x_abs >= y_abs and
+ x_abs >= z_abs then
+ y_raw = y_raw * (1 / x_abs)
+ z_raw = z_raw * (1 / x_abs)
+ x_raw = x_raw / x_abs
+ end
+ if y_abs >= x_abs and
+ y_abs >= z_abs then
+ x_raw = x_raw * (1 / y_abs)
+ z_raw = z_raw * (1 / y_abs)
+ y_raw = y_raw / y_abs
+ end
+ if z_abs >= y_abs and
+ z_abs >= x_abs then
+ x_raw = x_raw * (1 / z_abs)
+ y_raw = y_raw * (1 / z_abs)
+ z_raw = z_raw / z_abs
+ end
+ return {x=x_raw, y=y_raw, z=z_raw}
+end
+
+
+function vector.add(a, b)
+ if type(b) == "table" then
+ return {x = a.x + b.x,
+ y = a.y + b.y,
+ z = a.z + b.z}
+ else
+ return {x = a.x + b,
+ y = a.y + b,
+ z = a.z + b}
+ end
+end
+
+function vector.subtract(a, b)
+ if type(b) == "table" then
+ return {x = a.x - b.x,
+ y = a.y - b.y,
+ z = a.z - b.z}
+ else
+ return {x = a.x - b,
+ y = a.y - b,
+ z = a.z - b}
+ end
+end
+
+function vector.multiply(a, b)
+ if type(b) == "table" then
+ return {x = a.x * b.x,
+ y = a.y * b.y,
+ z = a.z * b.z}
+ else
+ return {x = a.x * b,
+ y = a.y * b,
+ z = a.z * b}
+ end
+end
+
+function vector.divide(a, b)
+ if type(b) == "table" then
+ return {x = a.x / b.x,
+ y = a.y / b.y,
+ z = a.z / b.z}
+ else
+ return {x = a.x / b,
+ y = a.y / b,
+ z = a.z / b}
+ end
+end
+
+function vector.sort(a, b)
+ return {x = math.min(a.x, b.x), y = math.min(a.y, b.y), z = math.min(a.z, b.z)},
+ {x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z)}
+end
diff --git a/builtin/fstk/buttonbar.lua b/builtin/fstk/buttonbar.lua
new file mode 100644
index 000000000..465588324
--- /dev/null
+++ b/builtin/fstk/buttonbar.lua
@@ -0,0 +1,215 @@
+--Minetest
+--Copyright (C) 2014 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+local function buttonbar_formspec(self)
+
+ if self.hidden then
+ return ""
+ end
+
+ local formspec = string.format("box[%f,%f;%f,%f;%s]",
+ self.pos.x,self.pos.y ,self.size.x,self.size.y,self.bgcolor)
+
+ for i=self.startbutton,#self.buttons,1 do
+ local btn_name = self.buttons[i].name
+ local btn_pos = {}
+
+ if self.orientation == "horizontal" then
+ btn_pos.x = self.pos.x + --base pos
+ (i - self.startbutton) * self.btn_size + --button offset
+ self.btn_initial_offset
+ else
+ btn_pos.x = self.pos.x + (self.btn_size * 0.05)
+ end
+
+ if self.orientation == "vertical" then
+ btn_pos.y = self.pos.y + --base pos
+ (i - self.startbutton) * self.btn_size + --button offset
+ self.btn_initial_offset
+ else
+ btn_pos.y = self.pos.y + (self.btn_size * 0.05)
+ end
+
+ if (self.orientation == "vertical" and
+ (btn_pos.y + self.btn_size <= self.pos.y + self.size.y)) or
+ (self.orientation == "horizontal" and
+ (btn_pos.x + self.btn_size <= self.pos.x + self.size.x)) then
+
+ local borders="true"
+
+ if self.buttons[i].image ~= nil then
+ borders="false"
+ end
+
+ formspec = formspec ..
+ string.format("image_button[%f,%f;%f,%f;%s;%s;%s;true;%s]tooltip[%s;%s]",
+ btn_pos.x, btn_pos.y, self.btn_size, self.btn_size,
+ self.buttons[i].image, btn_name, self.buttons[i].caption,
+ borders, btn_name, self.buttons[i].tooltip)
+ else
+ --print("end of displayable buttons: orientation: " .. self.orientation)
+ --print( "button_end: " .. (btn_pos.y + self.btn_size - (self.btn_size * 0.05)))
+ --print( "bar_end: " .. (self.pos.x + self.size.x))
+ break
+ end
+ end
+
+ if (self.have_move_buttons) then
+ local btn_dec_pos = {}
+ btn_dec_pos.x = self.pos.x + (self.btn_size * 0.05)
+ btn_dec_pos.y = self.pos.y + (self.btn_size * 0.05)
+ local btn_inc_pos = {}
+ local btn_size = {}
+
+ if self.orientation == "horizontal" then
+ btn_size.x = 0.5
+ btn_size.y = self.btn_size
+ btn_inc_pos.x = self.pos.x + self.size.x - 0.5
+ btn_inc_pos.y = self.pos.y + (self.btn_size * 0.05)
+ else
+ btn_size.x = self.btn_size
+ btn_size.y = 0.5
+ btn_inc_pos.x = self.pos.x + (self.btn_size * 0.05)
+ btn_inc_pos.y = self.pos.y + self.size.y - 0.5
+ end
+
+ local text_dec = "<"
+ local text_inc = ">"
+ if self.orientation == "vertical" then
+ text_dec = "^"
+ text_inc = "v"
+ end
+
+ formspec = formspec ..
+ string.format("image_button[%f,%f;%f,%f;;btnbar_dec_%s;%s;true;true]",
+ btn_dec_pos.x, btn_dec_pos.y, btn_size.x, btn_size.y,
+ self.name, text_dec)
+
+ formspec = formspec ..
+ string.format("image_button[%f,%f;%f,%f;;btnbar_inc_%s;%s;true;true]",
+ btn_inc_pos.x, btn_inc_pos.y, btn_size.x, btn_size.y,
+ self.name, text_inc)
+ end
+
+ return formspec
+end
+
+local function buttonbar_buttonhandler(self, fields)
+
+ if fields["btnbar_inc_" .. self.name] ~= nil and
+ self.startbutton < #self.buttons then
+
+ self.startbutton = self.startbutton + 1
+ return true
+ end
+
+ if fields["btnbar_dec_" .. self.name] ~= nil and self.startbutton > 1 then
+ self.startbutton = self.startbutton - 1
+ return true
+ end
+
+ for i=1,#self.buttons,1 do
+ if fields[self.buttons[i].name] ~= nil then
+ return self.userbuttonhandler(fields)
+ end
+ end
+end
+
+local buttonbar_metatable = {
+ handle_buttons = buttonbar_buttonhandler,
+ handle_events = function(self, event) end,
+ get_formspec = buttonbar_formspec,
+
+ hide = function(self) self.hidden = true end,
+ show = function(self) self.hidden = false end,
+
+ delete = function(self) ui.delete(self) end,
+
+ add_button = function(self, name, caption, image, tooltip)
+ if caption == nil then caption = "" end
+ if image == nil then image = "" end
+ if tooltip == nil then tooltip = "" end
+
+ self.buttons[#self.buttons + 1] = {
+ name = name,
+ caption = caption,
+ image = image,
+ tooltip = tooltip
+ }
+ if self.orientation == "horizontal" then
+ if ( (self.btn_size * #self.buttons) + (self.btn_size * 0.05 *2)
+ > self.size.x ) then
+
+ self.btn_initial_offset = self.btn_size * 0.05 + 0.5
+ self.have_move_buttons = true
+ end
+ else
+ if ((self.btn_size * #self.buttons) + (self.btn_size * 0.05 *2)
+ > self.size.y ) then
+
+ self.btn_initial_offset = self.btn_size * 0.05 + 0.5
+ self.have_move_buttons = true
+ end
+ end
+ end,
+
+ set_bgparams = function(self, bgcolor)
+ if (type(bgcolor) == "string") then
+ self.bgcolor = bgcolor
+ end
+ end,
+}
+
+buttonbar_metatable.__index = buttonbar_metatable
+
+function buttonbar_create(name, cbf_buttonhandler, pos, orientation, size)
+ assert(name ~= nil)
+ assert(cbf_buttonhandler ~= nil)
+ assert(orientation == "vertical" or orientation == "horizontal")
+ assert(pos ~= nil and type(pos) == "table")
+ assert(size ~= nil and type(size) == "table")
+
+ local self = {}
+ self.name = name
+ self.type = "addon"
+ self.bgcolor = "#000000"
+ self.pos = pos
+ self.size = size
+ self.orientation = orientation
+ self.startbutton = 1
+ self.have_move_buttons = false
+ self.hidden = false
+
+ if self.orientation == "horizontal" then
+ self.btn_size = self.size.y
+ else
+ self.btn_size = self.size.x
+ end
+
+ if (self.btn_initial_offset == nil) then
+ self.btn_initial_offset = self.btn_size * 0.05
+ end
+
+ self.userbuttonhandler = cbf_buttonhandler
+ self.buttons = {}
+
+ setmetatable(self,buttonbar_metatable)
+
+ ui.add(self)
+ return self
+end
diff --git a/builtin/fstk/dialog.lua b/builtin/fstk/dialog.lua
new file mode 100644
index 000000000..df887f413
--- /dev/null
+++ b/builtin/fstk/dialog.lua
@@ -0,0 +1,69 @@
+--Minetest
+--Copyright (C) 2014 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--this program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+local function dialog_event_handler(self,event)
+ if self.user_eventhandler == nil or
+ self.user_eventhandler(event) == false then
+
+ --close dialog on esc
+ if event == "MenuQuit" then
+ self:delete()
+ return true
+ end
+ end
+end
+
+local dialog_metatable = {
+ eventhandler = dialog_event_handler,
+ get_formspec = function(self)
+ if not self.hidden then return self.formspec(self.data) end
+ end,
+ handle_buttons = function(self,fields)
+ if not self.hidden then return self.buttonhandler(self,fields) end
+ end,
+ handle_events = function(self,event)
+ if not self.hidden then return self.eventhandler(self,event) end
+ end,
+ hide = function(self) self.hidden = true end,
+ show = function(self) self.hidden = false end,
+ delete = function(self)
+ if self.parent ~= nil then
+ self.parent:show()
+ end
+ ui.delete(self)
+ end,
+ set_parent = function(self,parent) self.parent = parent end
+}
+dialog_metatable.__index = dialog_metatable
+
+function dialog_create(name,get_formspec,buttonhandler,eventhandler)
+ local self = {}
+
+ self.name = name
+ self.type = "toplevel"
+ self.hidden = true
+ self.data = {}
+
+ self.formspec = get_formspec
+ self.buttonhandler = buttonhandler
+ self.user_eventhandler = eventhandler
+
+ setmetatable(self,dialog_metatable)
+
+ ui.add(self)
+ return self
+end
diff --git a/builtin/fstk/tabview.lua b/builtin/fstk/tabview.lua
new file mode 100644
index 000000000..3715e231b
--- /dev/null
+++ b/builtin/fstk/tabview.lua
@@ -0,0 +1,273 @@
+--Minetest
+--Copyright (C) 2014 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+--------------------------------------------------------------------------------
+-- A tabview implementation --
+-- Usage: --
+-- tabview.create: returns initialized tabview raw element --
+-- element.add(tab): add a tab declaration --
+-- element.handle_buttons() --
+-- element.handle_events() --
+-- element.getFormspec() returns formspec of tabview --
+--------------------------------------------------------------------------------
+
+--------------------------------------------------------------------------------
+local function add_tab(self,tab)
+ assert(tab.size == nil or (type(tab.size) == table and
+ tab.size.x ~= nil and tab.size.y ~= nil))
+ assert(tab.cbf_formspec ~= nil and type(tab.cbf_formspec) == "function")
+ assert(tab.cbf_button_handler == nil or
+ type(tab.cbf_button_handler) == "function")
+ assert(tab.cbf_events == nil or type(tab.cbf_events) == "function")
+
+ local newtab = {
+ name = tab.name,
+ caption = tab.caption,
+ button_handler = tab.cbf_button_handler,
+ event_handler = tab.cbf_events,
+ get_formspec = tab.cbf_formspec,
+ tabsize = tab.tabsize,
+ on_change = tab.on_change,
+ tabdata = {},
+ }
+
+ self.tablist[#self.tablist + 1] = newtab
+
+ if self.last_tab_index == #self.tablist then
+ self.current_tab = tab.name
+ if tab.on_activate ~= nil then
+ tab.on_activate(nil,tab.name)
+ end
+ end
+end
+
+--------------------------------------------------------------------------------
+local function get_formspec(self)
+ local formspec = ""
+
+ if not self.hidden and (self.parent == nil or not self.parent.hidden) then
+
+ if self.parent == nil then
+ local tsize = self.tablist[self.last_tab_index].tabsize or
+ {width=self.width, height=self.height}
+ formspec = formspec ..
+ string.format("size[%f,%f,%s]",tsize.width,tsize.height,
+ dump(self.fixed_size))
+ end
+ formspec = formspec .. self:tab_header()
+ formspec = formspec ..
+ self.tablist[self.last_tab_index].get_formspec(
+ self,
+ self.tablist[self.last_tab_index].name,
+ self.tablist[self.last_tab_index].tabdata,
+ self.tablist[self.last_tab_index].tabsize
+ )
+ end
+ return formspec
+end
+
+--------------------------------------------------------------------------------
+local function handle_buttons(self,fields)
+
+ if self.hidden then
+ return false
+ end
+
+ if self:handle_tab_buttons(fields) then
+ return true
+ end
+
+ if self.glb_btn_handler ~= nil and
+ self.glb_btn_handler(self,fields) then
+ return true
+ end
+
+ if self.tablist[self.last_tab_index].button_handler ~= nil then
+ return
+ self.tablist[self.last_tab_index].button_handler(
+ self,
+ fields,
+ self.tablist[self.last_tab_index].name,
+ self.tablist[self.last_tab_index].tabdata
+ )
+ end
+
+ return false
+end
+
+--------------------------------------------------------------------------------
+local function handle_events(self,event)
+
+ if self.hidden then
+ return false
+ end
+
+ if self.glb_evt_handler ~= nil and
+ self.glb_evt_handler(self,event) then
+ return true
+ end
+
+ if self.tablist[self.last_tab_index].evt_handler ~= nil then
+ return
+ self.tablist[self.last_tab_index].evt_handler(
+ self,
+ event,
+ self.tablist[self.last_tab_index].name,
+ self.tablist[self.last_tab_index].tabdata
+ )
+ end
+
+ return false
+end
+
+
+--------------------------------------------------------------------------------
+local function tab_header(self)
+
+ local toadd = ""
+
+ for i=1,#self.tablist,1 do
+
+ if toadd ~= "" then
+ toadd = toadd .. ","
+ end
+
+ toadd = toadd .. self.tablist[i].caption
+ end
+ return string.format("tabheader[%f,%f;%s;%s;%i;true;false]",
+ self.header_x, self.header_y, self.name, toadd, self.last_tab_index);
+end
+
+--------------------------------------------------------------------------------
+local function switch_to_tab(self, index)
+ --first call on_change for tab to leave
+ if self.tablist[self.last_tab_index].on_change ~= nil then
+ self.tablist[self.last_tab_index].on_change("LEAVE",
+ self.current_tab, self.tablist[index].name)
+ end
+
+ --update tabview data
+ self.last_tab_index = index
+ local old_tab = self.current_tab
+ self.current_tab = self.tablist[index].name
+
+ if (self.autosave_tab) then
+ core.settings:set(self.name .. "_LAST",self.current_tab)
+ end
+
+ -- call for tab to enter
+ if self.tablist[index].on_change ~= nil then
+ self.tablist[index].on_change("ENTER",
+ old_tab,self.current_tab)
+ end
+end
+
+--------------------------------------------------------------------------------
+local function handle_tab_buttons(self,fields)
+ --save tab selection to config file
+ if fields[self.name] then
+ local index = tonumber(fields[self.name])
+ switch_to_tab(self, index)
+ return true
+ end
+
+ return false
+end
+
+--------------------------------------------------------------------------------
+local function set_tab_by_name(self, name)
+ for i=1,#self.tablist,1 do
+ if self.tablist[i].name == name then
+ switch_to_tab(self, i)
+ return true
+ end
+ end
+
+ return false
+end
+
+--------------------------------------------------------------------------------
+local function hide_tabview(self)
+ self.hidden=true
+
+ --call on_change as we're not gonna show self tab any longer
+ if self.tablist[self.last_tab_index].on_change ~= nil then
+ self.tablist[self.last_tab_index].on_change("LEAVE",
+ self.current_tab, nil)
+ end
+end
+
+--------------------------------------------------------------------------------
+local function show_tabview(self)
+ self.hidden=false
+
+ -- call for tab to enter
+ if self.tablist[self.last_tab_index].on_change ~= nil then
+ self.tablist[self.last_tab_index].on_change("ENTER",
+ nil,self.current_tab)
+ end
+end
+
+local tabview_metatable = {
+ add = add_tab,
+ handle_buttons = handle_buttons,
+ handle_events = handle_events,
+ get_formspec = get_formspec,
+ show = show_tabview,
+ hide = hide_tabview,
+ delete = function(self) ui.delete(self) end,
+ set_parent = function(self,parent) self.parent = parent end,
+ set_autosave_tab =
+ function(self,value) self.autosave_tab = value end,
+ set_tab = set_tab_by_name,
+ set_global_button_handler =
+ function(self,handler) self.glb_btn_handler = handler end,
+ set_global_event_handler =
+ function(self,handler) self.glb_evt_handler = handler end,
+ set_fixed_size =
+ function(self,state) self.fixed_size = state end,
+ tab_header = tab_header,
+ handle_tab_buttons = handle_tab_buttons
+}
+
+tabview_metatable.__index = tabview_metatable
+
+--------------------------------------------------------------------------------
+function tabview_create(name, size, tabheaderpos)
+ local self = {}
+
+ self.name = name
+ self.type = "toplevel"
+ self.width = size.x
+ self.height = size.y
+ self.header_x = tabheaderpos.x
+ self.header_y = tabheaderpos.y
+
+ setmetatable(self, tabview_metatable)
+
+ self.fixed_size = true
+ self.hidden = true
+ self.current_tab = nil
+ self.last_tab_index = 1
+ self.tablist = {}
+
+ self.autosave_tab = false
+
+ ui.add(self)
+ return self
+end
diff --git a/builtin/fstk/ui.lua b/builtin/fstk/ui.lua
new file mode 100644
index 000000000..3ac0386ca
--- /dev/null
+++ b/builtin/fstk/ui.lua
@@ -0,0 +1,208 @@
+--Minetest
+--Copyright (C) 2014 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ui = {}
+ui.childlist = {}
+ui.default = nil
+
+--------------------------------------------------------------------------------
+function ui.add(child)
+ --TODO check child
+ ui.childlist[child.name] = child
+
+ return child.name
+end
+
+--------------------------------------------------------------------------------
+function ui.delete(child)
+
+ if ui.childlist[child.name] == nil then
+ return false
+ end
+
+ ui.childlist[child.name] = nil
+ return true
+end
+
+--------------------------------------------------------------------------------
+function ui.set_default(name)
+ ui.default = name
+end
+
+--------------------------------------------------------------------------------
+function ui.find_by_name(name)
+ return ui.childlist[name]
+end
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+-- Internal functions not to be called from user
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+
+local function wordwrap_quickhack(str)
+ local res = ""
+ local ar = str:split("\n")
+ for i = 1, #ar do
+ local text = ar[i]
+ -- Hack to add word wrapping.
+ -- TODO: Add engine support for wrapping in formspecs
+ while #text > 80 do
+ if res ~= "" then
+ res = res .. ","
+ end
+ res = res .. core.formspec_escape(string.sub(text, 1, 79))
+ text = string.sub(text, 80, #text)
+ end
+ if res ~= "" then
+ res = res .. ","
+ end
+ res = res .. core.formspec_escape(text)
+ end
+ return res
+end
+
+--------------------------------------------------------------------------------
+function ui.update()
+ local formspec = ""
+
+ -- handle errors
+ if gamedata ~= nil and gamedata.reconnect_requested then
+ formspec = wordwrap_quickhack(gamedata.errormessage or "")
+ formspec = "size[12,5]" ..
+ "label[0.5,0;" .. fgettext("The server has requested a reconnect:") ..
+ "]textlist[0.2,0.8;11.5,3.5;;" .. formspec ..
+ "]button[6,4.6;3,0.5;btn_reconnect_no;" .. fgettext("Main menu") .. "]" ..
+ "button[3,4.6;3,0.5;btn_reconnect_yes;" .. fgettext("Reconnect") .. "]"
+ elseif gamedata ~= nil and gamedata.errormessage ~= nil then
+ formspec = wordwrap_quickhack(gamedata.errormessage)
+ local error_title
+ if string.find(gamedata.errormessage, "ModError") then
+ error_title = fgettext("An error occured in a Lua script, such as a mod:")
+ else
+ error_title = fgettext("An error occured:")
+ end
+ formspec = "size[12,5]" ..
+ "label[0.5,0;" .. error_title ..
+ "]textlist[0.2,0.8;11.5,3.5;;" .. formspec ..
+ "]button[4.5,4.6;3,0.5;btn_error_confirm;" .. fgettext("Ok") .. "]"
+ else
+ local active_toplevel_ui_elements = 0
+ for key,value in pairs(ui.childlist) do
+ if (value.type == "toplevel") then
+ local retval = value:get_formspec()
+
+ if retval ~= nil and retval ~= "" then
+ active_toplevel_ui_elements = active_toplevel_ui_elements +1
+ formspec = formspec .. retval
+ end
+ end
+ end
+
+ -- no need to show addons if there ain't a toplevel element
+ if (active_toplevel_ui_elements > 0) then
+ for key,value in pairs(ui.childlist) do
+ if (value.type == "addon") then
+ local retval = value:get_formspec()
+
+ if retval ~= nil and retval ~= "" then
+ formspec = formspec .. retval
+ end
+ end
+ end
+ end
+
+ if (active_toplevel_ui_elements > 1) then
+ core.log("warning", "more than one active ui "..
+ "element, self most likely isn't intended")
+ end
+
+ if (active_toplevel_ui_elements == 0) then
+ core.log("warning", "no toplevel ui element "..
+ "active; switching to default")
+ ui.childlist[ui.default]:show()
+ formspec = ui.childlist[ui.default]:get_formspec()
+ end
+ end
+ core.update_formspec(formspec)
+end
+
+--------------------------------------------------------------------------------
+function ui.handle_buttons(fields)
+ for key,value in pairs(ui.childlist) do
+
+ local retval = value:handle_buttons(fields)
+
+ if retval then
+ ui.update()
+ return
+ end
+ end
+end
+
+
+--------------------------------------------------------------------------------
+function ui.handle_events(event)
+
+ for key,value in pairs(ui.childlist) do
+
+ if value.handle_events ~= nil then
+ local retval = value:handle_events(event)
+
+ if retval then
+ return retval
+ end
+ end
+ end
+end
+
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+-- initialize callbacks
+--------------------------------------------------------------------------------
+--------------------------------------------------------------------------------
+core.button_handler = function(fields)
+ if fields["btn_reconnect_yes"] then
+ gamedata.reconnect_requested = false
+ gamedata.errormessage = nil
+ gamedata.do_reconnect = true
+ core.start()
+ return
+ elseif fields["btn_reconnect_no"] or fields["btn_error_confirm"] then
+ gamedata.errormessage = nil
+ gamedata.reconnect_requested = false
+ ui.update()
+ return
+ end
+
+ if ui.handle_buttons(fields) then
+ ui.update()
+ end
+end
+
+--------------------------------------------------------------------------------
+core.event_handler = function(event)
+ if ui.handle_events(event) then
+ ui.update()
+ return
+ end
+
+ if event == "Refresh" then
+ ui.update()
+ return
+ end
+end
diff --git a/builtin/game/auth.lua b/builtin/game/auth.lua
new file mode 100644
index 000000000..19af8db73
--- /dev/null
+++ b/builtin/game/auth.lua
@@ -0,0 +1,216 @@
+-- Minetest: builtin/auth.lua
+
+--
+-- Authentication handler
+--
+
+function core.string_to_privs(str, delim)
+ assert(type(str) == "string")
+ delim = delim or ','
+ local privs = {}
+ for _, priv in pairs(string.split(str, delim)) do
+ privs[priv:trim()] = true
+ end
+ return privs
+end
+
+function core.privs_to_string(privs, delim)
+ assert(type(privs) == "table")
+ delim = delim or ','
+ local list = {}
+ for priv, bool in pairs(privs) do
+ if bool then
+ list[#list + 1] = priv
+ end
+ end
+ return table.concat(list, delim)
+end
+
+assert(core.string_to_privs("a,b").b == true)
+assert(core.privs_to_string({a=true,b=true}) == "a,b")
+
+core.auth_file_path = core.get_worldpath().."/auth.txt"
+core.auth_table = {}
+
+local function read_auth_file()
+ local newtable = {}
+ local file, errmsg = io.open(core.auth_file_path, 'rb')
+ if not file then
+ core.log("info", core.auth_file_path.." could not be opened for reading ("..errmsg.."); assuming new world")
+ return
+ end
+ for line in file:lines() do
+ if line ~= "" then
+ local fields = line:split(":", true)
+ local name, password, privilege_string, last_login = unpack(fields)
+ last_login = tonumber(last_login)
+ if not (name and password and privilege_string) then
+ error("Invalid line in auth.txt: "..dump(line))
+ end
+ local privileges = core.string_to_privs(privilege_string)
+ newtable[name] = {password=password, privileges=privileges, last_login=last_login}
+ end
+ end
+ io.close(file)
+ core.auth_table = newtable
+ core.notify_authentication_modified()
+end
+
+local function save_auth_file()
+ local newtable = {}
+ -- Check table for validness before attempting to save
+ for name, stuff in pairs(core.auth_table) do
+ assert(type(name) == "string")
+ assert(name ~= "")
+ assert(type(stuff) == "table")
+ assert(type(stuff.password) == "string")
+ assert(type(stuff.privileges) == "table")
+ assert(stuff.last_login == nil or type(stuff.last_login) == "number")
+ end
+ local content = {}
+ for name, stuff in pairs(core.auth_table) do
+ local priv_string = core.privs_to_string(stuff.privileges)
+ local parts = {name, stuff.password, priv_string, stuff.last_login or ""}
+ content[#content + 1] = table.concat(parts, ":")
+ end
+ if not core.safe_file_write(core.auth_file_path, table.concat(content, "\n")) then
+ error(core.auth_file_path.." could not be written to")
+ end
+end
+
+read_auth_file()
+
+core.builtin_auth_handler = {
+ get_auth = function(name)
+ assert(type(name) == "string")
+ -- Figure out what password to use for a new player (singleplayer
+ -- always has an empty password, otherwise use default, which is
+ -- usually empty too)
+ local new_password_hash = ""
+ -- If not in authentication table, return nil
+ if not core.auth_table[name] then
+ return nil
+ end
+ -- Figure out what privileges the player should have.
+ -- Take a copy of the privilege table
+ local privileges = {}
+ for priv, _ in pairs(core.auth_table[name].privileges) do
+ privileges[priv] = true
+ end
+ -- If singleplayer, give all privileges except those marked as give_to_singleplayer = false
+ if core.is_singleplayer() then
+ for priv, def in pairs(core.registered_privileges) do
+ if def.give_to_singleplayer then
+ privileges[priv] = true
+ end
+ end
+ -- For the admin, give everything
+ elseif name == core.settings:get("name") then
+ for priv, def in pairs(core.registered_privileges) do
+ privileges[priv] = true
+ end
+ end
+ -- All done
+ return {
+ password = core.auth_table[name].password,
+ privileges = privileges,
+ -- Is set to nil if unknown
+ last_login = core.auth_table[name].last_login,
+ }
+ end,
+ create_auth = function(name, password)
+ assert(type(name) == "string")
+ assert(type(password) == "string")
+ core.log('info', "Built-in authentication handler adding player '"..name.."'")
+ core.auth_table[name] = {
+ password = password,
+ privileges = core.string_to_privs(core.settings:get("default_privs")),
+ last_login = os.time(),
+ }
+ save_auth_file()
+ end,
+ set_password = function(name, password)
+ assert(type(name) == "string")
+ assert(type(password) == "string")
+ if not core.auth_table[name] then
+ core.builtin_auth_handler.create_auth(name, password)
+ else
+ core.log('info', "Built-in authentication handler setting password of player '"..name.."'")
+ core.auth_table[name].password = password
+ save_auth_file()
+ end
+ return true
+ end,
+ set_privileges = function(name, privileges)
+ assert(type(name) == "string")
+ assert(type(privileges) == "table")
+ if not core.auth_table[name] then
+ core.builtin_auth_handler.create_auth(name,
+ core.get_password_hash(name,
+ core.settings:get("default_password")))
+ end
+ core.auth_table[name].privileges = privileges
+ core.notify_authentication_modified(name)
+ save_auth_file()
+ end,
+ reload = function()
+ read_auth_file()
+ return true
+ end,
+ record_login = function(name)
+ assert(type(name) == "string")
+ assert(core.auth_table[name]).last_login = os.time()
+ save_auth_file()
+ end,
+}
+
+function core.register_authentication_handler(handler)
+ if core.registered_auth_handler then
+ error("Add-on authentication handler already registered by "..core.registered_auth_handler_modname)
+ end
+ core.registered_auth_handler = handler
+ core.registered_auth_handler_modname = core.get_current_modname()
+ handler.mod_origin = core.registered_auth_handler_modname
+end
+
+function core.get_auth_handler()
+ return core.registered_auth_handler or core.builtin_auth_handler
+end
+
+local function auth_pass(name)
+ return function(...)
+ local auth_handler = core.get_auth_handler()
+ if auth_handler[name] then
+ return auth_handler[name](...)
+ end
+ return false
+ end
+end
+
+core.set_player_password = auth_pass("set_password")
+core.set_player_privs = auth_pass("set_privileges")
+core.auth_reload = auth_pass("reload")
+
+
+local record_login = auth_pass("record_login")
+
+core.register_on_joinplayer(function(player)
+ record_login(player:get_player_name())
+end)
+
+core.register_on_prejoinplayer(function(name, ip)
+ local auth = core.auth_table
+ if auth[name] ~= nil then
+ return
+ end
+
+ local name_lower = name:lower()
+ for k in pairs(auth) do
+ if k:lower() == name_lower then
+ return string.format("\nCannot create new player called '%s'. "..
+ "Another account called '%s' is already registered. "..
+ "Please check the spelling if it's your account "..
+ "or use a different nickname.", name, k)
+ end
+ end
+end)
diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua
new file mode 100644
index 000000000..3bd8f2f9c
--- /dev/null
+++ b/builtin/game/chatcommands.lua
@@ -0,0 +1,968 @@
+-- Minetest: builtin/game/chatcommands.lua
+
+--
+-- Chat command handler
+--
+
+core.chatcommands = core.registered_chatcommands -- BACKWARDS COMPATIBILITY
+
+core.register_on_chat_message(function(name, message)
+ if message:sub(1,1) ~= "/" then
+ return
+ end
+
+ local cmd, param = string.match(message, "^/([^ ]+) *(.*)")
+ if not cmd then
+ core.chat_send_player(name, "-!- Empty command")
+ return true
+ end
+
+ param = param or ""
+
+ local cmd_def = core.registered_chatcommands[cmd]
+ if not cmd_def then
+ core.chat_send_player(name, "-!- Invalid command: " .. 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 success, message = cmd_def.func(name, param)
+ if message then
+ core.chat_send_player(name, message)
+ end
+ else
+ core.chat_send_player(name, "You don't have permission"
+ .. " to run this command (missing privileges: "
+ .. table.concat(missing_privs, ", ") .. ")")
+ end
+ return true -- Handled chat message
+end)
+
+if core.settings:get_bool("profiler.load") then
+ -- Run after register_chatcommand and its register_on_chat_message
+ -- Before any chattcommands that should be profiled
+ profiler.init_chatcommand()
+end
+
+-- Parses a "range" string in the format of "here (number)" or
+-- "(x1, y1, z1) (x2, y2, z2)", returning two position vectors
+local function parse_range_str(player_name, str)
+ local p1, p2
+ local args = str:split(" ")
+
+ if args[1] == "here" then
+ p1, p2 = core.get_player_radius_area(player_name, tonumber(args[2]))
+ if p1 == nil then
+ return false, "Unable to get player " .. player_name .. " position"
+ end
+ else
+ p1, p2 = core.string_to_area(str)
+ if p1 == nil then
+ return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)"
+ end
+ end
+
+ return p1, p2
+end
+
+--
+-- Chat commands
+--
+core.register_chatcommand("me", {
+ params = "<action>",
+ description = "Display chat action (e.g., '/me orders a pizza' displays"
+ .. " '<player name> orders a pizza')",
+ privs = {shout=true},
+ func = function(name, param)
+ core.chat_send_all("* " .. name .. " " .. param)
+ end,
+})
+
+core.register_chatcommand("admin", {
+ description = "Show the name of the server owner",
+ func = function(name)
+ local admin = minetest.settings:get("name")
+ if admin then
+ return true, "The administrator of this server is "..admin.."."
+ else
+ return false, "There's no administrator named in the config file."
+ end
+ end,
+})
+
+core.register_chatcommand("privs", {
+ params = "<name>",
+ description = "Print privileges of player",
+ func = function(caller, param)
+ param = param:trim()
+ local name = (param ~= "" and param or caller)
+ return true, "Privileges of " .. name .. ": "
+ .. core.privs_to_string(
+ core.get_player_privs(name), ' ')
+ end,
+})
+
+local function handle_grant_command(caller, grantname, grantprivstr)
+ local caller_privs = minetest.get_player_privs(caller)
+ if not (caller_privs.privs or caller_privs.basic_privs) then
+ return false, "Your privileges are insufficient."
+ end
+
+ if not core.get_auth_handler().get_auth(grantname) then
+ return false, "Player " .. grantname .. " does not exist."
+ end
+ local grantprivs = core.string_to_privs(grantprivstr)
+ if grantprivstr == "all" then
+ grantprivs = core.registered_privileges
+ end
+ local privs = core.get_player_privs(grantname)
+ local privs_unknown = ""
+ local basic_privs =
+ core.string_to_privs(core.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."
+ end
+ if not core.registered_privileges[priv] then
+ privs_unknown = privs_unknown .. "Unknown privilege: " .. priv .. "\n"
+ end
+ privs[priv] = true
+ end
+ if privs_unknown ~= "" then
+ return false, privs_unknown
+ end
+ core.set_player_privs(grantname, privs)
+ core.log("action", caller..' granted ('..core.privs_to_string(grantprivs, ', ')..') privileges to '..grantname)
+ if grantname ~= caller then
+ core.chat_send_player(grantname, caller
+ .. " granted you privileges: "
+ .. core.privs_to_string(grantprivs, ' '))
+ end
+ return true, "Privileges of " .. grantname .. ": "
+ .. core.privs_to_string(
+ core.get_player_privs(grantname), ' ')
+end
+
+core.register_chatcommand("grant", {
+ params = "<name> <privilege>|all",
+ description = "Give privilege to player",
+ func = function(name, param)
+ local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)")
+ if not grantname or not grantprivstr then
+ return false, "Invalid parameters (see /help grant)"
+ end
+ return handle_grant_command(name, grantname, grantprivstr)
+ end,
+})
+
+core.register_chatcommand("grantme", {
+ params = "<privilege>|all",
+ description = "Grant privileges to yourself",
+ func = function(name, param)
+ if param == "" then
+ return false, "Invalid parameters (see /help grantme)"
+ end
+ return handle_grant_command(name, name, param)
+ end,
+})
+
+core.register_chatcommand("revoke", {
+ params = "<name> <privilege>|all",
+ description = "Remove privilege from player",
+ privs = {},
+ func = function(name, param)
+ if not core.check_player_privs(name, {privs=true}) and
+ not core.check_player_privs(name, {basic_privs=true}) then
+ return false, "Your privileges are insufficient."
+ end
+ local revoke_name, revoke_priv_str = string.match(param, "([^ ]+) (.+)")
+ if not revoke_name or not revoke_priv_str then
+ return false, "Invalid parameters (see /help revoke)"
+ elseif not core.get_auth_handler().get_auth(revoke_name) then
+ return false, "Player " .. revoke_name .. " does not exist."
+ end
+ local revoke_privs = core.string_to_privs(revoke_priv_str)
+ local privs = core.get_player_privs(revoke_name)
+ local basic_privs =
+ core.string_to_privs(core.settings:get("basic_privs") or "interact,shout")
+ for priv, _ in pairs(revoke_privs) do
+ if not basic_privs[priv] and
+ not core.check_player_privs(name, {privs=true}) then
+ return false, "Your privileges are insufficient."
+ end
+ end
+ if revoke_priv_str == "all" then
+ privs = {}
+ else
+ for priv, _ in pairs(revoke_privs) do
+ privs[priv] = nil
+ end
+ end
+ core.set_player_privs(revoke_name, privs)
+ core.log("action", name..' revoked ('
+ ..core.privs_to_string(revoke_privs, ', ')
+ ..') privileges from '..revoke_name)
+ if revoke_name ~= name then
+ core.chat_send_player(revoke_name, name
+ .. " revoked privileges from you: "
+ .. core.privs_to_string(revoke_privs, ' '))
+ end
+ return true, "Privileges of " .. revoke_name .. ": "
+ .. core.privs_to_string(
+ core.get_player_privs(revoke_name), ' ')
+ end,
+})
+
+core.register_chatcommand("setpassword", {
+ params = "<name> <password>",
+ description = "Set player's password",
+ privs = {password=true},
+ func = function(name, param)
+ local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$")
+ if not toname then
+ toname = param:match("^([^ ]+) *$")
+ raw_password = nil
+ end
+ if not toname then
+ return false, "Name field required"
+ end
+ local act_str_past = "?"
+ local act_str_pres = "?"
+ if not raw_password then
+ core.set_player_password(toname, "")
+ act_str_past = "cleared"
+ act_str_pres = "clears"
+ else
+ core.set_player_password(toname,
+ core.get_password_hash(toname,
+ raw_password))
+ act_str_past = "set"
+ act_str_pres = "sets"
+ end
+ if toname ~= name then
+ core.chat_send_player(toname, "Your password was "
+ .. act_str_past .. " by " .. name)
+ end
+
+ core.log("action", name .. " " .. act_str_pres
+ .. " password of " .. toname .. ".")
+
+ return true, "Password of player \"" .. toname .. "\" " .. act_str_past
+ end,
+})
+
+core.register_chatcommand("clearpassword", {
+ params = "<name>",
+ description = "Set empty password",
+ privs = {password=true},
+ func = function(name, param)
+ local toname = param
+ if toname == "" then
+ return false, "Name field required"
+ end
+ core.set_player_password(toname, '')
+
+ core.log("action", name .. " clears password of " .. toname .. ".")
+
+ return true, "Password of player \"" .. toname .. "\" cleared"
+ end,
+})
+
+core.register_chatcommand("auth_reload", {
+ params = "",
+ description = "Reload authentication data",
+ privs = {server=true},
+ func = function(name, param)
+ local done = core.auth_reload()
+ return done, (done and "Done." or "Failed.")
+ end,
+})
+
+core.register_chatcommand("remove_player", {
+ params = "<name>",
+ description = "Remove player data",
+ privs = {server=true},
+ func = function(name, param)
+ local toname = param
+ if toname == "" then
+ return false, "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."
+ elseif rc == 1 then
+ return true, "No such player \"" .. toname .. "\" to remove."
+ elseif rc == 2 then
+ return true, "Player \"" .. toname .. "\" is connected, cannot remove."
+ end
+
+ return false, "Unhandled remove_player return code " .. rc .. ""
+ end,
+})
+
+core.register_chatcommand("teleport", {
+ params = "<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>",
+ description = "Teleport to player or position",
+ privs = {teleport=true},
+ func = function(name, param)
+ -- Returns (pos, true) if found, otherwise (pos, false)
+ local function find_free_position_near(pos)
+ local tries = {
+ {x=1,y=0,z=0},
+ {x=-1,y=0,z=0},
+ {x=0,y=0,z=1},
+ {x=0,y=0,z=-1},
+ }
+ for _, d in ipairs(tries) do
+ local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z}
+ local n = core.get_node_or_nil(p)
+ if n and n.name then
+ local def = core.registered_nodes[n.name]
+ if def and not def.walkable then
+ return p, true
+ end
+ end
+ end
+ return pos, false
+ end
+
+ local teleportee = nil
+ local p = {}
+ p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
+ p.x = tonumber(p.x)
+ p.y = tonumber(p.y)
+ p.z = tonumber(p.z)
+ if p.x and p.y and p.z then
+ local lm = 31000
+ if p.x < -lm or p.x > lm or p.y < -lm or p.y > lm or p.z < -lm or p.z > lm then
+ return false, "Cannot teleport out of map bounds!"
+ end
+ teleportee = core.get_player_by_name(name)
+ if teleportee then
+ teleportee:setpos(p)
+ return true, "Teleporting to "..core.pos_to_string(p)
+ end
+ end
+
+ local teleportee = nil
+ local p = nil
+ local target_name = nil
+ target_name = param:match("^([^ ]+)$")
+ teleportee = core.get_player_by_name(name)
+ if target_name then
+ local target = core.get_player_by_name(target_name)
+ if target then
+ p = target:getpos()
+ end
+ end
+ if teleportee and p then
+ p = find_free_position_near(p)
+ teleportee:setpos(p)
+ return true, "Teleporting to " .. target_name
+ .. " at "..core.pos_to_string(p)
+ end
+
+ if not core.check_player_privs(name, {bring=true}) then
+ return false, "You don't have permission to teleport other players (missing bring privilege)"
+ end
+
+ local teleportee = nil
+ local p = {}
+ local teleportee_name = nil
+ teleportee_name, p.x, p.y, p.z = param:match(
+ "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
+ p.x, p.y, p.z = tonumber(p.x), tonumber(p.y), tonumber(p.z)
+ if teleportee_name then
+ teleportee = core.get_player_by_name(teleportee_name)
+ end
+ if teleportee and p.x and p.y and p.z then
+ teleportee:setpos(p)
+ return true, "Teleporting " .. teleportee_name
+ .. " to " .. core.pos_to_string(p)
+ end
+
+ local teleportee = nil
+ local p = nil
+ local teleportee_name = nil
+ local target_name = nil
+ teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$")
+ if teleportee_name then
+ teleportee = core.get_player_by_name(teleportee_name)
+ end
+ if target_name then
+ local target = core.get_player_by_name(target_name)
+ if target then
+ p = target:getpos()
+ end
+ end
+ if teleportee and p then
+ p = find_free_position_near(p)
+ teleportee:setpos(p)
+ return true, "Teleporting " .. teleportee_name
+ .. " to " .. target_name
+ .. " at " .. core.pos_to_string(p)
+ end
+
+ return false, 'Invalid parameters ("' .. param
+ .. '") or player not found (see /help teleport)'
+ end,
+})
+
+core.register_chatcommand("set", {
+ params = "[-n] <name> <value> | <name>",
+ description = "Set or read server configuration setting",
+ privs = {server=true},
+ func = function(name, param)
+ local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)")
+ if arg and arg == "-n" and setname and setvalue then
+ core.settings:set(setname, setvalue)
+ return true, setname .. " = " .. setvalue
+ end
+ local 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."
+ end
+ core.settings:set(setname, setvalue)
+ return true, setname .. " = " .. setvalue
+ end
+ local setname = string.match(param, "([^ ]+)")
+ if setname then
+ local setvalue = core.settings:get(setname)
+ if not setvalue then
+ setvalue = "<not set>"
+ end
+ return true, setname .. " = " .. setvalue
+ end
+ return false, "Invalid parameters (see /help set)."
+ end,
+})
+
+local function emergeblocks_callback(pos, action, num_calls_remaining, ctx)
+ if ctx.total_blocks == 0 then
+ ctx.total_blocks = num_calls_remaining + 1
+ ctx.current_blocks = 0
+ end
+ ctx.current_blocks = ctx.current_blocks + 1
+
+ if ctx.current_blocks == ctx.total_blocks then
+ core.chat_send_player(ctx.requestor_name,
+ string.format("Finished emerging %d blocks in %.2fms.",
+ ctx.total_blocks, (os.clock() - ctx.start_time) * 1000))
+ end
+end
+
+local function emergeblocks_progress_update(ctx)
+ if ctx.current_blocks ~= ctx.total_blocks then
+ core.chat_send_player(ctx.requestor_name,
+ string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)",
+ ctx.current_blocks, ctx.total_blocks,
+ (ctx.current_blocks / ctx.total_blocks) * 100))
+
+ core.after(2, emergeblocks_progress_update, ctx)
+ end
+end
+
+core.register_chatcommand("emergeblocks", {
+ params = "(here [radius]) | (<pos1> <pos2>)",
+ description = "Load (or, if nonexistent, generate) map blocks "
+ .. "contained in area pos1 to pos2",
+ privs = {server=true},
+ func = function(name, param)
+ local p1, p2 = parse_range_str(name, param)
+ if p1 == false then
+ return false, p2
+ end
+
+ local context = {
+ current_blocks = 0,
+ total_blocks = 0,
+ start_time = os.clock(),
+ requestor_name = name
+ }
+
+ core.emerge_area(p1, p2, emergeblocks_callback, context)
+ core.after(2, emergeblocks_progress_update, context)
+
+ return true, "Started emerge of area ranging from " ..
+ core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
+ end,
+})
+
+core.register_chatcommand("deleteblocks", {
+ params = "(here [radius]) | (<pos1> <pos2>)",
+ description = "Delete map blocks contained in area pos1 to pos2",
+ privs = {server=true},
+ func = function(name, param)
+ local p1, p2 = parse_range_str(name, param)
+ if p1 == false then
+ return false, p2
+ end
+
+ if core.delete_area(p1, p2) then
+ return true, "Successfully cleared area ranging from " ..
+ core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
+ else
+ return false, "Failed to clear one or more blocks in area"
+ end
+ end,
+})
+
+core.register_chatcommand("fixlight", {
+ params = "(here [radius]) | (<pos1> <pos2>)",
+ description = "Resets lighting in the area between pos1 and pos2",
+ privs = {server = true},
+ func = function(name, param)
+ local p1, p2 = parse_range_str(name, param)
+ if p1 == false then
+ return false, p2
+ end
+
+ if core.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)
+ else
+ return false, "Failed to load one or more blocks in area"
+ end
+ end,
+})
+
+core.register_chatcommand("mods", {
+ params = "",
+ description = "List mods installed on the server",
+ privs = {},
+ func = function(name, param)
+ return true, table.concat(core.get_modnames(), ", ")
+ end,
+})
+
+local function handle_give_command(cmd, giver, receiver, stackstring)
+ core.log("action", giver .. " invoked " .. cmd
+ .. ', stackstring="' .. stackstring .. '"')
+ local itemstack = ItemStack(stackstring)
+ if itemstack:is_empty() then
+ return false, "Cannot give an empty item"
+ elseif not itemstack:is_known() then
+ return false, "Cannot give an unknown item"
+ end
+ local receiverref = core.get_player_by_name(receiver)
+ if receiverref == nil then
+ return false, receiver .. " is not a known player"
+ end
+ local leftover = receiverref:get_inventory():add_item("main", itemstack)
+ local partiality
+ if leftover:is_empty() then
+ partiality = ""
+ elseif leftover:get_count() == itemstack:get_count() then
+ partiality = "could not be "
+ else
+ partiality = "partially "
+ end
+ -- The actual item stack string may be different from what the "giver"
+ -- entered (e.g. big numbers are always interpreted as 2^16-1).
+ stackstring = itemstack:to_string()
+ if giver == receiver then
+ return true, ("%q %sadded to inventory.")
+ :format(stackstring, partiality)
+ else
+ core.chat_send_player(receiver, ("%q %sadded to inventory.")
+ :format(stackstring, partiality))
+ return true, ("%q %sadded to %s's inventory.")
+ :format(stackstring, partiality, receiver)
+ end
+end
+
+core.register_chatcommand("give", {
+ params = "<name> <ItemString>",
+ description = "Give item to player",
+ privs = {give=true},
+ func = function(name, param)
+ local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
+ if not toname or not itemstring then
+ return false, "Name and ItemString required"
+ end
+ return handle_give_command("/give", name, toname, itemstring)
+ end,
+})
+
+core.register_chatcommand("giveme", {
+ params = "<ItemString>",
+ description = "Give item to yourself",
+ privs = {give=true},
+ func = function(name, param)
+ local itemstring = string.match(param, "(.+)$")
+ if not itemstring then
+ return false, "ItemString required"
+ end
+ return handle_give_command("/giveme", name, name, itemstring)
+ end,
+})
+
+core.register_chatcommand("spawnentity", {
+ params = "<EntityName> [<X>,<Y>,<Z>]",
+ description = "Spawn entity at given (or your) position",
+ privs = {give=true, interact=true},
+ func = function(name, param)
+ local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
+ if not entityname then
+ return false, "EntityName required"
+ end
+ core.log("action", ("%s invokes /spawnentity, entityname=%q")
+ :format(name, entityname))
+ local player = core.get_player_by_name(name)
+ if player == nil then
+ core.log("error", "Unable to spawn entity, player is nil")
+ return false, "Unable to spawn entity, player is nil"
+ end
+ if p == "" then
+ p = player:getpos()
+ else
+ p = core.string_to_pos(p)
+ if p == nil then
+ return false, "Invalid parameters ('" .. param .. "')"
+ end
+ end
+ p.y = p.y + 1
+ core.add_entity(p, entityname)
+ return true, ("%q spawned."):format(entityname)
+ end,
+})
+
+core.register_chatcommand("pulverize", {
+ params = "",
+ description = "Destroy item in hand",
+ func = function(name, param)
+ local player = core.get_player_by_name(name)
+ if not player then
+ core.log("error", "Unable to pulverize, no player.")
+ return false, "Unable to pulverize, no player."
+ end
+ if player:get_wielded_item():is_empty() then
+ return false, "Unable to pulverize, no item in hand."
+ end
+ player:set_wielded_item(nil)
+ return true, "An item was pulverized."
+ end,
+})
+
+-- Key = player name
+core.rollback_punch_callbacks = {}
+
+core.register_on_punchnode(function(pos, node, puncher)
+ local name = puncher and puncher:get_player_name()
+ if name and core.rollback_punch_callbacks[name] then
+ core.rollback_punch_callbacks[name](pos, node, puncher)
+ core.rollback_punch_callbacks[name] = nil
+ end
+end)
+
+core.register_chatcommand("rollback_check", {
+ params = "[<range>] [<seconds>] [limit]",
+ description = "Check who last touched a node or a node near it"
+ .. " within the time specified by <seconds>. Default: range = 0,"
+ .. " seconds = 86400 = 24h, limit = 5",
+ privs = {rollback=true},
+ func = function(name, param)
+ if not core.settings:get_bool("enable_rollback_recording") then
+ return false, "Rollback functions are disabled."
+ end
+ local range, seconds, limit =
+ param:match("(%d+) *(%d*) *(%d*)")
+ range = tonumber(range) or 0
+ seconds = tonumber(seconds) or 86400
+ limit = tonumber(limit) or 5
+ if limit > 100 then
+ return false, "That limit is too high!"
+ end
+
+ core.rollback_punch_callbacks[name] = function(pos, node, puncher)
+ local name = puncher:get_player_name()
+ core.chat_send_player(name, "Checking " .. core.pos_to_string(pos) .. "...")
+ local actions = core.rollback_get_node_actions(pos, range, seconds, limit)
+ if not actions then
+ core.chat_send_player(name, "Rollback functions are disabled")
+ return
+ end
+ local num_actions = #actions
+ if num_actions == 0 then
+ core.chat_send_player(name, "Nobody has touched"
+ .. " the specified location in "
+ .. seconds .. " seconds")
+ return
+ end
+ local time = os.time()
+ for i = num_actions, 1, -1 do
+ local action = actions[i]
+ core.chat_send_player(name,
+ ("%s %s %s -> %s %d seconds ago.")
+ :format(
+ core.pos_to_string(action.pos),
+ action.actor,
+ action.oldnode.name,
+ action.newnode.name,
+ time - action.time))
+ end
+ end
+
+ return true, "Punch a node (range=" .. range .. ", seconds="
+ .. seconds .. "s, limit=" .. limit .. ")"
+ end,
+})
+
+core.register_chatcommand("rollback", {
+ params = "<player name> [<seconds>] | :<actor> [<seconds>]",
+ description = "Revert actions of a player. Default for <seconds> is 60",
+ privs = {rollback=true},
+ func = function(name, param)
+ if not core.settings:get_bool("enable_rollback_recording") then
+ return false, "Rollback functions are disabled."
+ end
+ local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
+ if not target_name then
+ local player_name = nil
+ player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
+ if not player_name then
+ return false, "Invalid parameters. See /help rollback"
+ .. " and /help rollback_check."
+ end
+ target_name = "player:"..player_name
+ end
+ seconds = tonumber(seconds) or 60
+ core.chat_send_player(name, "Reverting actions of "
+ .. target_name .. " since "
+ .. seconds .. " seconds.")
+ local success, log = core.rollback_revert_actions_by(
+ target_name, seconds)
+ local response = ""
+ if #log > 100 then
+ response = "(log is too long to show)\n"
+ else
+ for _, line in pairs(log) do
+ response = response .. line .. "\n"
+ end
+ end
+ response = response .. "Reverting actions "
+ .. (success and "succeeded." or "FAILED.")
+ return success, response
+ end,
+})
+
+core.register_chatcommand("status", {
+ description = "Print server status",
+ func = function(name, param)
+ return true, core.get_server_status()
+ end,
+})
+
+core.register_chatcommand("time", {
+ params = "<0..23>:<0..59> | <0..24000>",
+ description = "Set time of day",
+ privs = {},
+ func = function(name, param)
+ if param == "" then
+ local current_time = math.floor(core.get_timeofday() * 1440)
+ local minutes = current_time % 60
+ local hour = (current_time - minutes) / 60
+ return true, ("Current time is %d:%02d"):format(hour, minutes)
+ end
+ local player_privs = core.get_player_privs(name)
+ if not player_privs.settime then
+ return false, "You don't have permission to run this command " ..
+ "(missing privilege: settime)."
+ end
+ local hour, minute = param:match("^(%d+):(%d+)$")
+ if not hour then
+ local new_time = tonumber(param)
+ if not new_time then
+ return false, "Invalid time."
+ end
+ -- Backward compatibility.
+ core.set_timeofday((new_time % 24000) / 24000)
+ core.log("action", name .. " sets time to " .. new_time)
+ return true, "Time of day changed."
+ end
+ hour = tonumber(hour)
+ minute = tonumber(minute)
+ if hour < 0 or hour > 23 then
+ return false, "Invalid hour (must be between 0 and 23 inclusive)."
+ elseif minute < 0 or minute > 59 then
+ return false, "Invalid minute (must be between 0 and 59 inclusive)."
+ end
+ core.set_timeofday((hour * 60 + minute) / 1440)
+ core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
+ return true, "Time of day changed."
+ end,
+})
+
+core.register_chatcommand("days", {
+ description = "Display day count",
+ func = function(name, param)
+ return true, "Current day is " .. core.get_day_count()
+ end
+})
+
+core.register_chatcommand("shutdown", {
+ description = "Shutdown server",
+ params = "[delay_in_seconds (non-negative number, or -1 to cancel)] [reconnect] [message]",
+ privs = {server=true},
+ func = function(name, param)
+ local delay, reconnect, message = param:match("([^ ][-]?[0-9]+)([^ ]+)(.*)")
+ message = message or ""
+
+ if delay ~= "" then
+ delay = tonumber(delay) or 0
+ else
+ delay = 0
+ core.log("action", name .. " shuts down server")
+ core.chat_send_all("*** Server shutting down (operator request).")
+ end
+ core.request_shutdown(message:trim(), core.is_yes(reconnect), delay)
+ end,
+})
+
+core.register_chatcommand("ban", {
+ params = "<name>",
+ description = "Ban IP of player",
+ privs = {ban=true},
+ func = function(name, param)
+ if param == "" then
+ return true, "Ban list: " .. core.get_ban_list()
+ end
+ if not core.get_player_by_name(param) then
+ return false, "No such player."
+ end
+ if not core.ban_player(param) then
+ return false, "Failed to ban player."
+ end
+ local desc = core.get_ban_description(param)
+ core.log("action", name .. " bans " .. desc .. ".")
+ return true, "Banned " .. desc .. "."
+ end,
+})
+
+core.register_chatcommand("unban", {
+ params = "<name/ip>",
+ description = "Remove IP ban",
+ privs = {ban=true},
+ func = function(name, param)
+ if not core.unban_player_or_ip(param) then
+ return false, "Failed to unban player/IP."
+ end
+ core.log("action", name .. " unbans " .. param)
+ return true, "Unbanned " .. param
+ end,
+})
+
+core.register_chatcommand("kick", {
+ params = "<name> [reason]",
+ description = "Kick a player",
+ privs = {kick=true},
+ func = function(name, param)
+ local tokick, reason = param:match("([^ ]+) (.+)")
+ tokick = tokick or param
+ if not core.kick_player(tokick, reason) then
+ return false, "Failed to kick player " .. tokick
+ end
+ local log_reason = ""
+ if reason then
+ log_reason = " with reason \"" .. reason .. "\""
+ end
+ core.log("action", name .. " kicks " .. tokick .. log_reason)
+ return true, "Kicked " .. tokick
+ end,
+})
+
+core.register_chatcommand("clearobjects", {
+ params = "[full|quick]",
+ description = "Clear all objects in world",
+ privs = {server=true},
+ func = function(name, param)
+ local options = {}
+ if param == "" or param == "full" then
+ options.mode = "full"
+ elseif param == "quick" then
+ options.mode = "quick"
+ else
+ return false, "Invalid usage, see /help clearobjects."
+ end
+
+ core.log("action", name .. " clears all objects ("
+ .. options.mode .. " mode).")
+ core.chat_send_all("Clearing all objects. This may take long."
+ .. " You may experience a timeout. (by "
+ .. name .. ")")
+ core.clear_objects(options)
+ core.log("action", "Object clearing done.")
+ core.chat_send_all("*** Cleared all objects.")
+ end,
+})
+
+core.register_chatcommand("msg", {
+ params = "<name> <message>",
+ description = "Send a private message",
+ privs = {shout=true},
+ func = function(name, param)
+ local sendto, message = param:match("^(%S+)%s(.+)$")
+ if not sendto then
+ return false, "Invalid usage, see /help msg."
+ end
+ if not core.get_player_by_name(sendto) then
+ return false, "The player " .. sendto
+ .. " is not online."
+ end
+ core.log("action", "PM from " .. name .. " to " .. sendto
+ .. ": " .. message)
+ core.chat_send_player(sendto, "PM from " .. name .. ": "
+ .. message)
+ return true, "Message sent."
+ end,
+})
+
+core.register_chatcommand("last-login", {
+ params = "[name]",
+ description = "Get the last login time of a player",
+ func = function(name, param)
+ if param == "" then
+ param = name
+ end
+ local pauth = core.get_auth_handler().get_auth(param)
+ if pauth and pauth.last_login then
+ -- Time in UTC, ISO 8601 format
+ return true, "Last login time was " ..
+ os.date("!%Y-%m-%dT%H:%M:%SZ", pauth.last_login)
+ end
+ return false, "Last login time is unknown"
+ end,
+})
+
+core.register_chatcommand("clearinv", {
+ params = "[name]",
+ description = "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 run this command (missing privilege: server)"
+ end
+ player = core.get_player_by_name(param)
+ core.chat_send_player(param, name.." cleared your inventory.")
+ else
+ player = core.get_player_by_name(name)
+ end
+
+ if player then
+ player:get_inventory():set_list("main", {})
+ 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."
+ else
+ return false, "Player must be online to clear inventory!"
+ end
+ end,
+})
diff --git a/builtin/game/constants.lua b/builtin/game/constants.lua
new file mode 100644
index 000000000..50c515b24
--- /dev/null
+++ b/builtin/game/constants.lua
@@ -0,0 +1,27 @@
+-- Minetest: builtin/constants.lua
+
+--
+-- Constants values for use with the Lua API
+--
+
+-- mapnode.h
+-- Built-in Content IDs (for use with VoxelManip API)
+core.CONTENT_UNKNOWN = 125
+core.CONTENT_AIR = 126
+core.CONTENT_IGNORE = 127
+
+-- emerge.h
+-- Block emerge status constants (for use with core.emerge_area)
+core.EMERGE_CANCELLED = 0
+core.EMERGE_ERRORED = 1
+core.EMERGE_FROM_MEMORY = 2
+core.EMERGE_FROM_DISK = 3
+core.EMERGE_GENERATED = 4
+
+-- constants.h
+-- Size of mapblocks in nodes
+core.MAP_BLOCKSIZE = 16
+
+-- light.h
+-- Maximum value for node 'light_source' parameter
+core.LIGHT_MAX = 14
diff --git a/builtin/game/deprecated.lua b/builtin/game/deprecated.lua
new file mode 100644
index 000000000..1a9a96f2a
--- /dev/null
+++ b/builtin/game/deprecated.lua
@@ -0,0 +1,72 @@
+-- Minetest: builtin/deprecated.lua
+
+--
+-- Default material types
+--
+local function digprop_err()
+ core.log("deprecated", "The core.digprop_* functions are obsolete and need to be replaced by item groups.")
+end
+
+core.digprop_constanttime = digprop_err
+core.digprop_stonelike = digprop_err
+core.digprop_dirtlike = digprop_err
+core.digprop_gravellike = digprop_err
+core.digprop_woodlike = digprop_err
+core.digprop_leaveslike = digprop_err
+core.digprop_glasslike = digprop_err
+
+function core.node_metadata_inventory_move_allow_all()
+ core.log("deprecated", "core.node_metadata_inventory_move_allow_all is obsolete and does nothing.")
+end
+
+function core.add_to_creative_inventory(itemstring)
+ core.log("deprecated", "core.add_to_creative_inventory: This function is deprecated and does nothing.")
+end
+
+--
+-- EnvRef
+--
+core.env = {}
+local envref_deprecation_message_printed = false
+setmetatable(core.env, {
+ __index = function(table, key)
+ if not envref_deprecation_message_printed then
+ core.log("deprecated", "core.env:[...] is deprecated and should be replaced with core.[...]")
+ envref_deprecation_message_printed = true
+ end
+ local func = core[key]
+ if type(func) == "function" then
+ rawset(table, key, function(self, ...)
+ return func(...)
+ end)
+ else
+ rawset(table, key, nil)
+ end
+ return rawget(table, key)
+ end
+})
+
+function core.rollback_get_last_node_actor(pos, range, seconds)
+ return core.rollback_get_node_actions(pos, range, seconds, 1)[1]
+end
+
+--
+-- core.setting_*
+--
+
+local settings = core.settings
+
+local function setting_proxy(name)
+ return function(...)
+ core.log("deprecated", "WARNING: minetest.setting_* "..
+ "functions are deprecated. "..
+ "Use methods on the minetest.settings object.")
+ return settings[name](settings, ...)
+ end
+end
+
+core.setting_set = setting_proxy("set")
+core.setting_get = setting_proxy("get")
+core.setting_setbool = setting_proxy("set_bool")
+core.setting_getbool = setting_proxy("get_bool")
+core.setting_save = setting_proxy("write")
diff --git a/builtin/game/detached_inventory.lua b/builtin/game/detached_inventory.lua
new file mode 100644
index 000000000..420e89ff2
--- /dev/null
+++ b/builtin/game/detached_inventory.lua
@@ -0,0 +1,20 @@
+-- Minetest: builtin/detached_inventory.lua
+
+core.detached_inventories = {}
+
+function core.create_detached_inventory(name, callbacks, player_name)
+ local stuff = {}
+ stuff.name = name
+ if callbacks then
+ stuff.allow_move = callbacks.allow_move
+ stuff.allow_put = callbacks.allow_put
+ stuff.allow_take = callbacks.allow_take
+ stuff.on_move = callbacks.on_move
+ stuff.on_put = callbacks.on_put
+ stuff.on_take = callbacks.on_take
+ end
+ stuff.mod_origin = core.get_current_modname() or "??"
+ core.detached_inventories[name] = stuff
+ return core.create_detached_inventory_raw(name, player_name)
+end
+
diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua
new file mode 100644
index 000000000..991962cc3
--- /dev/null
+++ b/builtin/game/falling.lua
@@ -0,0 +1,331 @@
+-- Minetest: builtin/item.lua
+
+local builtin_shared = ...
+
+--
+-- Falling stuff
+--
+
+core.register_entity(":__builtin:falling_node", {
+ initial_properties = {
+ visual = "wielditem",
+ visual_size = {x = 0.667, y = 0.667},
+ textures = {},
+ physical = true,
+ is_visible = false,
+ collide_with_objects = false,
+ collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
+ },
+
+ node = {},
+ meta = {},
+
+ set_node = function(self, node, meta)
+ self.node = node
+ self.meta = meta or {}
+ self.object:set_properties({
+ is_visible = true,
+ textures = {node.name},
+ })
+ end,
+
+ get_staticdata = function(self)
+ local ds = {
+ node = self.node,
+ meta = self.meta,
+ }
+ return core.serialize(ds)
+ end,
+
+ on_activate = function(self, staticdata)
+ self.object:set_armor_groups({immortal = 1})
+
+ local ds = core.deserialize(staticdata)
+ if ds and ds.node then
+ self:set_node(ds.node, ds.meta)
+ elseif ds then
+ self:set_node(ds)
+ elseif staticdata ~= "" then
+ self:set_node({name = staticdata})
+ end
+ end,
+
+ on_step = function(self, dtime)
+ -- Set gravity
+ local acceleration = self.object:getacceleration()
+ if not vector.equals(acceleration, {x = 0, y = -10, z = 0}) then
+ self.object:setacceleration({x = 0, y = -10, z = 0})
+ end
+ -- Turn to actual node when colliding with ground, or continue to move
+ local pos = self.object:getpos()
+ -- Position of bottom center point
+ local bcp = {x = pos.x, y = pos.y - 0.7, z = pos.z}
+ -- 'bcn' is nil for unloaded nodes
+ local bcn = core.get_node_or_nil(bcp)
+ -- Delete on contact with ignore at world edges
+ if bcn and bcn.name == "ignore" then
+ self.object:remove()
+ return
+ end
+ local bcd = bcn and core.registered_nodes[bcn.name]
+ if bcn and
+ (not bcd or bcd.walkable or
+ (core.get_item_group(self.node.name, "float") ~= 0 and
+ bcd.liquidtype ~= "none")) then
+ if bcd and bcd.leveled and
+ bcn.name == self.node.name then
+ local addlevel = self.node.level
+ if not addlevel or addlevel <= 0 then
+ addlevel = bcd.leveled
+ end
+ if core.add_node_level(bcp, addlevel) == 0 then
+ self.object:remove()
+ return
+ end
+ elseif bcd and bcd.buildable_to and
+ (core.get_item_group(self.node.name, "float") == 0 or
+ bcd.liquidtype == "none") then
+ core.remove_node(bcp)
+ return
+ end
+ local np = {x = bcp.x, y = bcp.y + 1, z = bcp.z}
+ -- Check what's here
+ local n2 = core.get_node(np)
+ local nd = core.registered_nodes[n2.name]
+ -- If it's not air or liquid, remove node and replace it with
+ -- it's drops
+ if n2.name ~= "air" and (not nd or nd.liquidtype == "none") then
+ core.remove_node(np)
+ if nd and nd.buildable_to == false then
+ -- Add dropped items
+ local drops = core.get_node_drops(n2, "")
+ for _, dropped_item in pairs(drops) do
+ core.add_item(np, dropped_item)
+ end
+ end
+ -- Run script hook
+ for _, callback in pairs(core.registered_on_dignodes) do
+ callback(np, n2)
+ end
+ end
+ -- Create node and remove entity
+ if core.registered_nodes[self.node.name] then
+ core.add_node(np, self.node)
+ if self.meta then
+ local meta = core.get_meta(np)
+ meta:from_table(self.meta)
+ end
+ end
+ self.object:remove()
+ core.check_for_falling(np)
+ return
+ end
+ local vel = self.object:getvelocity()
+ if vector.equals(vel, {x = 0, y = 0, z = 0}) then
+ local npos = self.object:getpos()
+ self.object:setpos(vector.round(npos))
+ end
+ end
+})
+
+local function spawn_falling_node(p, node, meta)
+ local obj = core.add_entity(p, "__builtin:falling_node")
+ if obj then
+ obj:get_luaentity():set_node(node, meta)
+ end
+end
+
+function core.spawn_falling_node(pos)
+ local node = core.get_node(pos)
+ if node.name == "air" or node.name == "ignore" then
+ return false
+ end
+ local obj = core.add_entity(pos, "__builtin:falling_node")
+ if obj then
+ obj:get_luaentity():set_node(node)
+ core.remove_node(pos)
+ return true
+ end
+ return false
+end
+
+local function drop_attached_node(p)
+ local n = core.get_node(p)
+ core.remove_node(p)
+ for _, item in pairs(core.get_node_drops(n, "")) do
+ local pos = {
+ x = p.x + math.random()/2 - 0.25,
+ y = p.y + math.random()/2 - 0.25,
+ z = p.z + math.random()/2 - 0.25,
+ }
+ core.add_item(pos, item)
+ end
+end
+
+function builtin_shared.check_attached_node(p, n)
+ local def = core.registered_nodes[n.name]
+ local d = {x = 0, y = 0, z = 0}
+ if def.paramtype2 == "wallmounted" 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}
+ else
+ d.y = -1
+ end
+ local p2 = vector.add(p, d)
+ local nn = core.get_node(p2).name
+ local def2 = core.registered_nodes[nn]
+ if def2 and not def2.walkable then
+ return false
+ end
+ return true
+end
+
+--
+-- Some common functions
+--
+
+function core.check_single_for_falling(p)
+ local n = core.get_node(p)
+ if core.get_item_group(n.name, "falling_node") ~= 0 then
+ local p_bottom = {x = p.x, y = p.y - 1, z = p.z}
+ -- Only spawn falling node if node below is loaded
+ local n_bottom = core.get_node_or_nil(p_bottom)
+ local d_bottom = n_bottom and core.registered_nodes[n_bottom.name]
+ if d_bottom and
+
+ (core.get_item_group(n.name, "float") == 0 or
+ d_bottom.liquidtype == "none") and
+
+ (n.name ~= n_bottom.name or (d_bottom.leveled and
+ core.get_node_level(p_bottom) <
+ core.get_node_max_level(p_bottom))) and
+
+ (not d_bottom.walkable or d_bottom.buildable_to) then
+ n.level = core.get_node_level(p)
+ local meta = core.get_meta(p)
+ local metatable = {}
+ if meta ~= nil then
+ metatable = meta:to_table()
+ end
+ core.remove_node(p)
+ spawn_falling_node(p, n, metatable)
+ return true
+ end
+ end
+
+ if core.get_item_group(n.name, "attached_node") ~= 0 then
+ if not builtin_shared.check_attached_node(p, n) then
+ drop_attached_node(p)
+ return true
+ end
+ end
+
+ return false
+end
+
+-- This table is specifically ordered.
+-- We don't walk diagonals, only our direct neighbors, and self.
+-- Down first as likely case, but always before self. The same with sides.
+-- Up must come last, so that things above self will also fall all at once.
+local check_for_falling_neighbors = {
+ {x = -1, y = -1, z = 0},
+ {x = 1, y = -1, z = 0},
+ {x = 0, y = -1, z = -1},
+ {x = 0, y = -1, z = 1},
+ {x = 0, y = -1, z = 0},
+ {x = -1, y = 0, z = 0},
+ {x = 1, y = 0, z = 0},
+ {x = 0, y = 0, z = 1},
+ {x = 0, y = 0, z = -1},
+ {x = 0, y = 0, z = 0},
+ {x = 0, y = 1, z = 0},
+}
+
+function core.check_for_falling(p)
+ -- Round p to prevent falling entities to get stuck.
+ p = vector.round(p)
+
+ -- We make a stack, and manually maintain size for performance.
+ -- Stored in the stack, we will maintain tables with pos, and
+ -- last neighbor visited. This way, when we get back to each
+ -- node, we know which directions we have already walked, and
+ -- which direction is the next to walk.
+ local s = {}
+ local n = 0
+ -- The neighbor order we will visit from our table.
+ local v = 1
+
+ while true do
+ -- Push current pos onto the stack.
+ n = n + 1
+ s[n] = {p = p, v = v}
+ -- Select next node from neighbor list.
+ p = vector.add(p, check_for_falling_neighbors[v])
+ -- Now we check out the node. If it is in need of an update,
+ -- it will let us know in the return value (true = updated).
+ if not core.check_single_for_falling(p) then
+ -- If we don't need to "recurse" (walk) to it then pop
+ -- our previous pos off the stack and continue from there,
+ -- with the v value we were at when we last were at that
+ -- node
+ repeat
+ local pop = s[n]
+ p = pop.p
+ v = pop.v
+ s[n] = nil
+ n = n - 1
+ -- If there's nothing left on the stack, and no
+ -- more sides to walk to, we're done and can exit
+ if n == 0 and v == 11 then
+ return
+ end
+ until v < 11
+ -- The next round walk the next neighbor in list.
+ v = v + 1
+ else
+ -- If we did need to walk the neighbor, then
+ -- start walking it from the walk order start (1),
+ -- and not the order we just pushed up the stack.
+ v = 1
+ end
+ end
+end
+
+--
+-- Global callbacks
+--
+
+local function on_placenode(p, node)
+ core.check_for_falling(p)
+end
+core.register_on_placenode(on_placenode)
+
+local function on_dignode(p, node)
+ core.check_for_falling(p)
+end
+core.register_on_dignode(on_dignode)
+
+local function on_punchnode(p, node)
+ core.check_for_falling(p)
+end
+core.register_on_punchnode(on_punchnode)
+
+--
+-- Globally exported functions
+--
+
+-- TODO remove this function after the 0.4.15 release
+function nodeupdate(p)
+ core.log("deprecated", "nodeupdate: deprecated, please use core.check_for_falling instead")
+ core.check_for_falling(p)
+end
+
+-- TODO remove this function after the 0.4.15 release
+function nodeupdate_single(p)
+ core.log("deprecated", "nodeupdate_single: deprecated, please use core.check_single_for_falling instead")
+ core.check_single_for_falling(p)
+end
diff --git a/builtin/game/features.lua b/builtin/game/features.lua
new file mode 100644
index 000000000..ef85fbbc3
--- /dev/null
+++ b/builtin/game/features.lua
@@ -0,0 +1,33 @@
+-- Minetest: builtin/features.lua
+
+core.features = {
+ glasslike_framed = true,
+ nodebox_as_selectionbox = true,
+ chat_send_player_param3 = true,
+ get_all_craft_recipes_works = true,
+ use_texture_alpha = true,
+ no_legacy_abms = true,
+ texture_names_parens = true,
+ area_store_custom_ids = true,
+ add_entity_with_staticdata = true,
+ no_chat_message_prediction = true,
+}
+
+function core.has_feature(arg)
+ if type(arg) == "table" then
+ local missing_features = {}
+ local result = true
+ for ftr in pairs(arg) do
+ if not core.features[ftr] then
+ missing_features[ftr] = true
+ result = false
+ end
+ end
+ return result, missing_features
+ elseif type(arg) == "string" then
+ if not core.features[arg] then
+ return false, {[arg]=true}
+ end
+ return true, {}
+ end
+end
diff --git a/builtin/game/forceloading.lua b/builtin/game/forceloading.lua
new file mode 100644
index 000000000..7c5537e85
--- /dev/null
+++ b/builtin/game/forceloading.lua
@@ -0,0 +1,100 @@
+-- Prevent anyone else accessing those functions
+local forceload_block = core.forceload_block
+local forceload_free_block = core.forceload_free_block
+core.forceload_block = nil
+core.forceload_free_block = nil
+
+local blocks_forceloaded
+local blocks_temploaded = {}
+local total_forceloaded = 0
+
+local BLOCKSIZE = core.MAP_BLOCKSIZE
+local function get_blockpos(pos)
+ return {
+ x = math.floor(pos.x/BLOCKSIZE),
+ y = math.floor(pos.y/BLOCKSIZE),
+ z = math.floor(pos.z/BLOCKSIZE)}
+end
+
+-- When we create/free a forceload, it's either transient or persistent. We want
+-- to add to/remove from the table that corresponds to the type of forceload, but
+-- we also need the other table because whether we forceload a block depends on
+-- both tables.
+-- This function returns the "primary" table we are adding to/removing from, and
+-- the other table.
+local function get_relevant_tables(transient)
+ if transient then
+ return blocks_temploaded, blocks_forceloaded
+ else
+ return blocks_forceloaded, blocks_temploaded
+ end
+end
+
+function core.forceload_block(pos, transient)
+ local blockpos = get_blockpos(pos)
+ local hash = core.hash_node_position(blockpos)
+ local relevant_table, other_table = get_relevant_tables(transient)
+ if relevant_table[hash] ~= nil then
+ relevant_table[hash] = relevant_table[hash] + 1
+ return true
+ elseif other_table[hash] ~= nil then
+ relevant_table[hash] = 1
+ else
+ if total_forceloaded >= (tonumber(core.settings:get("max_forceloaded_blocks")) or 16) then
+ return false
+ end
+ total_forceloaded = total_forceloaded+1
+ relevant_table[hash] = 1
+ forceload_block(blockpos)
+ return true
+ end
+end
+
+function core.forceload_free_block(pos, transient)
+ local blockpos = get_blockpos(pos)
+ local hash = core.hash_node_position(blockpos)
+ local relevant_table, other_table = get_relevant_tables(transient)
+ if relevant_table[hash] == nil then return end
+ if relevant_table[hash] > 1 then
+ relevant_table[hash] = relevant_table[hash] - 1
+ elseif other_table[hash] ~= nil then
+ relevant_table[hash] = nil
+ else
+ total_forceloaded = total_forceloaded-1
+ relevant_table[hash] = nil
+ forceload_free_block(blockpos)
+ end
+end
+
+-- Keep the forceloaded areas after restart
+local wpath = core.get_worldpath()
+local function read_file(filename)
+ local f = io.open(filename, "r")
+ if f==nil then return {} end
+ local t = f:read("*all")
+ f:close()
+ if t=="" or t==nil then return {} end
+ return core.deserialize(t) or {}
+end
+
+local function write_file(filename, table)
+ local f = io.open(filename, "w")
+ f:write(core.serialize(table))
+ f:close()
+end
+
+blocks_forceloaded = read_file(wpath.."/force_loaded.txt")
+for _, __ in pairs(blocks_forceloaded) do
+ total_forceloaded = total_forceloaded + 1
+end
+
+core.after(5, function()
+ for hash, _ in pairs(blocks_forceloaded) do
+ local blockpos = core.get_position_from_hash(hash)
+ forceload_block(blockpos)
+ end
+end)
+
+core.register_on_shutdown(function()
+ write_file(wpath.."/force_loaded.txt", blocks_forceloaded)
+end)
diff --git a/builtin/game/init.lua b/builtin/game/init.lua
new file mode 100644
index 000000000..e2635f07a
--- /dev/null
+++ b/builtin/game/init.lua
@@ -0,0 +1,36 @@
+
+local scriptpath = core.get_builtin_path()..DIR_DELIM
+local commonpath = scriptpath.."common"..DIR_DELIM
+local gamepath = scriptpath.."game"..DIR_DELIM
+
+-- Shared between builtin files, but
+-- not exposed to outer context
+local builtin_shared = {}
+
+dofile(commonpath.."vector.lua")
+
+dofile(gamepath.."constants.lua")
+assert(loadfile(gamepath.."item.lua"))(builtin_shared)
+dofile(gamepath.."register.lua")
+
+if core.settings:get_bool("profiler.load") then
+ profiler = dofile(scriptpath.."profiler"..DIR_DELIM.."init.lua")
+end
+
+dofile(commonpath .. "after.lua")
+dofile(gamepath.."item_entity.lua")
+dofile(gamepath.."deprecated.lua")
+dofile(gamepath.."misc.lua")
+dofile(gamepath.."privileges.lua")
+dofile(gamepath.."auth.lua")
+dofile(commonpath .. "chatcommands.lua")
+dofile(gamepath.."chatcommands.lua")
+dofile(gamepath.."static_spawn.lua")
+dofile(gamepath.."detached_inventory.lua")
+assert(loadfile(gamepath.."falling.lua"))(builtin_shared)
+dofile(gamepath.."features.lua")
+dofile(gamepath.."voxelarea.lua")
+dofile(gamepath.."forceloading.lua")
+dofile(gamepath.."statbars.lua")
+
+profiler = nil
diff --git a/builtin/game/item.lua b/builtin/game/item.lua
new file mode 100644
index 000000000..ea9681ae2
--- /dev/null
+++ b/builtin/game/item.lua
@@ -0,0 +1,757 @@
+-- Minetest: builtin/item.lua
+
+local builtin_shared = ...
+
+local function copy_pointed_thing(pointed_thing)
+ return {
+ type = pointed_thing.type,
+ above = vector.new(pointed_thing.above),
+ under = vector.new(pointed_thing.under),
+ ref = pointed_thing.ref,
+ }
+end
+
+--
+-- Item definition helpers
+--
+
+function core.inventorycube(img1, img2, img3)
+ img2 = img2 or img1
+ img3 = img3 or img1
+ return "[inventorycube"
+ .. "{" .. img1:gsub("%^", "&")
+ .. "{" .. img2:gsub("%^", "&")
+ .. "{" .. img3:gsub("%^", "&")
+end
+
+function core.get_pointed_thing_position(pointed_thing, above)
+ if pointed_thing.type == "node" then
+ if above then
+ -- The position where a node would be placed
+ return pointed_thing.above
+ end
+ -- The position where a node would be dug
+ return pointed_thing.under
+ elseif pointed_thing.type == "object" then
+ return pointed_thing.ref and pointed_thing.ref:getpos()
+ end
+end
+
+function core.dir_to_facedir(dir, is6d)
+ --account for y if requested
+ if is6d and math.abs(dir.y) > math.abs(dir.x) and math.abs(dir.y) > math.abs(dir.z) then
+
+ --from above
+ if dir.y < 0 then
+ if math.abs(dir.x) > math.abs(dir.z) then
+ if dir.x < 0 then
+ return 19
+ else
+ return 13
+ end
+ else
+ if dir.z < 0 then
+ return 10
+ else
+ return 4
+ end
+ end
+
+ --from below
+ else
+ if math.abs(dir.x) > math.abs(dir.z) then
+ if dir.x < 0 then
+ return 15
+ else
+ return 17
+ end
+ else
+ if dir.z < 0 then
+ return 6
+ else
+ return 8
+ end
+ end
+ end
+
+ --otherwise, place horizontally
+ elseif math.abs(dir.x) > math.abs(dir.z) then
+ if dir.x < 0 then
+ return 3
+ else
+ return 1
+ end
+ else
+ if dir.z < 0 then
+ return 2
+ else
+ return 0
+ end
+ end
+end
+
+-- Table of possible dirs
+local facedir_to_dir = {
+ {x= 0, y=0, z= 1},
+ {x= 1, y=0, z= 0},
+ {x= 0, y=0, z=-1},
+ {x=-1, y=0, z= 0},
+ {x= 0, y=-1, z= 0},
+ {x= 0, y=1, z= 0},
+}
+-- Mapping from facedir value to index in facedir_to_dir.
+local facedir_to_dir_map = {
+ [0]=1, 2, 3, 4,
+ 5, 2, 6, 4,
+ 6, 2, 5, 4,
+ 1, 5, 3, 6,
+ 1, 6, 3, 5,
+ 1, 4, 3, 2,
+}
+function core.facedir_to_dir(facedir)
+ return facedir_to_dir[facedir_to_dir_map[facedir % 32]]
+end
+
+function core.dir_to_wallmounted(dir)
+ if math.abs(dir.y) > math.max(math.abs(dir.x), math.abs(dir.z)) then
+ if dir.y < 0 then
+ return 1
+ else
+ return 0
+ end
+ elseif math.abs(dir.x) > math.abs(dir.z) then
+ if dir.x < 0 then
+ return 3
+ else
+ return 2
+ end
+ else
+ if dir.z < 0 then
+ return 5
+ else
+ return 4
+ end
+ end
+end
+
+-- table of dirs in wallmounted order
+local wallmounted_to_dir = {
+ [0] = {x = 0, y = 1, z = 0},
+ {x = 0, y = -1, z = 0},
+ {x = 1, y = 0, z = 0},
+ {x = -1, y = 0, z = 0},
+ {x = 0, y = 0, z = 1},
+ {x = 0, y = 0, z = -1},
+}
+function core.wallmounted_to_dir(wallmounted)
+ return wallmounted_to_dir[wallmounted % 8]
+end
+
+function core.dir_to_yaw(dir)
+ return -math.atan2(dir.x, dir.z)
+end
+
+function core.yaw_to_dir(yaw)
+ return {x = -math.sin(yaw), y = 0, z = math.cos(yaw)}
+end
+
+function core.is_colored_paramtype(ptype)
+ return (ptype == "color") or (ptype == "colorfacedir") or
+ (ptype == "colorwallmounted")
+end
+
+function core.strip_param2_color(param2, paramtype2)
+ if not core.is_colored_paramtype(paramtype2) then
+ return nil
+ end
+ if paramtype2 == "colorfacedir" then
+ param2 = math.floor(param2 / 32) * 32
+ elseif paramtype2 == "colorwallmounted" then
+ param2 = math.floor(param2 / 8) * 8
+ end
+ -- paramtype2 == "color" requires no modification.
+ return param2
+end
+
+function core.get_node_drops(node, toolname)
+ -- Compatibility, if node is string
+ local nodename = node
+ local param2 = 0
+ -- New format, if node is table
+ if (type(node) == "table") then
+ nodename = node.name
+ param2 = node.param2
+ end
+ local def = core.registered_nodes[nodename]
+ local drop = def and def.drop
+ local ptype = def and def.paramtype2
+ -- get color, if there is color (otherwise nil)
+ local palette_index = core.strip_param2_color(param2, ptype)
+ if drop == nil then
+ -- default drop
+ if palette_index then
+ local stack = ItemStack(nodename)
+ stack:get_meta():set_int("palette_index", palette_index)
+ return {stack:to_string()}
+ end
+ return {nodename}
+ elseif type(drop) == "string" then
+ -- itemstring drop
+ return {drop}
+ elseif drop.items == nil then
+ -- drop = {} to disable default drop
+ return {}
+ end
+
+ -- Extended drop table
+ local got_items = {}
+ local got_count = 0
+ local _, item, tool
+ for _, item in ipairs(drop.items) do
+ local good_rarity = true
+ local good_tool = true
+ if item.rarity ~= nil then
+ good_rarity = item.rarity < 1 or math.random(item.rarity) == 1
+ end
+ if item.tools ~= nil then
+ good_tool = false
+ end
+ if item.tools ~= nil and toolname then
+ for _, tool in ipairs(item.tools) do
+ if tool:sub(1, 1) == '~' then
+ good_tool = toolname:find(tool:sub(2)) ~= nil
+ else
+ good_tool = toolname == tool
+ end
+ if good_tool then
+ break
+ end
+ end
+ end
+ if good_rarity and good_tool then
+ got_count = got_count + 1
+ for _, add_item in ipairs(item.items) do
+ -- add color, if necessary
+ if item.inherit_color and palette_index then
+ local stack = ItemStack(add_item)
+ stack:get_meta():set_int("palette_index", palette_index)
+ add_item = stack:to_string()
+ end
+ got_items[#got_items+1] = add_item
+ end
+ if drop.max_items ~= nil and got_count == drop.max_items then
+ break
+ end
+ end
+ end
+ return got_items
+end
+
+local function user_name(user)
+ return user and user:get_player_name() or ""
+end
+
+local function is_protected(pos, name)
+ return core.is_protected(pos, name) and
+ not minetest.check_player_privs(name, "protection_bypass")
+end
+
+-- Returns a logging function. For empty names, does not log.
+local function make_log(name)
+ return name ~= "" and core.log or function() end
+end
+
+function core.item_place_node(itemstack, placer, pointed_thing, param2,
+ prevent_after_place)
+ local def = itemstack:get_definition()
+ if def.type ~= "node" or pointed_thing.type ~= "node" then
+ return itemstack, false
+ end
+
+ local under = pointed_thing.under
+ local oldnode_under = core.get_node_or_nil(under)
+ local above = pointed_thing.above
+ local oldnode_above = core.get_node_or_nil(above)
+ local playername = user_name(placer)
+ local log = make_log(playername)
+
+ if not oldnode_under or not oldnode_above then
+ log("info", playername .. " tried to place"
+ .. " node in unloaded position " .. core.pos_to_string(above))
+ return itemstack, false
+ end
+
+ local olddef_under = core.registered_nodes[oldnode_under.name]
+ olddef_under = olddef_under or core.nodedef_default
+ local olddef_above = core.registered_nodes[oldnode_above.name]
+ olddef_above = olddef_above or core.nodedef_default
+
+ if not olddef_above.buildable_to and not olddef_under.buildable_to then
+ log("info", playername .. " tried to place"
+ .. " node in invalid position " .. core.pos_to_string(above)
+ .. ", replacing " .. oldnode_above.name)
+ return itemstack, false
+ end
+
+ -- Place above pointed node
+ local place_to = {x = above.x, y = above.y, z = above.z}
+
+ -- If node under is buildable_to, place into it instead (eg. snow)
+ if olddef_under.buildable_to then
+ log("info", "node under is buildable to")
+ place_to = {x = under.x, y = under.y, z = under.z}
+ end
+
+ if is_protected(place_to, playername) then
+ log("action", playername
+ .. " tried to place " .. def.name
+ .. " at protected position "
+ .. core.pos_to_string(place_to))
+ core.record_protection_violation(place_to, playername)
+ return itemstack
+ end
+
+ log("action", playername .. " places node "
+ .. def.name .. " at " .. core.pos_to_string(place_to))
+
+ local oldnode = core.get_node(place_to)
+ local newnode = {name = def.name, param1 = 0, param2 = param2 or 0}
+
+ -- Calculate direction for wall mounted stuff like torches and signs
+ if def.place_param2 ~= nil then
+ newnode.param2 = def.place_param2
+ elseif (def.paramtype2 == "wallmounted" 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
+ }
+ 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:getpos()
+ if placer_pos then
+ local dir = {
+ x = above.x - placer_pos.x,
+ y = above.y - placer_pos.y,
+ z = above.z - placer_pos.z
+ }
+ newnode.param2 = core.dir_to_facedir(dir)
+ log("action", "facedir: " .. newnode.param2)
+ end
+ end
+
+ local metatable = itemstack:get_meta():to_table().fields
+
+ -- Transfer color information
+ if metatable.palette_index and not def.place_param2 then
+ local color_divisor = nil
+ if def.paramtype2 == "color" then
+ color_divisor = 1
+ elseif def.paramtype2 == "colorwallmounted" then
+ color_divisor = 8
+ elseif def.paramtype2 == "colorfacedir" then
+ color_divisor = 32
+ end
+ if color_divisor then
+ local color = math.floor(metatable.palette_index / color_divisor)
+ local other = newnode.param2 % color_divisor
+ newnode.param2 = color * color_divisor + other
+ end
+ end
+
+ -- Check if the node is attached and if it can be placed there
+ if core.get_item_group(def.name, "attached_node") ~= 0 and
+ not builtin_shared.check_attached_node(place_to, newnode) then
+ log("action", "attached node " .. def.name ..
+ " can not be placed at " .. core.pos_to_string(place_to))
+ return itemstack, false
+ end
+
+ -- Add node and update
+ core.add_node(place_to, newnode)
+
+ local take_item = true
+
+ -- Run callback
+ if def.after_place_node 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 pointed_thing_copy = copy_pointed_thing(pointed_thing)
+ if def.after_place_node(place_to_copy, placer, itemstack,
+ pointed_thing_copy) then
+ take_item = false
+ end
+ end
+
+ -- Run script hook
+ for _, callback in ipairs(core.registered_on_placenodes) do
+ -- Deepcopy pos, node and pointed_thing because callback can modify them
+ local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
+ local newnode_copy = {name=newnode.name, param1=newnode.param1, param2=newnode.param2}
+ local oldnode_copy = {name=oldnode.name, param1=oldnode.param1, param2=oldnode.param2}
+ local pointed_thing_copy = copy_pointed_thing(pointed_thing)
+ if callback(place_to_copy, newnode_copy, placer, oldnode_copy, itemstack, pointed_thing_copy) then
+ take_item = false
+ end
+ end
+
+ if take_item then
+ itemstack:take_item()
+ end
+ return itemstack, true
+end
+
+function core.item_place_object(itemstack, placer, pointed_thing)
+ local pos = core.get_pointed_thing_position(pointed_thing, true)
+ if pos ~= nil then
+ local item = itemstack:take_item()
+ core.add_item(pos, item)
+ end
+ return itemstack
+end
+
+function core.item_place(itemstack, placer, pointed_thing, param2)
+ -- Call on_rightclick if the pointed node defines it
+ if pointed_thing.type == "node" and placer and
+ not placer:get_player_control().sneak then
+ local n = core.get_node(pointed_thing.under)
+ local nn = n.name
+ if core.registered_nodes[nn] and core.registered_nodes[nn].on_rightclick then
+ return core.registered_nodes[nn].on_rightclick(pointed_thing.under, n,
+ placer, itemstack, pointed_thing) or itemstack, false
+ end
+ end
+
+ if itemstack:get_definition().type == "node" then
+ return core.item_place_node(itemstack, placer, pointed_thing, param2)
+ end
+ return itemstack
+end
+
+function core.item_secondary_use(itemstack, placer)
+ return itemstack
+end
+
+function core.item_drop(itemstack, dropper, pos)
+ local dropper_is_player = dropper and dropper:is_player()
+ local p = table.copy(pos)
+ local cnt = itemstack:get_count()
+ if dropper_is_player then
+ p.y = p.y + 1.2
+ if dropper:get_player_control().sneak then
+ cnt = 1
+ end
+ end
+ local item = itemstack:take_item(cnt)
+ local obj = core.add_item(p, item)
+ if obj then
+ if dropper_is_player then
+ local dir = dropper:get_look_dir()
+ dir.x = dir.x * 2.9
+ dir.y = dir.y * 2.9 + 2
+ dir.z = dir.z * 2.9
+ obj:set_velocity(dir)
+ obj:get_luaentity().dropped_by = dropper:get_player_name()
+ end
+ return itemstack
+ end
+ -- If we reach this, adding the object to the
+ -- environment failed
+end
+
+function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
+ for _, callback in pairs(core.registered_on_item_eats) do
+ local result = callback(hp_change, replace_with_item, itemstack, user, pointed_thing)
+ if result then
+ return result
+ end
+ end
+ if itemstack:take_item() ~= nil then
+ user:set_hp(user:get_hp() + hp_change)
+
+ if replace_with_item then
+ if itemstack:is_empty() then
+ itemstack:add_item(replace_with_item)
+ else
+ local inv = user:get_inventory()
+ -- 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:getpos()
+ pos.y = math.floor(pos.y + 0.5)
+ core.add_item(pos, replace_with_item)
+ end
+ end
+ end
+ end
+ return itemstack
+end
+
+function core.item_eat(hp_change, replace_with_item)
+ return function(itemstack, user, pointed_thing) -- closure
+ if user then
+ return core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
+ end
+ end
+end
+
+function core.node_punch(pos, node, puncher, pointed_thing)
+ -- Run script hook
+ for _, callback in ipairs(core.registered_on_punchnodes) do
+ -- Copy pos and node because callback can modify them
+ local pos_copy = vector.new(pos)
+ local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
+ local pointed_thing_copy = pointed_thing and copy_pointed_thing(pointed_thing) or nil
+ callback(pos_copy, node_copy, puncher, pointed_thing_copy)
+ end
+end
+
+function core.handle_node_drops(pos, drops, digger)
+ -- Add dropped items to object's inventory
+ local inv = digger and digger:get_inventory()
+ local give_item
+ if inv then
+ give_item = function(item)
+ return inv:add_item("main", item)
+ end
+ else
+ give_item = function(item)
+ -- itemstring to ItemStack for left:is_empty()
+ return ItemStack(item)
+ end
+ end
+
+ 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,
+ }
+ core.add_item(p, left)
+ end
+ end
+end
+
+function core.node_dig(pos, node, digger)
+ local diggername = user_name(digger)
+ local log = make_log(diggername)
+ local def = core.registered_nodes[node.name]
+ if def and (not def.diggable or
+ (def.can_dig and not def.can_dig(pos, digger))) then
+ log("info", diggername .. " tried to dig "
+ .. node.name .. " which is not diggable "
+ .. core.pos_to_string(pos))
+ return
+ end
+
+ if is_protected(pos, diggername) then
+ log("action", diggername
+ .. " tried to dig " .. node.name
+ .. " at protected position "
+ .. core.pos_to_string(pos))
+ core.record_protection_violation(pos, diggername)
+ return
+ end
+
+ log('action', diggername .. " digs "
+ .. node.name .. " at " .. core.pos_to_string(pos))
+
+ local wielded = digger and digger:get_wielded_item()
+ local drops = core.get_node_drops(node, wielded and wielded:get_name())
+
+ 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)
+ if wdef and wdef.after_use then
+ wielded = wdef.after_use(wielded, digger, node, dp) or wielded
+ else
+ -- Wear out tool
+ if not core.settings:get_bool("creative_mode") then
+ wielded:add_wear(dp.wear)
+ if wielded:get_count() == 0 and wdef.sound and wdef.sound.breaks then
+ core.sound_play(wdef.sound.breaks, {pos = pos, gain = 0.5})
+ end
+ end
+ end
+ digger:set_wielded_item(wielded)
+ end
+
+ -- Handle drops
+ core.handle_node_drops(pos, drops, digger)
+
+ local oldmetadata = nil
+ if def and def.after_dig_node then
+ oldmetadata = core.get_meta(pos):to_table()
+ end
+
+ -- Remove node and update
+ core.remove_node(pos)
+
+ -- Run callback
+ if def 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 node_copy = {name=node.name, param1=node.param1, param2=node.param2}
+ def.after_dig_node(pos_copy, node_copy, oldmetadata, digger)
+ end
+
+ -- Run script hook
+ local _, callback
+ for _, callback in ipairs(core.registered_on_dignodes) do
+ local origin = core.callback_origins[callback]
+ if origin then
+ core.set_last_run_mod(origin.mod)
+ --print("Running " .. tostring(callback) ..
+ -- " (a " .. origin.name .. " callback in " .. origin.mod .. ")")
+ else
+ --print("No data associated with callback")
+ end
+
+ -- Copy pos and node because callback can modify them
+ local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
+ local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
+ callback(pos_copy, node_copy, digger)
+ end
+end
+
+-- This is used to allow mods to redefine core.item_place and so on
+-- NOTE: This is not the preferred way. Preferred way is to provide enough
+-- callbacks to not require redefining global functions. -celeron55
+local function redef_wrapper(table, name)
+ return function(...)
+ return table[name](...)
+ end
+end
+
+--
+-- Item definition defaults
+--
+
+core.nodedef_default = {
+ -- Item properties
+ type="node",
+ -- name intentionally not defined here
+ description = "",
+ groups = {},
+ inventory_image = "",
+ wield_image = "",
+ wield_scale = {x=1,y=1,z=1},
+ stack_max = 99,
+ usable = false,
+ liquids_pointable = false,
+ tool_capabilities = nil,
+ node_placement_prediction = nil,
+
+ -- Interaction callbacks
+ on_place = redef_wrapper(core, 'item_place'), -- core.item_place
+ on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop
+ on_use = nil,
+ can_dig = nil,
+
+ on_punch = redef_wrapper(core, 'node_punch'), -- core.node_punch
+ on_rightclick = nil,
+ on_dig = redef_wrapper(core, 'node_dig'), -- core.node_dig
+
+ on_receive_fields = nil,
+
+ on_metadata_inventory_move = core.node_metadata_inventory_move_allow_all,
+ on_metadata_inventory_offer = core.node_metadata_inventory_offer_allow_all,
+ on_metadata_inventory_take = core.node_metadata_inventory_take_allow_all,
+
+ -- Node properties
+ drawtype = "normal",
+ visual_scale = 1.0,
+ -- Don't define these because otherwise the old tile_images and
+ -- special_materials wouldn't be read
+ --tiles ={""},
+ --special_tiles = {
+ -- {name="", backface_culling=true},
+ -- {name="", backface_culling=true},
+ --},
+ alpha = 255,
+ post_effect_color = {a=0, r=0, g=0, b=0},
+ paramtype = "none",
+ paramtype2 = "none",
+ is_ground_content = true,
+ sunlight_propagates = false,
+ walkable = true,
+ pointable = true,
+ diggable = true,
+ climbable = false,
+ buildable_to = false,
+ floodable = false,
+ liquidtype = "none",
+ liquid_alternative_flowing = "",
+ liquid_alternative_source = "",
+ liquid_viscosity = 0,
+ drowning = 0,
+ light_source = 0,
+ damage_per_second = 0,
+ selection_box = {type="regular"},
+ legacy_facedir_simple = false,
+ legacy_wallmounted = false,
+}
+
+core.craftitemdef_default = {
+ type="craft",
+ -- name intentionally not defined here
+ description = "",
+ groups = {},
+ inventory_image = "",
+ wield_image = "",
+ wield_scale = {x=1,y=1,z=1},
+ stack_max = 99,
+ liquids_pointable = false,
+ tool_capabilities = nil,
+
+ -- Interaction callbacks
+ on_place = redef_wrapper(core, 'item_place'), -- core.item_place
+ on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop
+ on_secondary_use = redef_wrapper(core, 'item_secondary_use'),
+ on_use = nil,
+}
+
+core.tooldef_default = {
+ type="tool",
+ -- name intentionally not defined here
+ description = "",
+ groups = {},
+ inventory_image = "",
+ wield_image = "",
+ wield_scale = {x=1,y=1,z=1},
+ stack_max = 1,
+ liquids_pointable = false,
+ tool_capabilities = nil,
+
+ -- Interaction callbacks
+ on_place = redef_wrapper(core, 'item_place'), -- core.item_place
+ on_secondary_use = redef_wrapper(core, 'item_secondary_use'),
+ on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop
+ on_use = nil,
+}
+
+core.noneitemdef_default = { -- This is used for the hand and unknown items
+ type="none",
+ -- name intentionally not defined here
+ description = "",
+ groups = {},
+ inventory_image = "",
+ wield_image = "",
+ wield_scale = {x=1,y=1,z=1},
+ stack_max = 99,
+ liquids_pointable = false,
+ tool_capabilities = nil,
+
+ -- Interaction callbacks
+ on_place = redef_wrapper(core, 'item_place'),
+ on_secondary_use = redef_wrapper(core, 'item_secondary_use'),
+ on_drop = nil,
+ on_use = nil,
+}
diff --git a/builtin/game/item_entity.lua b/builtin/game/item_entity.lua
new file mode 100644
index 000000000..caa759828
--- /dev/null
+++ b/builtin/game/item_entity.lua
@@ -0,0 +1,228 @@
+-- Minetest: builtin/item_entity.lua
+
+function core.spawn_item(pos, item)
+ -- Take item in any format
+ local stack = ItemStack(item)
+ local obj = core.add_entity(pos, "__builtin:item")
+ -- Don't use obj if it couldn't be added to the map.
+ if obj then
+ obj:get_luaentity():set_item(stack:to_string())
+ end
+ return obj
+end
+
+-- If item_entity_ttl is not set, enity will have default life time
+-- Setting it to -1 disables the feature
+
+local time_to_live = tonumber(core.settings:get("item_entity_ttl"))
+if not time_to_live then
+ time_to_live = 900
+end
+
+core.register_entity(":__builtin:item", {
+ initial_properties = {
+ hp_max = 1,
+ physical = true,
+ collide_with_objects = false,
+ collisionbox = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3},
+ visual = "wielditem",
+ visual_size = {x = 0.4, y = 0.4},
+ textures = {""},
+ spritediv = {x = 1, y = 1},
+ initial_sprite_basepos = {x = 0, y = 0},
+ is_visible = false,
+ },
+
+ itemstring = '',
+ physical_state = true,
+ age = 0,
+
+ set_item = function(self, itemstring)
+ self.itemstring = itemstring
+ local stack = ItemStack(itemstring)
+ local count = stack:get_count()
+ local max_count = stack:get_stack_max()
+ if count > max_count then
+ count = max_count
+ self.itemstring = stack:get_name().." "..max_count
+ end
+ local s = 0.2 + 0.1 * (count / max_count)
+ local c = s
+ local itemtable = stack:to_table()
+ local itemname = nil
+ if itemtable then
+ itemname = stack:to_table().name
+ end
+ -- Backwards compatibility: old clients use the texture
+ -- to get the type of the item
+ local item_texture = nil
+ local item_type = ""
+ if core.registered_items[itemname] then
+ item_texture = core.registered_items[itemname].inventory_image
+ item_type = core.registered_items[itemname].type
+ end
+ local prop = {
+ is_visible = true,
+ visual = "wielditem",
+ textures = {itemname},
+ visual_size = {x = s, y = s},
+ collisionbox = {-c, -c, -c, c, c, c},
+ automatic_rotate = math.pi * 0.5,
+ wield_item = itemstring,
+ }
+ self.object:set_properties(prop)
+ end,
+
+ get_staticdata = function(self)
+ return core.serialize({
+ itemstring = self.itemstring,
+ always_collect = self.always_collect,
+ age = self.age,
+ dropped_by = self.dropped_by
+ })
+ end,
+
+ on_activate = function(self, staticdata, dtime_s)
+ if string.sub(staticdata, 1, string.len("return")) == "return" then
+ local data = core.deserialize(staticdata)
+ if data and type(data) == "table" then
+ self.itemstring = data.itemstring
+ self.always_collect = data.always_collect
+ if data.age then
+ self.age = data.age + dtime_s
+ else
+ self.age = dtime_s
+ end
+ self.dropped_by = data.dropped_by
+ end
+ else
+ self.itemstring = staticdata
+ end
+ self.object:set_armor_groups({immortal = 1})
+ self.object:setvelocity({x = 0, y = 2, z = 0})
+ self.object:setacceleration({x = 0, y = -10, z = 0})
+ self:set_item(self.itemstring)
+ end,
+
+ -- moves items from this stack to an other stack
+ try_merge_with = function(self, own_stack, object, obj)
+ -- other item's stack
+ local stack = ItemStack(obj.itemstring)
+ -- only merge if items are the same
+ if own_stack:get_name() == stack:get_name() and
+ own_stack:get_meta() == stack:get_meta() and
+ own_stack:get_wear() == stack:get_wear() and
+ stack:get_free_space() > 0 then
+ local overflow = false
+ local count = stack:get_count() + own_stack:get_count()
+ local max_count = stack:get_stack_max()
+ if count > max_count then
+ overflow = true
+ stack:set_count(max_count)
+ count = count - max_count
+ own_stack:set_count(count)
+ else
+ self.itemstring = ''
+ stack:set_count(count)
+ end
+ local pos = object:getpos()
+ pos.y = pos.y + (count - stack:get_count()) / max_count * 0.15
+ object:moveto(pos, false)
+ local s, c
+ if not overflow then
+ obj.itemstring = stack:to_string()
+ s = 0.2 + 0.1 * (count / max_count)
+ c = s
+ object:set_properties({
+ visual_size = {x = s, y = s},
+ collisionbox = {-c, -c, -c, c, c, c},
+ wield_item = obj.itemstring
+ })
+ self.object:remove()
+ -- merging succeeded
+ return true
+ else
+ s = 0.4
+ c = 0.3
+ obj.itemstring = stack:to_string()
+ object:set_properties({
+ visual_size = {x = s, y = s},
+ collisionbox = {-c, -c, -c, c, c, c},
+ wield_item = obj.itemstring
+ })
+ s = 0.2 + 0.1 * (count / max_count)
+ c = s
+ self.itemstring = own_stack:to_string()
+ self.object:set_properties({
+ visual_size = {x = s, y = s},
+ collisionbox = {-c, -c, -c, c, c, c},
+ wield_item = self.itemstring
+ })
+ end
+ end
+ -- merging didn't succeed
+ return false
+ end,
+
+ on_step = function(self, dtime)
+ self.age = self.age + dtime
+ if time_to_live > 0 and self.age > time_to_live then
+ self.itemstring = ''
+ self.object:remove()
+ return
+ end
+ local p = self.object:getpos()
+ p.y = p.y - 0.5
+ local node = core.get_node_or_nil(p)
+ -- Delete in 'ignore' nodes
+ if node and node.name == "ignore" then
+ self.itemstring = ""
+ self.object:remove()
+ return
+ end
+
+ -- If node is nil (unloaded area), or node is not registered, or node is
+ -- walkably solid and item is resting on nodebox
+ local v = self.object:getvelocity()
+ if not node or not core.registered_nodes[node.name] or
+ core.registered_nodes[node.name].walkable and v.y == 0 then
+ if self.physical_state then
+ local own_stack = ItemStack(self.object:get_luaentity().itemstring)
+ -- Merge with close entities of the same item
+ for _, object in ipairs(core.get_objects_inside_radius(p, 0.8)) do
+ local obj = object:get_luaentity()
+ if obj and obj.name == "__builtin:item"
+ and obj.physical_state == false then
+ if self:try_merge_with(own_stack, object, obj) then
+ return
+ end
+ end
+ end
+ self.object:setvelocity({x = 0, y = 0, z = 0})
+ self.object:setacceleration({x = 0, y = 0, z = 0})
+ self.physical_state = false
+ self.object:set_properties({physical = false})
+ end
+ else
+ if not self.physical_state then
+ self.object:setvelocity({x = 0, y = 0, z = 0})
+ self.object:setacceleration({x = 0, y = -10, z = 0})
+ self.physical_state = true
+ self.object:set_properties({physical = true})
+ end
+ end
+ end,
+
+ on_punch = function(self, hitter)
+ local inv = hitter:get_inventory()
+ if inv and self.itemstring ~= '' then
+ local left = inv:add_item("main", self.itemstring)
+ if left and not left:is_empty() then
+ self.itemstring = left:to_string()
+ return
+ end
+ end
+ self.itemstring = ''
+ self.object:remove()
+ end,
+})
diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua
new file mode 100644
index 000000000..d8f7a638d
--- /dev/null
+++ b/builtin/game/misc.lua
@@ -0,0 +1,189 @@
+-- Minetest: builtin/misc.lua
+
+--
+-- Misc. API functions
+--
+
+function core.check_player_privs(name, ...)
+ if core.is_player(name) then
+ name = name:get_player_name()
+ elseif type(name) ~= "string" then
+ error("core.check_player_privs expects a player or playername as " ..
+ "argument.", 2)
+ end
+
+ local requested_privs = {...}
+ local player_privs = core.get_player_privs(name)
+ local missing_privileges = {}
+
+ if type(requested_privs[1]) == "table" then
+ -- We were provided with a table like { privA = true, privB = true }.
+ for priv, value in pairs(requested_privs[1]) do
+ if value and not player_privs[priv] then
+ missing_privileges[#missing_privileges + 1] = priv
+ end
+ end
+ else
+ -- Only a list, we can process it directly.
+ for key, priv in pairs(requested_privs) do
+ if not player_privs[priv] then
+ missing_privileges[#missing_privileges + 1] = priv
+ end
+ end
+ end
+
+ if #missing_privileges > 0 then
+ return false, missing_privileges
+ end
+
+ return true, ""
+end
+
+local player_list = {}
+
+core.register_on_joinplayer(function(player)
+ local player_name = player:get_player_name()
+ player_list[player_name] = player
+ if not minetest.is_singleplayer() then
+ core.chat_send_all("*** " .. player_name .. " joined the game.")
+ end
+end)
+
+core.register_on_leaveplayer(function(player, timed_out)
+ local player_name = player:get_player_name()
+ player_list[player_name] = nil
+ local announcement = "*** " .. player_name .. " left the game."
+ if timed_out then
+ announcement = announcement .. " (timed out)"
+ end
+ core.chat_send_all(announcement)
+end)
+
+function core.get_connected_players()
+ local temp_table = {}
+ for index, value in pairs(player_list) do
+ if value:is_player_connected() then
+ temp_table[#temp_table + 1] = value
+ end
+ end
+ return temp_table
+end
+
+
+function core.is_player(player)
+ -- a table being a player is also supported because it quacks sufficiently
+ -- like a player if it has the is_player function
+ local t = type(player)
+ return (t == "userdata" or t == "table") and
+ type(player.is_player) == "function" and player:is_player()
+end
+
+
+function minetest.player_exists(name)
+ return minetest.get_auth_handler().get_auth(name) ~= nil
+end
+
+-- Returns two position vectors representing a box of `radius` in each
+-- direction centered around the player corresponding to `player_name`
+function core.get_player_radius_area(player_name, radius)
+ local player = core.get_player_by_name(player_name)
+ if player == nil then
+ return nil
+ end
+
+ local p1 = player:getpos()
+ local p2 = p1
+
+ if radius then
+ p1 = vector.subtract(p1, radius)
+ p2 = vector.add(p2, radius)
+ end
+
+ return p1, p2
+end
+
+function core.hash_node_position(pos)
+ return (pos.z+32768)*65536*65536 + (pos.y+32768)*65536 + pos.x+32768
+end
+
+function core.get_position_from_hash(hash)
+ local pos = {}
+ pos.x = (hash%65536) - 32768
+ hash = math.floor(hash/65536)
+ pos.y = (hash%65536) - 32768
+ hash = math.floor(hash/65536)
+ pos.z = (hash%65536) - 32768
+ return pos
+end
+
+function core.get_item_group(name, group)
+ if not core.registered_items[name] or not
+ core.registered_items[name].groups[group] then
+ return 0
+ end
+ return core.registered_items[name].groups[group]
+end
+
+function core.get_node_group(name, group)
+ core.log("deprecated", "Deprecated usage of get_node_group, use get_item_group instead")
+ return core.get_item_group(name, group)
+end
+
+function core.setting_get_pos(name)
+ local value = core.settings:get(name)
+ if not value then
+ return nil
+ end
+ return core.string_to_pos(value)
+end
+
+-- To be overriden by protection mods
+function core.is_protected(pos, name)
+ return false
+end
+
+function core.record_protection_violation(pos, name)
+ for _, func in pairs(core.registered_on_protection_violation) do
+ func(pos, name)
+ end
+end
+
+local raillike_ids = {}
+local raillike_cur_id = 0
+function core.raillike_group(name)
+ local id = raillike_ids[name]
+ if not id then
+ raillike_cur_id = raillike_cur_id + 1
+ raillike_ids[name] = raillike_cur_id
+ id = raillike_cur_id
+ end
+ return id
+end
+
+-- HTTP callback interface
+function core.http_add_fetch(httpenv)
+ httpenv.fetch = function(req, callback)
+ local handle = httpenv.fetch_async(req)
+
+ local function update_http_status()
+ local res = httpenv.fetch_async_get(handle)
+ if res.completed then
+ callback(res)
+ else
+ core.after(0, update_http_status)
+ end
+ end
+ core.after(0, update_http_status)
+ end
+
+ return httpenv
+end
+
+function core.close_formspec(player_name, formname)
+ return minetest.show_formspec(player_name, formname, "")
+end
+
+function core.cancel_shutdown_requests()
+ core.request_shutdown("", false, -1)
+end
+
diff --git a/builtin/game/privileges.lua b/builtin/game/privileges.lua
new file mode 100644
index 000000000..56e090f4c
--- /dev/null
+++ b/builtin/game/privileges.lua
@@ -0,0 +1,92 @@
+-- Minetest: builtin/privileges.lua
+
+--
+-- Privileges
+--
+
+core.registered_privileges = {}
+
+function core.register_privilege(name, param)
+ local function fill_defaults(def)
+ if def.give_to_singleplayer == nil then
+ def.give_to_singleplayer = true
+ end
+ if def.description == nil then
+ def.description = "(no description)"
+ end
+ end
+ local def = {}
+ if type(param) == "table" then
+ def = param
+ else
+ def = {description = param}
+ end
+ fill_defaults(def)
+ core.registered_privileges[name] = def
+end
+
+core.register_privilege("interact", "Can interact with things and modify the world")
+core.register_privilege("shout", "Can speak in chat")
+core.register_privilege("basic_privs", "Can modify 'shout' and 'interact' privileges")
+core.register_privilege("privs", "Can modify privileges")
+
+core.register_privilege("teleport", {
+ description = "Can use /teleport command",
+ give_to_singleplayer = false,
+})
+core.register_privilege("bring", {
+ description = "Can teleport other players",
+ give_to_singleplayer = false,
+})
+core.register_privilege("settime", {
+ description = "Can use /time",
+ give_to_singleplayer = false,
+})
+core.register_privilege("server", {
+ description = "Can do server maintenance stuff",
+ give_to_singleplayer = false,
+})
+core.register_privilege("protection_bypass", {
+ description = "Can bypass node protection in the world",
+ give_to_singleplayer = false,
+})
+core.register_privilege("ban", {
+ description = "Can ban and unban players",
+ give_to_singleplayer = false,
+})
+core.register_privilege("kick", {
+ description = "Can kick players",
+ give_to_singleplayer = false,
+})
+core.register_privilege("give", {
+ description = "Can use /give and /giveme",
+ give_to_singleplayer = false,
+})
+core.register_privilege("password", {
+ description = "Can use /setpassword and /clearpassword",
+ give_to_singleplayer = false,
+})
+core.register_privilege("fly", {
+ description = "Can fly using the free_move mode",
+ give_to_singleplayer = false,
+})
+core.register_privilege("fast", {
+ description = "Can walk fast using the fast_move mode",
+ give_to_singleplayer = false,
+})
+core.register_privilege("noclip", {
+ description = "Can fly through walls",
+ give_to_singleplayer = false,
+})
+core.register_privilege("rollback", {
+ description = "Can use the rollback functionality",
+ give_to_singleplayer = false,
+})
+core.register_privilege("zoom", {
+ description = "Can zoom the camera",
+ give_to_singleplayer = false,
+})
+core.register_privilege("debug", {
+ description = "Allows enabling various debug options that may affect gameplay",
+ give_to_singleplayer = false,
+})
diff --git a/builtin/game/register.lua b/builtin/game/register.lua
new file mode 100644
index 000000000..25af24eb7
--- /dev/null
+++ b/builtin/game/register.lua
@@ -0,0 +1,570 @@
+-- Minetest: builtin/misc_register.lua
+
+--
+-- Make raw registration functions inaccessible to anyone except this file
+--
+
+local register_item_raw = core.register_item_raw
+core.register_item_raw = nil
+
+local unregister_item_raw = core.unregister_item_raw
+core.unregister_item_raw = nil
+
+local register_alias_raw = core.register_alias_raw
+core.register_alias_raw = nil
+
+--
+-- Item / entity / ABM / LBM registration functions
+--
+
+core.registered_abms = {}
+core.registered_lbms = {}
+core.registered_entities = {}
+core.registered_items = {}
+core.registered_nodes = {}
+core.registered_craftitems = {}
+core.registered_tools = {}
+core.registered_aliases = {}
+
+-- For tables that are indexed by item name:
+-- If table[X] does not exist, default to table[core.registered_aliases[X]]
+local alias_metatable = {
+ __index = function(t, name)
+ return rawget(t, core.registered_aliases[name])
+ end
+}
+setmetatable(core.registered_items, alias_metatable)
+setmetatable(core.registered_nodes, alias_metatable)
+setmetatable(core.registered_craftitems, alias_metatable)
+setmetatable(core.registered_tools, alias_metatable)
+
+-- These item names may not be used because they would interfere
+-- with legacy itemstrings
+local forbidden_item_names = {
+ MaterialItem = true,
+ MaterialItem2 = true,
+ MaterialItem3 = true,
+ NodeItem = true,
+ node = true,
+ CraftItem = true,
+ craft = true,
+ MBOItem = true,
+ ToolItem = true,
+ tool = true,
+}
+
+local function check_modname_prefix(name)
+ if name:sub(1,1) == ":" then
+ -- If the name starts with a colon, we can skip the modname prefix
+ -- mechanism.
+ return name:sub(2)
+ else
+ -- Enforce that the name starts with the correct mod name.
+ local expected_prefix = core.get_current_modname() .. ":"
+ if name:sub(1, #expected_prefix) ~= expected_prefix then
+ error("Name " .. name .. " does not follow naming conventions: " ..
+ "\"" .. expected_prefix .. "\" or \":\" prefix required")
+ end
+
+ -- Enforce that the name only contains letters, numbers and underscores.
+ local subname = name:sub(#expected_prefix+1)
+ if subname:find("[^%w_]") then
+ error("Name " .. name .. " does not follow naming conventions: " ..
+ "contains unallowed characters")
+ end
+
+ return name
+ end
+end
+
+function core.register_abm(spec)
+ -- Add to core.registered_abms
+ core.registered_abms[#core.registered_abms + 1] = spec
+ spec.mod_origin = core.get_current_modname() or "??"
+end
+
+function core.register_lbm(spec)
+ -- Add to core.registered_lbms
+ check_modname_prefix(spec.name)
+ core.registered_lbms[#core.registered_lbms + 1] = spec
+ spec.mod_origin = core.get_current_modname() or "??"
+end
+
+function core.register_entity(name, prototype)
+ -- Check name
+ if name == nil then
+ error("Unable to register entity: Name is nil")
+ end
+ name = check_modname_prefix(tostring(name))
+
+ prototype.name = name
+ prototype.__index = prototype -- so that it can be used as a metatable
+
+ -- Add to core.registered_entities
+ core.registered_entities[name] = prototype
+ prototype.mod_origin = core.get_current_modname() or "??"
+end
+
+function core.register_item(name, itemdef)
+ -- Check name
+ if name == nil then
+ error("Unable to register item: Name is nil")
+ end
+ name = check_modname_prefix(tostring(name))
+ if forbidden_item_names[name] then
+ error("Unable to register item: Name is forbidden: " .. name)
+ end
+ itemdef.name = name
+
+ local is_overriding = core.registered_items[name]
+
+ -- Apply defaults and add to registered_* table
+ if itemdef.type == "node" then
+ -- Use the nodebox as selection box if it's not set manually
+ if itemdef.drawtype == "nodebox" and not itemdef.selection_box then
+ itemdef.selection_box = itemdef.node_box
+ elseif itemdef.drawtype == "fencelike" and not itemdef.selection_box then
+ itemdef.selection_box = {
+ type = "fixed",
+ fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8},
+ }
+ end
+ if itemdef.light_source and itemdef.light_source > core.LIGHT_MAX then
+ itemdef.light_source = core.LIGHT_MAX
+ core.log("warning", "Node 'light_source' value exceeds maximum," ..
+ " limiting to maximum: " ..name)
+ end
+ setmetatable(itemdef, {__index = core.nodedef_default})
+ core.registered_nodes[itemdef.name] = itemdef
+ elseif itemdef.type == "craft" then
+ setmetatable(itemdef, {__index = core.craftitemdef_default})
+ core.registered_craftitems[itemdef.name] = itemdef
+ elseif itemdef.type == "tool" then
+ setmetatable(itemdef, {__index = core.tooldef_default})
+ core.registered_tools[itemdef.name] = itemdef
+ elseif itemdef.type == "none" then
+ setmetatable(itemdef, {__index = core.noneitemdef_default})
+ else
+ error("Unable to register item: Type is invalid: " .. dump(itemdef))
+ end
+
+ -- Flowing liquid uses param2
+ if itemdef.type == "node" and itemdef.liquidtype == "flowing" then
+ itemdef.paramtype2 = "flowingliquid"
+ end
+
+ -- BEGIN Legacy stuff
+ if itemdef.cookresult_itemstring ~= nil and itemdef.cookresult_itemstring ~= "" then
+ core.register_craft({
+ type="cooking",
+ output=itemdef.cookresult_itemstring,
+ recipe=itemdef.name,
+ cooktime=itemdef.furnace_cooktime
+ })
+ end
+ if itemdef.furnace_burntime ~= nil and itemdef.furnace_burntime >= 0 then
+ core.register_craft({
+ type="fuel",
+ recipe=itemdef.name,
+ burntime=itemdef.furnace_burntime
+ })
+ end
+ -- END Legacy stuff
+
+ itemdef.mod_origin = core.get_current_modname() or "??"
+
+ -- Disable all further modifications
+ getmetatable(itemdef).__newindex = {}
+
+ --core.log("Registering item: " .. itemdef.name)
+ core.registered_items[itemdef.name] = itemdef
+ core.registered_aliases[itemdef.name] = nil
+
+ -- Used to allow builtin to register ignore to registered_items
+ if name ~= "ignore" then
+ register_item_raw(itemdef)
+ elseif is_overriding then
+ core.log("warning", "Attempted redefinition of \"ignore\"")
+ end
+end
+
+function core.unregister_item(name)
+ if not core.registered_items[name] then
+ core.log("warning", "Not unregistering item " ..name..
+ " because it doesn't exist.")
+ return
+ end
+ -- Erase from registered_* table
+ local type = core.registered_items[name].type
+ if type == "node" then
+ core.registered_nodes[name] = nil
+ elseif type == "craft" then
+ core.registered_craftitems[name] = nil
+ elseif type == "tool" then
+ core.registered_tools[name] = nil
+ end
+ core.registered_items[name] = nil
+
+
+ unregister_item_raw(name)
+end
+
+function core.register_node(name, nodedef)
+ nodedef.type = "node"
+ core.register_item(name, nodedef)
+end
+
+function core.register_craftitem(name, craftitemdef)
+ craftitemdef.type = "craft"
+
+ -- BEGIN Legacy stuff
+ if craftitemdef.inventory_image == nil and craftitemdef.image ~= nil then
+ craftitemdef.inventory_image = craftitemdef.image
+ end
+ -- END Legacy stuff
+
+ core.register_item(name, craftitemdef)
+end
+
+function core.register_tool(name, tooldef)
+ tooldef.type = "tool"
+ tooldef.stack_max = 1
+
+ -- BEGIN Legacy stuff
+ if tooldef.inventory_image == nil and tooldef.image ~= nil then
+ tooldef.inventory_image = tooldef.image
+ end
+ if tooldef.tool_capabilities == nil and
+ (tooldef.full_punch_interval ~= nil or
+ tooldef.basetime ~= nil or
+ tooldef.dt_weight ~= nil or
+ tooldef.dt_crackiness ~= nil or
+ tooldef.dt_crumbliness ~= nil or
+ tooldef.dt_cuttability ~= nil or
+ tooldef.basedurability ~= nil or
+ tooldef.dd_weight ~= nil or
+ tooldef.dd_crackiness ~= nil or
+ tooldef.dd_crumbliness ~= nil or
+ tooldef.dd_cuttability ~= nil) then
+ tooldef.tool_capabilities = {
+ full_punch_interval = tooldef.full_punch_interval,
+ basetime = tooldef.basetime,
+ dt_weight = tooldef.dt_weight,
+ dt_crackiness = tooldef.dt_crackiness,
+ dt_crumbliness = tooldef.dt_crumbliness,
+ dt_cuttability = tooldef.dt_cuttability,
+ basedurability = tooldef.basedurability,
+ dd_weight = tooldef.dd_weight,
+ dd_crackiness = tooldef.dd_crackiness,
+ dd_crumbliness = tooldef.dd_crumbliness,
+ dd_cuttability = tooldef.dd_cuttability,
+ }
+ end
+ -- END Legacy stuff
+
+ core.register_item(name, tooldef)
+end
+
+function core.register_alias(name, convert_to)
+ if forbidden_item_names[name] then
+ error("Unable to register alias: Name is forbidden: " .. name)
+ end
+ if core.registered_items[name] ~= nil then
+ core.log("warning", "Not registering alias, item with same name" ..
+ " is already defined: " .. name .. " -> " .. convert_to)
+ else
+ --core.log("Registering alias: " .. name .. " -> " .. convert_to)
+ core.registered_aliases[name] = convert_to
+ register_alias_raw(name, convert_to)
+ end
+end
+
+function core.register_alias_force(name, convert_to)
+ if forbidden_item_names[name] then
+ error("Unable to register alias: Name is forbidden: " .. name)
+ end
+ if core.registered_items[name] ~= nil then
+ core.unregister_item(name)
+ core.log("info", "Removed item " ..name..
+ " while attempting to force add an alias")
+ end
+ --core.log("Registering alias: " .. name .. " -> " .. convert_to)
+ core.registered_aliases[name] = convert_to
+ register_alias_raw(name, convert_to)
+end
+
+function core.on_craft(itemstack, player, old_craft_list, craft_inv)
+ for _, func in ipairs(core.registered_on_crafts) do
+ itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack
+ end
+ return itemstack
+end
+
+function core.craft_predict(itemstack, player, old_craft_list, craft_inv)
+ for _, func in ipairs(core.registered_craft_predicts) do
+ itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack
+ end
+ return itemstack
+end
+
+-- Alias the forbidden item names to "" so they can't be
+-- created via itemstrings (e.g. /give)
+local name
+for name in pairs(forbidden_item_names) do
+ core.registered_aliases[name] = ""
+ register_alias_raw(name, "")
+end
+
+
+-- Deprecated:
+-- Aliases for core.register_alias (how ironic...)
+--core.alias_node = core.register_alias
+--core.alias_tool = core.register_alias
+--core.alias_craftitem = core.register_alias
+
+--
+-- Built-in node definitions. Also defined in C.
+--
+
+core.register_item(":unknown", {
+ type = "none",
+ description = "Unknown Item",
+ inventory_image = "unknown_item.png",
+ on_place = core.item_place,
+ on_secondary_use = core.item_secondary_use,
+ on_drop = core.item_drop,
+ groups = {not_in_creative_inventory=1},
+ diggable = true,
+})
+
+core.register_node(":air", {
+ description = "Air (you hacker you!)",
+ inventory_image = "air.png",
+ wield_image = "air.png",
+ drawtype = "airlike",
+ paramtype = "light",
+ sunlight_propagates = true,
+ walkable = false,
+ pointable = false,
+ diggable = false,
+ buildable_to = true,
+ floodable = true,
+ air_equivalent = true,
+ drop = "",
+ groups = {not_in_creative_inventory=1},
+})
+
+core.register_node(":ignore", {
+ description = "Ignore (you hacker you!)",
+ inventory_image = "ignore.png",
+ wield_image = "ignore.png",
+ drawtype = "airlike",
+ paramtype = "none",
+ sunlight_propagates = false,
+ walkable = false,
+ pointable = false,
+ diggable = false,
+ buildable_to = true, -- A way to remove accidentally placed ignores
+ air_equivalent = true,
+ drop = "",
+ groups = {not_in_creative_inventory=1},
+})
+
+-- The hand (bare definition)
+core.register_item(":", {
+ type = "none",
+ groups = {not_in_creative_inventory=1},
+})
+
+
+function core.override_item(name, redefinition)
+ if redefinition.name ~= nil then
+ error("Attempt to redefine name of "..name.." to "..dump(redefinition.name), 2)
+ end
+ if redefinition.type ~= nil then
+ error("Attempt to redefine type of "..name.." to "..dump(redefinition.type), 2)
+ end
+ local item = core.registered_items[name]
+ if not item then
+ error("Attempt to override non-existent item "..name, 2)
+ end
+ for k, v in pairs(redefinition) do
+ rawset(item, k, v)
+ end
+ register_item_raw(item)
+end
+
+
+core.callback_origins = {}
+
+function core.run_callbacks(callbacks, mode, ...)
+ assert(type(callbacks) == "table")
+ local cb_len = #callbacks
+ if cb_len == 0 then
+ if mode == 2 or mode == 3 then
+ return true
+ elseif mode == 4 or mode == 5 then
+ return false
+ end
+ end
+ local ret = nil
+ for i = 1, cb_len do
+ local origin = core.callback_origins[callbacks[i]]
+ if origin then
+ core.set_last_run_mod(origin.mod)
+ --print("Running " .. tostring(callbacks[i]) ..
+ -- " (a " .. origin.name .. " callback in " .. origin.mod .. ")")
+ else
+ --print("No data associated with callback")
+ end
+ local cb_ret = callbacks[i](...)
+
+ if mode == 0 and i == 1 then
+ ret = cb_ret
+ elseif mode == 1 and i == cb_len then
+ ret = cb_ret
+ elseif mode == 2 then
+ if not cb_ret or i == 1 then
+ ret = cb_ret
+ end
+ elseif mode == 3 then
+ if cb_ret then
+ return cb_ret
+ end
+ ret = cb_ret
+ elseif mode == 4 then
+ if (cb_ret and not ret) or i == 1 then
+ ret = cb_ret
+ end
+ elseif mode == 5 and cb_ret then
+ return cb_ret
+ end
+ end
+ return ret
+end
+
+--
+-- Callback registration
+--
+
+local function make_registration()
+ local t = {}
+ local registerfunc = function(func)
+ t[#t + 1] = func
+ core.callback_origins[func] = {
+ mod = core.get_current_modname() or "??",
+ name = debug.getinfo(1, "n").name or "??"
+ }
+ --local origin = core.callback_origins[func]
+ --print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func))
+ end
+ return t, registerfunc
+end
+
+local function make_registration_reverse()
+ local t = {}
+ local registerfunc = function(func)
+ table.insert(t, 1, func)
+ core.callback_origins[func] = {
+ mod = core.get_current_modname() or "??",
+ name = debug.getinfo(1, "n").name or "??"
+ }
+ --local origin = core.callback_origins[func]
+ --print(origin.name .. ": " .. origin.mod .. " registering cbk " .. tostring(func))
+ end
+ return t, registerfunc
+end
+
+local function make_registration_wrap(reg_fn_name, clear_fn_name)
+ local list = {}
+
+ local orig_reg_fn = core[reg_fn_name]
+ core[reg_fn_name] = function(def)
+ local retval = orig_reg_fn(def)
+ if retval ~= nil then
+ if def.name ~= nil then
+ list[def.name] = def
+ else
+ list[retval] = def
+ end
+ end
+ return retval
+ end
+
+ local orig_clear_fn = core[clear_fn_name]
+ core[clear_fn_name] = function()
+ for k in pairs(list) do
+ list[k] = nil
+ end
+ return orig_clear_fn()
+ end
+
+ return list
+end
+
+core.registered_on_player_hpchanges = { modifiers = { }, loggers = { } }
+
+function core.registered_on_player_hpchange(player, hp_change)
+ local last = false
+ for i = #core.registered_on_player_hpchanges.modifiers, 1, -1 do
+ local func = core.registered_on_player_hpchanges.modifiers[i]
+ hp_change, last = func(player, hp_change)
+ if type(hp_change) ~= "number" then
+ local debuginfo = debug.getinfo(func)
+ error("The register_on_hp_changes function has to return a number at " ..
+ debuginfo.short_src .. " line " .. debuginfo.linedefined)
+ end
+ if last then
+ break
+ end
+ end
+ for i, func in ipairs(core.registered_on_player_hpchanges.loggers) do
+ func(player, hp_change)
+ end
+ return hp_change
+end
+
+function core.register_on_player_hpchange(func, modifier)
+ if modifier then
+ core.registered_on_player_hpchanges.modifiers[#core.registered_on_player_hpchanges.modifiers + 1] = func
+ else
+ core.registered_on_player_hpchanges.loggers[#core.registered_on_player_hpchanges.loggers + 1] = func
+ end
+ core.callback_origins[func] = {
+ mod = core.get_current_modname() or "??",
+ name = debug.getinfo(1, "n").name or "??"
+ }
+end
+
+core.registered_biomes = make_registration_wrap("register_biome", "clear_registered_biomes")
+core.registered_ores = make_registration_wrap("register_ore", "clear_registered_ores")
+core.registered_decorations = make_registration_wrap("register_decoration", "clear_registered_decorations")
+
+core.registered_on_chat_messages, core.register_on_chat_message = make_registration()
+core.registered_globalsteps, core.register_globalstep = make_registration()
+core.registered_playerevents, core.register_playerevent = make_registration()
+core.registered_on_shutdown, core.register_on_shutdown = make_registration()
+core.registered_on_punchnodes, core.register_on_punchnode = make_registration()
+core.registered_on_placenodes, core.register_on_placenode = make_registration()
+core.registered_on_dignodes, core.register_on_dignode = make_registration()
+core.registered_on_generateds, core.register_on_generated = make_registration()
+core.registered_on_newplayers, core.register_on_newplayer = make_registration()
+core.registered_on_dieplayers, core.register_on_dieplayer = make_registration()
+core.registered_on_respawnplayers, core.register_on_respawnplayer = make_registration()
+core.registered_on_prejoinplayers, core.register_on_prejoinplayer = make_registration()
+core.registered_on_joinplayers, core.register_on_joinplayer = make_registration()
+core.registered_on_leaveplayers, core.register_on_leaveplayer = make_registration()
+core.registered_on_player_receive_fields, core.register_on_player_receive_fields = make_registration_reverse()
+core.registered_on_cheats, core.register_on_cheat = make_registration()
+core.registered_on_crafts, core.register_on_craft = make_registration()
+core.registered_craft_predicts, core.register_craft_predict = make_registration()
+core.registered_on_protection_violation, core.register_on_protection_violation = make_registration()
+core.registered_on_item_eats, core.register_on_item_eat = make_registration()
+core.registered_on_punchplayers, core.register_on_punchplayer = make_registration()
+
+--
+-- Compatibility for on_mapgen_init()
+--
+
+core.register_on_mapgen_init = function(func) func(core.get_mapgen_params()) end
+
diff --git a/builtin/game/statbars.lua b/builtin/game/statbars.lua
new file mode 100644
index 000000000..6aa106140
--- /dev/null
+++ b/builtin/game/statbars.lua
@@ -0,0 +1,165 @@
+-- cache setting
+local enable_damage = core.settings:get_bool("enable_damage")
+
+local health_bar_definition =
+{
+ hud_elem_type = "statbar",
+ position = { x=0.5, y=1 },
+ text = "heart.png",
+ number = 20,
+ direction = 0,
+ size = { x=24, y=24 },
+ offset = { x=(-10*24)-25, y=-(48+24+16)},
+}
+
+local breath_bar_definition =
+{
+ hud_elem_type = "statbar",
+ position = { x=0.5, y=1 },
+ text = "bubble.png",
+ number = 20,
+ direction = 0,
+ size = { x=24, y=24 },
+ offset = {x=25,y=-(48+24+16)},
+}
+
+local hud_ids = {}
+
+local function initialize_builtin_statbars(player)
+
+ if not player:is_player() then
+ return
+ end
+
+ local name = player:get_player_name()
+
+ if name == "" then
+ return
+ end
+
+ if (hud_ids[name] == nil) then
+ hud_ids[name] = {}
+ -- flags are not transmitted to client on connect, we need to make sure
+ -- our current flags are transmitted by sending them actively
+ player:hud_set_flags(player:hud_get_flags())
+ end
+
+ if player:hud_get_flags().healthbar and enable_damage then
+ if hud_ids[name].id_healthbar == nil then
+ health_bar_definition.number = player:get_hp()
+ hud_ids[name].id_healthbar = player:hud_add(health_bar_definition)
+ end
+ else
+ if hud_ids[name].id_healthbar ~= nil then
+ player:hud_remove(hud_ids[name].id_healthbar)
+ hud_ids[name].id_healthbar = nil
+ end
+ end
+
+ if (player:get_breath() < 11) then
+ if player:hud_get_flags().breathbar and enable_damage then
+ if hud_ids[name].id_breathbar == nil then
+ hud_ids[name].id_breathbar = player:hud_add(breath_bar_definition)
+ end
+ else
+ if hud_ids[name].id_breathbar ~= nil then
+ player:hud_remove(hud_ids[name].id_breathbar)
+ hud_ids[name].id_breathbar = nil
+ end
+ end
+ elseif hud_ids[name].id_breathbar ~= nil then
+ player:hud_remove(hud_ids[name].id_breathbar)
+ hud_ids[name].id_breathbar = nil
+ end
+end
+
+local function cleanup_builtin_statbars(player)
+
+ if not player:is_player() then
+ return
+ end
+
+ local name = player:get_player_name()
+
+ if name == "" then
+ return
+ end
+
+ hud_ids[name] = nil
+end
+
+local function player_event_handler(player,eventname)
+ assert(player:is_player())
+
+ local name = player:get_player_name()
+
+ if name == "" then
+ return
+ end
+
+ if eventname == "health_changed" then
+ initialize_builtin_statbars(player)
+
+ if hud_ids[name].id_healthbar ~= nil then
+ player:hud_change(hud_ids[name].id_healthbar,"number",player:get_hp())
+ return true
+ end
+ end
+
+ if eventname == "breath_changed" then
+ initialize_builtin_statbars(player)
+
+ if hud_ids[name].id_breathbar ~= nil then
+ player:hud_change(hud_ids[name].id_breathbar,"number",player:get_breath()*2)
+ return true
+ end
+ end
+
+ if eventname == "hud_changed" then
+ initialize_builtin_statbars(player)
+ return true
+ end
+
+ return false
+end
+
+function core.hud_replace_builtin(name, definition)
+
+ if definition == nil or
+ type(definition) ~= "table" or
+ definition.hud_elem_type ~= "statbar" then
+ return false
+ end
+
+ if name == "health" then
+ health_bar_definition = definition
+
+ for name,ids in pairs(hud_ids) do
+ local player = core.get_player_by_name(name)
+ if player and hud_ids[name].id_healthbar then
+ player:hud_remove(hud_ids[name].id_healthbar)
+ initialize_builtin_statbars(player)
+ end
+ end
+ return true
+ end
+
+ if name == "breath" then
+ breath_bar_definition = definition
+
+ for name,ids in pairs(hud_ids) do
+ local player = core.get_player_by_name(name)
+ if player and hud_ids[name].id_breathbar then
+ player:hud_remove(hud_ids[name].id_breathbar)
+ initialize_builtin_statbars(player)
+ end
+ end
+ return true
+ end
+
+ return false
+end
+
+core.register_on_joinplayer(initialize_builtin_statbars)
+core.register_on_leaveplayer(cleanup_builtin_statbars)
+core.register_playerevent(player_event_handler)
diff --git a/builtin/game/static_spawn.lua b/builtin/game/static_spawn.lua
new file mode 100644
index 000000000..b1157b42e
--- /dev/null
+++ b/builtin/game/static_spawn.lua
@@ -0,0 +1,25 @@
+-- Minetest: builtin/static_spawn.lua
+
+local function warn_invalid_static_spawnpoint()
+ if core.settings:get("static_spawnpoint") and
+ not core.setting_get_pos("static_spawnpoint") then
+ core.log("error", "The static_spawnpoint setting is invalid: \""..
+ core.settings:get("static_spawnpoint").."\"")
+ end
+end
+
+warn_invalid_static_spawnpoint()
+
+local function put_player_in_spawn(player_obj)
+ local static_spawnpoint = core.setting_get_pos("static_spawnpoint")
+ if not static_spawnpoint then
+ return false
+ end
+ core.log("action", "Moving " .. player_obj:get_player_name() ..
+ " to static spawnpoint at " .. core.pos_to_string(static_spawnpoint))
+ player_obj:setpos(static_spawnpoint)
+ return true
+end
+
+core.register_on_newplayer(put_player_in_spawn)
+core.register_on_respawnplayer(put_player_in_spawn)
diff --git a/builtin/game/voxelarea.lua b/builtin/game/voxelarea.lua
new file mode 100644
index 000000000..724761414
--- /dev/null
+++ b/builtin/game/voxelarea.lua
@@ -0,0 +1,132 @@
+VoxelArea = {
+ MinEdge = {x=1, y=1, z=1},
+ MaxEdge = {x=0, y=0, z=0},
+ ystride = 0,
+ zstride = 0,
+}
+
+function VoxelArea:new(o)
+ o = o or {}
+ setmetatable(o, self)
+ self.__index = self
+
+ local e = o:getExtent()
+ o.ystride = e.x
+ o.zstride = e.x * e.y
+
+ return o
+end
+
+function VoxelArea:getExtent()
+ local MaxEdge, MinEdge = self.MaxEdge, self.MinEdge
+ return {
+ x = MaxEdge.x - MinEdge.x + 1,
+ y = MaxEdge.y - MinEdge.y + 1,
+ z = MaxEdge.z - MinEdge.z + 1,
+ }
+end
+
+function VoxelArea:getVolume()
+ local e = self:getExtent()
+ return e.x * e.y * e.z
+end
+
+function VoxelArea:index(x, y, z)
+ local MinEdge = self.MinEdge
+ local i = (z - MinEdge.z) * self.zstride +
+ (y - MinEdge.y) * self.ystride +
+ (x - MinEdge.x) + 1
+ return math.floor(i)
+end
+
+function VoxelArea:indexp(p)
+ local MinEdge = self.MinEdge
+ local i = (p.z - MinEdge.z) * self.zstride +
+ (p.y - MinEdge.y) * self.ystride +
+ (p.x - MinEdge.x) + 1
+ return math.floor(i)
+end
+
+function VoxelArea:position(i)
+ local p = {}
+ local MinEdge = self.MinEdge
+
+ i = i - 1
+
+ p.z = math.floor(i / self.zstride) + MinEdge.z
+ i = i % self.zstride
+
+ p.y = math.floor(i / self.ystride) + MinEdge.y
+ i = i % self.ystride
+
+ p.x = math.floor(i) + MinEdge.x
+
+ return p
+end
+
+function VoxelArea:contains(x, y, z)
+ local MaxEdge, MinEdge = self.MaxEdge, self.MinEdge
+ return (x >= MinEdge.x) and (x <= MaxEdge.x) and
+ (y >= MinEdge.y) and (y <= MaxEdge.y) and
+ (z >= MinEdge.z) and (z <= MaxEdge.z)
+end
+
+function VoxelArea:containsp(p)
+ local MaxEdge, MinEdge = self.MaxEdge, self.MinEdge
+ return (p.x >= MinEdge.x) and (p.x <= MaxEdge.x) and
+ (p.y >= MinEdge.y) and (p.y <= MaxEdge.y) and
+ (p.z >= MinEdge.z) and (p.z <= MaxEdge.z)
+end
+
+function VoxelArea:containsi(i)
+ return (i >= 1) and (i <= self:getVolume())
+end
+
+function VoxelArea:iter(minx, miny, minz, maxx, maxy, maxz)
+ local i = self:index(minx, miny, minz) - 1
+ local xrange = maxx - minx + 1
+ local nextaction = i + 1 + xrange
+
+ local y = 0
+ local yrange = maxy - miny + 1
+ local yreqstride = self.ystride - xrange
+
+ local z = 0
+ local zrange = maxz - minz + 1
+ local multistride = self.zstride - ((yrange - 1) * self.ystride + xrange)
+
+ return function()
+ -- continue i until it needs to jump
+ i = i + 1
+ if i ~= nextaction then
+ return i
+ end
+
+ -- continue y until maxy is exceeded
+ y = y + 1
+ if y ~= yrange then
+ -- set i to index(minx, miny + y, minz + z) - 1
+ i = i + yreqstride
+ nextaction = i + xrange
+ return i
+ end
+
+ -- continue z until maxz is exceeded
+ z = z + 1
+ if z == zrange then
+ -- cuboid finished, return nil
+ return
+ end
+
+ -- set i to index(minx, miny, minz + z) - 1
+ i = i + multistride
+
+ y = 0
+ nextaction = i + xrange
+ return i
+ end
+end
+
+function VoxelArea:iterp(minp, maxp)
+ return self:iter(minp.x, minp.y, minp.z, maxp.x, maxp.y, maxp.z)
+end
diff --git a/builtin/init.lua b/builtin/init.lua
new file mode 100644
index 000000000..73ab5cfd0
--- /dev/null
+++ b/builtin/init.lua
@@ -0,0 +1,52 @@
+--
+-- This file contains built-in stuff in Minetest implemented in Lua.
+--
+-- It is always loaded and executed after registration of the C API,
+-- before loading and running any mods.
+--
+
+-- Initialize some very basic things
+function core.debug(...) core.log(table.concat({...}, "\t")) end
+if core.print then
+ local core_print = core.print
+ -- Override native print and use
+ -- terminal if that's turned on
+ function print(...)
+ local n, t = select("#", ...), {...}
+ for i = 1, n do
+ t[i] = tostring(t[i])
+ end
+ core_print(table.concat(t, "\t"))
+ end
+ core.print = nil -- don't pollute our namespace
+end
+math.randomseed(os.time())
+minetest = core
+
+-- Load other files
+local scriptdir = core.get_builtin_path() .. DIR_DELIM
+local gamepath = scriptdir .. "game" .. DIR_DELIM
+local clientpath = scriptdir .. "client" .. DIR_DELIM
+local commonpath = scriptdir .. "common" .. DIR_DELIM
+local asyncpath = scriptdir .. "async" .. DIR_DELIM
+
+dofile(commonpath .. "strict.lua")
+dofile(commonpath .. "serialize.lua")
+dofile(commonpath .. "misc_helpers.lua")
+
+if INIT == "game" then
+ dofile(gamepath .. "init.lua")
+elseif INIT == "mainmenu" then
+ local mm_script = core.settings:get("main_menu_script")
+ if mm_script and mm_script ~= "" then
+ dofile(mm_script)
+ else
+ dofile(core.get_mainmenu_path() .. DIR_DELIM .. "init.lua")
+ end
+elseif INIT == "async" then
+ dofile(asyncpath .. "init.lua")
+elseif INIT == "client" then
+ dofile(clientpath .. "init.lua")
+else
+ error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT)))
+end
diff --git a/builtin/mainmenu/common.lua b/builtin/mainmenu/common.lua
new file mode 100644
index 000000000..7eb941775
--- /dev/null
+++ b/builtin/mainmenu/common.lua
@@ -0,0 +1,336 @@
+--Minetest
+--Copyright (C) 2014 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+--------------------------------------------------------------------------------
+-- Global menu data
+--------------------------------------------------------------------------------
+menudata = {}
+
+--------------------------------------------------------------------------------
+-- Local cached values
+--------------------------------------------------------------------------------
+local min_supp_proto, max_supp_proto
+
+function common_update_cached_supp_proto()
+ min_supp_proto = core.get_min_supp_proto()
+ max_supp_proto = core.get_max_supp_proto()
+end
+common_update_cached_supp_proto()
+--------------------------------------------------------------------------------
+-- Menu helper functions
+--------------------------------------------------------------------------------
+
+--------------------------------------------------------------------------------
+local function render_client_count(n)
+ if n > 99 then return '99+'
+ elseif n >= 0 then return tostring(n)
+ else return '?' end
+end
+
+local function configure_selected_world_params(idx)
+ local worldconfig = modmgr.get_worldconfig(menudata.worldlist:get_list()[idx].path)
+ if worldconfig.creative_mode then
+ core.settings:set("creative_mode", worldconfig.creative_mode)
+ end
+ if worldconfig.enable_damage then
+ core.settings:set("enable_damage", worldconfig.enable_damage)
+ end
+end
+
+--------------------------------------------------------------------------------
+function image_column(tooltip, flagname)
+ return "image,tooltip=" .. core.formspec_escape(tooltip) .. "," ..
+ "0=" .. core.formspec_escape(defaulttexturedir .. "blank.png") .. "," ..
+ "1=" .. core.formspec_escape(defaulttexturedir ..
+ (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 order_favorite_list(list)
+ local res = {}
+ --orders the favorite list after support
+ for i = 1, #list do
+ local fav = list[i]
+ if is_server_protocol_compat(fav.proto_min, fav.proto_max) then
+ res[#res + 1] = fav
+ end
+ end
+ for i = 1, #list do
+ local fav = list[i]
+ if not is_server_protocol_compat(fav.proto_min, fav.proto_max) then
+ res[#res + 1] = fav
+ end
+ end
+ return res
+end
+
+--------------------------------------------------------------------------------
+function render_serverlist_row(spec, is_favorite)
+ local text = ""
+ if spec.name then
+ text = text .. core.formspec_escape(spec.name:trim())
+ elseif spec.address then
+ text = text .. spec.address:trim()
+ if spec.port then
+ text = text .. ":" .. spec.port
+ end
+ end
+
+ local details = ""
+ local grey_out = not is_server_protocol_compat(spec.proto_min, spec.proto_max)
+
+ if is_favorite then
+ details = "1,"
+ else
+ details = "0,"
+ end
+
+ if spec.ping then
+ local ping = spec.ping * 1000
+ if ping <= 50 then
+ details = details .. "2,"
+ elseif ping <= 100 then
+ details = details .. "3,"
+ elseif ping <= 250 then
+ details = details .. "4,"
+ else
+ details = details .. "5,"
+ end
+ else
+ details = details .. "0,"
+ end
+
+ if spec.clients and spec.clients_max then
+ local clients_color = ''
+ local clients_percent = 100 * spec.clients / spec.clients_max
+
+ -- Choose a color depending on how many clients are connected
+ -- (relatively to clients_max)
+ if grey_out then clients_color = '#aaaaaa'
+ elseif spec.clients == 0 then clients_color = '' -- 0 players: default/white
+ elseif clients_percent <= 60 then clients_color = '#a1e587' -- 0-60%: green
+ elseif clients_percent <= 90 then clients_color = '#ffdc97' -- 60-90%: yellow
+ elseif clients_percent == 100 then clients_color = '#dd5b5b' -- full server: red (darker)
+ else clients_color = '#ffba97' -- 90-100%: orange
+ end
+
+ details = details .. clients_color .. ',' ..
+ render_client_count(spec.clients) .. ',/,' ..
+ render_client_count(spec.clients_max) .. ','
+
+ elseif grey_out then
+ details = details .. '#aaaaaa,?,/,?,'
+ else
+ details = details .. ',?,/,?,'
+ end
+
+ if spec.creative then
+ details = details .. "1,"
+ else
+ details = details .. "0,"
+ end
+
+ if spec.damage then
+ details = details .. "1,"
+ else
+ details = details .. "0,"
+ end
+
+ if spec.pvp then
+ details = details .. "1,"
+ else
+ details = details .. "0,"
+ end
+
+ return details .. (grey_out and '#aaaaaa,' or ',') .. text
+end
+
+--------------------------------------------------------------------------------
+os.tempfolder = function()
+ if core.settings:get("TMPFolder") then
+ return core.settings:get("TMPFolder") .. DIR_DELIM .. "MT_" .. math.random(0,10000)
+ end
+
+ local filetocheck = os.tmpname()
+ os.remove(filetocheck)
+
+ local randname = "MTTempModFolder_" .. math.random(0,10000)
+ if DIR_DELIM == "\\" then
+ local tempfolder = os.getenv("TEMP")
+ return tempfolder .. filetocheck
+ else
+ local backstring = filetocheck:reverse()
+ return filetocheck:sub(0,filetocheck:len()-backstring:find(DIR_DELIM)+1) ..randname
+ end
+
+end
+
+--------------------------------------------------------------------------------
+function menu_render_worldlist()
+ local retval = ""
+ local current_worldlist = menudata.worldlist:get_list()
+
+ for i, v in ipairs(current_worldlist) do
+ if retval ~= "" then retval = retval .. "," end
+ retval = retval .. core.formspec_escape(v.name) ..
+ " \\[" .. core.formspec_escape(v.gameid) .. "\\]"
+ end
+
+ return retval
+end
+
+--------------------------------------------------------------------------------
+function menu_handle_key_up_down(fields, textlist, settingname)
+ local oldidx, newidx = core.get_textlist_index(textlist), 1
+ if fields.key_up or fields.key_down then
+ if fields.key_up and oldidx and oldidx > 1 then
+ newidx = oldidx - 1
+ elseif fields.key_down and oldidx and
+ oldidx < menudata.worldlist:size() then
+ newidx = oldidx + 1
+ end
+ core.settings:set(settingname, menudata.worldlist:get_raw_index(newidx))
+ configure_selected_world_params(newidx)
+ return true
+ end
+ return false
+end
+
+--------------------------------------------------------------------------------
+function asyncOnlineFavourites()
+ if not menudata.public_known then
+ menudata.public_known = {{
+ name = fgettext("Loading..."),
+ description = fgettext_ne("Try reenabling public serverlist and check your internet connection.")
+ }}
+ end
+ menudata.favorites = menudata.public_known
+ menudata.favorites_is_public = true
+
+ if not menudata.public_downloading then
+ menudata.public_downloading = true
+ else
+ return
+ end
+
+ core.handle_async(
+ function(param)
+ return core.get_favorites("online")
+ end,
+ nil,
+ function(result)
+ menudata.public_downloading = nil
+ local favs = order_favorite_list(result)
+ if favs[1] then
+ menudata.public_known = favs
+ menudata.favorites = menudata.public_known
+ menudata.favorites_is_public = true
+ end
+ core.event_handler("Refresh")
+ end
+ )
+end
+
+--------------------------------------------------------------------------------
+function text2textlist(xpos, ypos, width, height, tl_name, textlen, text, transparency)
+ local textlines = core.wrap_text(text, textlen, true)
+ local retval = "textlist[" .. xpos .. "," .. ypos .. ";" .. width ..
+ "," .. height .. ";" .. tl_name .. ";"
+
+ for i = 1, #textlines do
+ textlines[i] = textlines[i]:gsub("\r", "")
+ retval = retval .. core.formspec_escape(textlines[i]) .. ","
+ end
+
+ retval = retval .. ";0;"
+ if transparency then retval = retval .. "true" end
+ retval = retval .. "]"
+
+ return retval
+end
+
+--------------------------------------------------------------------------------
+function is_server_protocol_compat(server_proto_min, server_proto_max)
+ if (not server_proto_min) or (not server_proto_max) then
+ -- There is no info. Assume the best and act as if we would be compatible.
+ return true
+ end
+ return min_supp_proto <= server_proto_max and max_supp_proto >= server_proto_min
+end
+--------------------------------------------------------------------------------
+function is_server_protocol_compat_or_error(server_proto_min, server_proto_max)
+ if not is_server_protocol_compat(server_proto_min, server_proto_max) then
+ local server_prot_ver_info, client_prot_ver_info
+ local s_p_min = server_proto_min
+ local s_p_max = server_proto_max
+
+ if s_p_min ~= s_p_max then
+ server_prot_ver_info = fgettext_ne("Server supports protocol versions between $1 and $2. ",
+ s_p_min, s_p_max)
+ else
+ server_prot_ver_info = fgettext_ne("Server enforces protocol version $1. ",
+ s_p_min)
+ end
+ if min_supp_proto ~= max_supp_proto then
+ client_prot_ver_info= fgettext_ne("We support protocol versions between version $1 and $2.",
+ min_supp_proto, max_supp_proto)
+ else
+ client_prot_ver_info = fgettext_ne("We only support protocol version $1.", min_supp_proto)
+ end
+ gamedata.errormessage = fgettext_ne("Protocol version mismatch. ")
+ .. server_prot_ver_info
+ .. client_prot_ver_info
+ return false
+ end
+
+ return true
+end
+--------------------------------------------------------------------------------
+function menu_worldmt(selected, setting, value)
+ local world = menudata.worldlist:get_list()[selected]
+ if world then
+ local filename = world.path .. DIR_DELIM .. "world.mt"
+ local world_conf = Settings(filename)
+
+ if value then
+ if not world_conf:write() then
+ core.log("error", "Failed to write world config file")
+ end
+ world_conf:set(setting, value)
+ world_conf:write()
+ else
+ return world_conf:get(setting)
+ end
+ else
+ return nil
+ end
+end
+
+function menu_worldmt_legacy(selected)
+ local modes_names = {"creative_mode", "enable_damage", "server_announce"}
+ for _, mode_name in pairs(modes_names) do
+ local mode_val = menu_worldmt(selected, mode_name)
+ if mode_val then
+ core.settings:set(mode_name, mode_val)
+ else
+ menu_worldmt(selected, mode_name, core.settings:get(mode_name))
+ end
+ end
+end
diff --git a/builtin/mainmenu/dlg_config_world.lua b/builtin/mainmenu/dlg_config_world.lua
new file mode 100644
index 000000000..fcedadea8
--- /dev/null
+++ b/builtin/mainmenu/dlg_config_world.lua
@@ -0,0 +1,287 @@
+--Minetest
+--Copyright (C) 2013 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+--------------------------------------------------------------------------------
+
+local enabled_all = false
+
+local function modname_valid(name)
+ return not name:find("[^a-z0-9_]")
+end
+
+local function get_formspec(data)
+ local mod = data.list:get_list()[data.selected_mod]
+
+ local retval =
+ "size[11.5,7.5,true]" ..
+ "label[0.5,0;" .. fgettext("World:") .. "]" ..
+ "label[1.75,0;" .. data.worldspec.name .. "]"
+
+ if mod == nil then
+ mod = {name=""}
+ end
+
+ local hard_deps, soft_deps = modmgr.get_dependencies(mod.path)
+
+ retval = retval ..
+ "label[0,0.7;" .. fgettext("Mod:") .. "]" ..
+ "label[0.75,0.7;" .. mod.name .. "]" ..
+ "label[0,1.25;" .. fgettext("Dependencies:") .. "]" ..
+ "textlist[0,1.75;5,2.125;world_config_depends;" ..
+ hard_deps .. ";0]" ..
+ "label[0,3.875;" .. fgettext("Optional dependencies:") .. "]" ..
+ "textlist[0,4.375;5,1.8;world_config_optdepends;" ..
+ soft_deps .. ";0]" ..
+ "button[3.25,7;2.5,0.5;btn_config_world_save;" .. fgettext("Save") .. "]" ..
+ "button[5.75,7;2.5,0.5;btn_config_world_cancel;" .. fgettext("Cancel") .. "]"
+
+ if mod and mod.name ~= "" and not mod.is_game_content then
+ if mod.is_modpack then
+ local rawlist = data.list:get_raw_list()
+
+ local all_enabled = true
+ for j = 1, #rawlist, 1 do
+ if rawlist[j].modpack == mod.name and not rawlist[j].enabled then
+ all_enabled = false
+ break
+ end
+ end
+
+ if all_enabled then
+ retval = retval .. "button[5.5,0.125;2.5,0.5;btn_mp_disable;" ..
+ fgettext("Disable MP") .. "]"
+ else
+ retval = retval .. "button[5.5,0.125;2.5,0.5;btn_mp_enable;" ..
+ fgettext("Enable MP") .. "]"
+ end
+ else
+ if mod.enabled then
+ retval = retval .. "checkbox[5.5,-0.125;cb_mod_enable;" ..
+ fgettext("enabled") .. ";true]"
+ else
+ retval = retval .. "checkbox[5.5,-0.125;cb_mod_enable;" ..
+ fgettext("enabled") .. ";false]"
+ end
+ end
+ end
+ if enabled_all then
+ retval = retval ..
+ "button[8.75,0.125;2.5,0.5;btn_disable_all_mods;" .. fgettext("Disable all") .. "]"
+ else
+ retval = retval ..
+ "button[8.75,0.125;2.5,0.5;btn_enable_all_mods;" .. fgettext("Enable all") .. "]"
+ end
+ retval = retval ..
+ "tablecolumns[color;tree;text]" ..
+ "table[5.5,0.75;5.75,6;world_config_modlist;"
+ retval = retval .. modmgr.render_modlist(data.list)
+ retval = retval .. ";" .. data.selected_mod .."]"
+
+ return retval
+end
+
+local function enable_mod(this, toset)
+ local mod = this.data.list:get_list()[this.data.selected_mod]
+
+ if mod.is_game_content then
+ -- game mods can't be enabled or disabled
+ elseif not mod.is_modpack then
+ if toset == nil then
+ mod.enabled = not mod.enabled
+ else
+ mod.enabled = toset
+ end
+ else
+ local list = this.data.list:get_raw_list()
+ for i=1,#list,1 do
+ if list[i].modpack == mod.name then
+ if toset == nil then
+ toset = not list[i].enabled
+ end
+ list[i].enabled = toset
+ end
+ end
+ end
+end
+
+
+local function handle_buttons(this, fields)
+ if fields["world_config_modlist"] ~= nil then
+ local event = core.explode_table_event(fields["world_config_modlist"])
+ this.data.selected_mod = event.row
+ core.settings:set("world_config_selected_mod", event.row)
+
+ if event.type == "DCL" then
+ enable_mod(this)
+ end
+
+ return true
+ end
+
+ if fields["key_enter"] ~= nil then
+ enable_mod(this)
+ return true
+ end
+
+ if fields["cb_mod_enable"] ~= nil then
+ local toset = core.is_yes(fields["cb_mod_enable"])
+ enable_mod(this,toset)
+ return true
+ end
+
+ if fields["btn_mp_enable"] ~= nil or
+ fields["btn_mp_disable"] then
+ local toset = (fields["btn_mp_enable"] ~= nil)
+ enable_mod(this,toset)
+ return true
+ end
+
+ if fields["btn_config_world_save"] then
+ local filename = this.data.worldspec.path ..
+ DIR_DELIM .. "world.mt"
+
+ local worldfile = Settings(filename)
+ local mods = worldfile:to_table()
+
+ local rawlist = this.data.list:get_raw_list()
+
+ local i,mod
+ for i,mod in ipairs(rawlist) do
+ if not mod.is_modpack and
+ not mod.is_game_content then
+ if modname_valid(mod.name) then
+ worldfile:set("load_mod_"..mod.name, tostring(mod.enabled))
+ else
+ if mod.enabled then
+ gamedata.errormessage = fgettext_ne("Failed to enable mod \"$1\" as it contains disallowed characters. Only chararacters [a-z0-9_] are allowed.", mod.name)
+ end
+ end
+ mods["load_mod_"..mod.name] = nil
+ end
+ end
+
+ -- Remove mods that are not present anymore
+ for key,value in pairs(mods) do
+ if key:sub(1,9) == "load_mod_" then
+ worldfile:remove(key)
+ end
+ end
+
+ if not worldfile:write() then
+ core.log("error", "Failed to write world config file")
+ end
+
+ this:delete()
+ return true
+ end
+
+ if fields["btn_config_world_cancel"] then
+ this:delete()
+ return true
+ end
+
+ if fields.btn_enable_all_mods then
+ local list = this.data.list:get_raw_list()
+
+ for i = 1, #list do
+ if not list[i].is_game_content
+ and not list[i].is_modpack then
+ list[i].enabled = true
+ end
+ end
+ enabled_all = true
+ return true
+ end
+
+ if fields.btn_disable_all_mods then
+ local list = this.data.list:get_raw_list()
+
+ for i = 1, #list do
+ if not list[i].is_game_content
+ and not list[i].is_modpack then
+ list[i].enabled = false
+ end
+ end
+ enabled_all = false
+ return true
+ end
+
+ return false
+end
+
+function create_configure_world_dlg(worldidx)
+ local dlg = dialog_create("sp_config_world",
+ get_formspec,
+ handle_buttons,
+ nil)
+
+ dlg.data.selected_mod = tonumber(core.settings:get("world_config_selected_mod"))
+ if dlg.data.selected_mod == nil then
+ dlg.data.selected_mod = 0
+ end
+
+ dlg.data.worldspec = core.get_worlds()[worldidx]
+ if dlg.data.worldspec == nil then dlg:delete() return nil end
+
+ dlg.data.worldconfig = modmgr.get_worldconfig(dlg.data.worldspec.path)
+
+ if dlg.data.worldconfig == nil or dlg.data.worldconfig.id == nil or
+ dlg.data.worldconfig.id == "" then
+
+ dlg:delete()
+ return nil
+ end
+
+ dlg.data.list = filterlist.create(
+ modmgr.preparemodlist, --refresh
+ modmgr.comparemod, --compare
+ function(element,uid) --uid match
+ if element.name == uid then
+ return true
+ end
+ end,
+ function(element, criteria)
+ if criteria.hide_game and
+ element.is_game_content then
+ return false
+ end
+
+ if criteria.hide_modpackcontents and
+ element.modpack ~= nil then
+ return false
+ end
+ return true
+ end, --filter
+ { worldpath= dlg.data.worldspec.path,
+ gameid = dlg.data.worldspec.gameid }
+ )
+
+
+ if dlg.data.selected_mod > dlg.data.list:size() then
+ dlg.data.selected_mod = 0
+ end
+
+ dlg.data.list:set_filtercriteria(
+ {
+ hide_game=dlg.data.hide_gamemods,
+ hide_modpackcontents= dlg.data.hide_modpackcontents
+ })
+ dlg.data.list:add_sort_mechanism("alphabetic", sort_mod_list)
+ dlg.data.list:set_sortmode("alphabetic")
+
+ return dlg
+end
diff --git a/builtin/mainmenu/dlg_create_world.lua b/builtin/mainmenu/dlg_create_world.lua
new file mode 100644
index 000000000..e9ca7799f
--- /dev/null
+++ b/builtin/mainmenu/dlg_create_world.lua
@@ -0,0 +1,143 @@
+--Minetest
+--Copyright (C) 2014 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+local function create_world_formspec(dialogdata)
+ local mapgens = core.get_mapgen_names()
+
+ local current_seed = core.settings:get("fixed_map_seed") or ""
+ local current_mg = core.settings:get("mg_name")
+
+ local mglist = ""
+ local selindex = 1
+ local i = 1
+ for k,v in pairs(mapgens) do
+ if current_mg == v then
+ selindex = i
+ end
+ i = i + 1
+ mglist = mglist .. v .. ","
+ end
+ mglist = mglist:sub(1, -2)
+
+ local gameid = core.settings:get("menu_last_game")
+
+ local game, gameidx = nil , 0
+ if gameid ~= nil then
+ game, gameidx = gamemgr.find_by_gameid(gameid)
+
+ if gameidx == nil then
+ gameidx = 0
+ end
+ end
+
+ current_seed = core.formspec_escape(current_seed)
+ local retval =
+ "size[11.5,6.5,true]" ..
+ "label[2,0;" .. fgettext("World name") .. "]"..
+ "field[4.5,0.4;6,0.5;te_world_name;;]" ..
+
+ "label[2,1;" .. fgettext("Seed") .. "]"..
+ "field[4.5,1.4;6,0.5;te_seed;;".. current_seed .. "]" ..
+
+ "label[2,2;" .. fgettext("Mapgen") .. "]"..
+ "dropdown[4.2,2;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]" ..
+
+ "label[2,3;" .. fgettext("Game") .. "]"..
+ "textlist[4.2,3;5.8,2.3;games;" .. gamemgr.gamelist() ..
+ ";" .. gameidx .. ";true]" ..
+
+ "button[3.25,6;2.5,0.5;world_create_confirm;" .. fgettext("Create") .. "]" ..
+ "button[5.75,6;2.5,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]"
+
+ if #gamemgr.games == 0 then
+ retval = retval .. "box[2,4;8,1;#ff8800]label[2.25,4;" ..
+ fgettext("You have no subgames installed.") .. "]label[2.25,4.4;" ..
+ fgettext("Download one from minetest.net") .. "]"
+ elseif #gamemgr.games == 1 and gamemgr.games[1].id == "minimal" then
+ retval = retval .. "box[1.75,4;8.7,1;#ff8800]label[2,4;" ..
+ fgettext("Warning: The minimal development test is meant for developers.") .. "]label[2,4.4;" ..
+ fgettext("Download a subgame, such as minetest_game, from minetest.net") .. "]"
+ end
+
+ return retval
+
+end
+
+local function create_world_buttonhandler(this, fields)
+
+ if fields["world_create_confirm"] or
+ fields["key_enter"] then
+
+ local worldname = fields["te_world_name"]
+ local gameindex = core.get_textlist_index("games")
+
+ if gameindex ~= nil and
+ worldname ~= "" then
+
+ local message = nil
+
+ core.settings:set("fixed_map_seed", fields["te_seed"])
+
+ if not menudata.worldlist:uid_exists_raw(worldname) then
+ core.settings:set("mg_name",fields["dd_mapgen"])
+ message = core.create_world(worldname,gameindex)
+ else
+ message = fgettext("A world named \"$1\" already exists", worldname)
+ end
+
+ if message ~= nil then
+ gamedata.errormessage = message
+ else
+ core.settings:set("menu_last_game",gamemgr.games[gameindex].id)
+ if this.data.update_worldlist_filter then
+ menudata.worldlist:set_filtercriteria(gamemgr.games[gameindex].id)
+ mm_texture.update("singleplayer", gamemgr.games[gameindex].id)
+ end
+ menudata.worldlist:refresh()
+ core.settings:set("mainmenu_last_selected_world",
+ menudata.worldlist:raw_index_by_uid(worldname))
+ end
+ else
+ gamedata.errormessage =
+ fgettext("No worldname given or no game selected")
+ end
+ this:delete()
+ return true
+ end
+
+ if fields["games"] then
+ return true
+ end
+
+ if fields["world_create_cancel"] then
+ this:delete()
+ return true
+ end
+
+ return false
+end
+
+
+function create_create_world_dlg(update_worldlistfilter)
+ local retval = dialog_create("sp_create_world",
+ create_world_formspec,
+ create_world_buttonhandler,
+ nil)
+ retval.update_worldlist_filter = update_worldlistfilter
+
+ return retval
+end
diff --git a/builtin/mainmenu/dlg_delete_mod.lua b/builtin/mainmenu/dlg_delete_mod.lua
new file mode 100644
index 000000000..2efd70414
--- /dev/null
+++ b/builtin/mainmenu/dlg_delete_mod.lua
@@ -0,0 +1,69 @@
+--Minetest
+--Copyright (C) 2014 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+--------------------------------------------------------------------------------
+
+local function delete_mod_formspec(dialogdata)
+
+ dialogdata.mod = modmgr.global_mods:get_list()[dialogdata.selected]
+
+ local retval =
+ "size[11.5,4.5,true]" ..
+ "label[2,2;" ..
+ fgettext("Are you sure you want to delete \"$1\"?", dialogdata.mod.name) .. "]"..
+ "button[3.25,3.5;2.5,0.5;dlg_delete_mod_confirm;" .. fgettext("Delete") .. "]" ..
+ "button[5.75,3.5;2.5,0.5;dlg_delete_mod_cancel;" .. fgettext("Cancel") .. "]"
+
+ return retval
+end
+
+--------------------------------------------------------------------------------
+local function delete_mod_buttonhandler(this, fields)
+ if fields["dlg_delete_mod_confirm"] ~= nil then
+
+ if this.data.mod.path ~= nil and
+ this.data.mod.path ~= "" and
+ this.data.mod.path ~= core.get_modpath() then
+ if not core.delete_dir(this.data.mod.path) then
+ gamedata.errormessage = fgettext("Modmgr: failed to delete \"$1\"", this.data.mod.path)
+ end
+ modmgr.refresh_globals()
+ else
+ gamedata.errormessage = fgettext("Modmgr: invalid modpath \"$1\"", this.data.mod.path)
+ end
+ this:delete()
+ return true
+ end
+
+ if fields["dlg_delete_mod_cancel"] then
+ this:delete()
+ return true
+ end
+
+ return false
+end
+
+--------------------------------------------------------------------------------
+function create_delete_mod_dlg(selected_index)
+
+ local retval = dialog_create("dlg_delete_mod",
+ delete_mod_formspec,
+ delete_mod_buttonhandler,
+ nil)
+ retval.data.selected = selected_index
+ return retval
+end
diff --git a/builtin/mainmenu/dlg_delete_world.lua b/builtin/mainmenu/dlg_delete_world.lua
new file mode 100644
index 000000000..df1091033
--- /dev/null
+++ b/builtin/mainmenu/dlg_delete_world.lua
@@ -0,0 +1,61 @@
+--Minetest
+--Copyright (C) 2014 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+local function delete_world_formspec(dialogdata)
+ local retval =
+ "size[10,2.5,true]" ..
+ "label[0.5,0.5;" ..
+ fgettext("Delete World \"$1\"?", dialogdata.delete_name) .. "]" ..
+ "button[0.5,1.5;2.5,0.5;world_delete_confirm;" .. fgettext("Delete") .. "]" ..
+ "button[7.0,1.5;2.5,0.5;world_delete_cancel;" .. fgettext("Cancel") .. "]"
+ return retval
+end
+
+local function delete_world_buttonhandler(this, fields)
+ if fields["world_delete_confirm"] then
+ if this.data.delete_index > 0 and
+ this.data.delete_index <= #menudata.worldlist:get_raw_list() then
+ core.delete_world(this.data.delete_index)
+ menudata.worldlist:refresh()
+ end
+ this:delete()
+ return true
+ end
+
+ if fields["world_delete_cancel"] then
+ this:delete()
+ return true
+ end
+
+ return false
+end
+
+
+function create_delete_world_dlg(name_to_del, index_to_del)
+ assert(name_to_del ~= nil and type(name_to_del) == "string" and name_to_del ~= "")
+ assert(index_to_del ~= nil and type(index_to_del) == "number")
+
+ local retval = dialog_create("delete_world",
+ delete_world_formspec,
+ delete_world_buttonhandler,
+ nil)
+ retval.data.delete_name = name_to_del
+ retval.data.delete_index = index_to_del
+
+ return retval
+end
diff --git a/builtin/mainmenu/dlg_rename_modpack.lua b/builtin/mainmenu/dlg_rename_modpack.lua
new file mode 100644
index 000000000..959c65d9b
--- /dev/null
+++ b/builtin/mainmenu/dlg_rename_modpack.lua
@@ -0,0 +1,67 @@
+--Minetest
+--Copyright (C) 2014 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+--------------------------------------------------------------------------------
+
+local function rename_modpack_formspec(dialogdata)
+
+ dialogdata.mod = modmgr.global_mods:get_list()[dialogdata.selected]
+
+ local retval =
+ "size[11.5,4.5,true]" ..
+ "field[2.5,2;7,0.5;te_modpack_name;".. fgettext("Rename Modpack:") .. ";" ..
+ dialogdata.mod.name .. "]" ..
+ "button[3.25,3.5;2.5,0.5;dlg_rename_modpack_confirm;"..
+ fgettext("Accept") .. "]" ..
+ "button[5.75,3.5;2.5,0.5;dlg_rename_modpack_cancel;"..
+ fgettext("Cancel") .. "]"
+
+ return retval
+end
+
+--------------------------------------------------------------------------------
+local function rename_modpack_buttonhandler(this, fields)
+ if fields["dlg_rename_modpack_confirm"] ~= nil then
+ local oldpath = core.get_modpath() .. DIR_DELIM .. this.data.mod.name
+ local targetpath = core.get_modpath() .. DIR_DELIM .. fields["te_modpack_name"]
+ core.copy_dir(oldpath,targetpath,false)
+ modmgr.refresh_globals()
+ modmgr.selected_mod = modmgr.global_mods:get_current_index(
+ modmgr.global_mods:raw_index_by_uid(fields["te_modpack_name"]))
+
+ this:delete()
+ return true
+ end
+
+ if fields["dlg_rename_modpack_cancel"] then
+ this:delete()
+ return true
+ end
+
+ return false
+end
+
+--------------------------------------------------------------------------------
+function create_rename_modpack_dlg(selected_index)
+
+ local retval = dialog_create("dlg_delete_mod",
+ rename_modpack_formspec,
+ rename_modpack_buttonhandler,
+ nil)
+ retval.data.selected = selected_index
+ return retval
+end
diff --git a/builtin/mainmenu/dlg_settings_advanced.lua b/builtin/mainmenu/dlg_settings_advanced.lua
new file mode 100644
index 000000000..206ce1620
--- /dev/null
+++ b/builtin/mainmenu/dlg_settings_advanced.lua
@@ -0,0 +1,772 @@
+--Minetest
+--Copyright (C) 2015 PilzAdam
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+local FILENAME = "settingtypes.txt"
+
+local CHAR_CLASSES = {
+ SPACE = "[%s]",
+ VARIABLE = "[%w_%-%.]",
+ INTEGER = "[+-]?[%d]",
+ FLOAT = "[+-]?[%d%.]",
+ FLAGS = "[%w_%-%.,]",
+}
+
+-- returns error message, or nil
+local function parse_setting_line(settings, line, read_all, base_level, allow_secure)
+ -- comment
+ local comment = line:match("^#" .. CHAR_CLASSES.SPACE .. "*(.*)$")
+ if comment then
+ if settings.current_comment == "" then
+ settings.current_comment = comment
+ else
+ settings.current_comment = settings.current_comment .. "\n" .. comment
+ end
+ return
+ end
+
+ -- clear current_comment so only comments directly above a setting are bound to it
+ -- but keep a local reference to it for variables in the current line
+ local current_comment = settings.current_comment
+ settings.current_comment = ""
+
+ -- empty lines
+ if line:match("^" .. CHAR_CLASSES.SPACE .. "*$") then
+ return
+ end
+
+ -- category
+ local stars, category = line:match("^%[([%*]*)([^%]]+)%]$")
+ if category then
+ table.insert(settings, {
+ name = category,
+ level = stars:len() + base_level,
+ type = "category",
+ })
+ return
+ end
+
+ -- settings
+ local first_part, name, readable_name, setting_type = line:match("^"
+ -- this first capture group matches the whole first part,
+ -- so we can later strip it from the rest of the line
+ .. "("
+ .. "([" .. CHAR_CLASSES.VARIABLE .. "+)" -- variable name
+ .. CHAR_CLASSES.SPACE .. "*"
+ .. "%(([^%)]*)%)" -- readable name
+ .. CHAR_CLASSES.SPACE .. "*"
+ .. "(" .. CHAR_CLASSES.VARIABLE .. "+)" -- type
+ .. CHAR_CLASSES.SPACE .. "*"
+ .. ")")
+
+ if not first_part then
+ return "Invalid line"
+ end
+
+ if name:match("secure%.[.]*") and not allow_secure then
+ return "Tried to add \"secure.\" setting"
+ end
+
+ if readable_name == "" then
+ readable_name = nil
+ end
+ local remaining_line = line:sub(first_part:len() + 1)
+
+ if setting_type == "int" then
+ local default, min, max = remaining_line:match("^"
+ -- first int is required, the last 2 are optional
+ .. "(" .. CHAR_CLASSES.INTEGER .. "+)" .. CHAR_CLASSES.SPACE .. "*"
+ .. "(" .. CHAR_CLASSES.INTEGER .. "*)" .. CHAR_CLASSES.SPACE .. "*"
+ .. "(" .. CHAR_CLASSES.INTEGER .. "*)"
+ .. "$")
+
+ if not default or not tonumber(default) then
+ return "Invalid integer setting"
+ end
+
+ min = tonumber(min)
+ max = tonumber(max)
+ table.insert(settings, {
+ name = name,
+ readable_name = readable_name,
+ type = "int",
+ default = default,
+ min = min,
+ max = max,
+ comment = current_comment,
+ })
+ return
+ end
+
+ if setting_type == "string" or setting_type == "noise_params"
+ or setting_type == "key" or setting_type == "v3f" then
+ local default = remaining_line:match("^(.*)$")
+
+ if not default then
+ return "Invalid string setting"
+ end
+ if setting_type == "key" and not read_all then
+ -- ignore key type if read_all is false
+ return
+ end
+
+ table.insert(settings, {
+ name = name,
+ readable_name = readable_name,
+ type = setting_type,
+ default = default,
+ comment = current_comment,
+ })
+ return
+ end
+
+ if setting_type == "bool" then
+ if remaining_line ~= "false" and remaining_line ~= "true" then
+ return "Invalid boolean setting"
+ end
+
+ table.insert(settings, {
+ name = name,
+ readable_name = readable_name,
+ type = "bool",
+ default = remaining_line,
+ comment = current_comment,
+ })
+ return
+ end
+
+ if setting_type == "float" then
+ local default, min, max = remaining_line:match("^"
+ -- first float is required, the last 2 are optional
+ .. "(" .. CHAR_CLASSES.FLOAT .. "+)" .. CHAR_CLASSES.SPACE .. "*"
+ .. "(" .. CHAR_CLASSES.FLOAT .. "*)" .. CHAR_CLASSES.SPACE .. "*"
+ .. "(" .. CHAR_CLASSES.FLOAT .. "*)"
+ .."$")
+
+ if not default or not tonumber(default) then
+ return "Invalid float setting"
+ end
+
+ min = tonumber(min)
+ max = tonumber(max)
+ table.insert(settings, {
+ name = name,
+ readable_name = readable_name,
+ type = "float",
+ default = default,
+ min = min,
+ max = max,
+ comment = current_comment,
+ })
+ return
+ end
+
+ if setting_type == "enum" then
+ local default, values = remaining_line:match("^"
+ -- first value (default) may be empty (i.e. is optional)
+ .. "(" .. CHAR_CLASSES.VARIABLE .. "*)" .. CHAR_CLASSES.SPACE .. "*"
+ .. "(" .. CHAR_CLASSES.FLAGS .. "+)"
+ .. "$")
+
+ if not default or values == "" then
+ return "Invalid enum setting"
+ end
+
+ table.insert(settings, {
+ name = name,
+ readable_name = readable_name,
+ type = "enum",
+ default = default,
+ values = values:split(",", true),
+ comment = current_comment,
+ })
+ return
+ end
+
+ if setting_type == "path" then
+ local default = remaining_line:match("^(.*)$")
+
+ if not default then
+ return "Invalid path setting"
+ end
+
+ table.insert(settings, {
+ name = name,
+ readable_name = readable_name,
+ type = "path",
+ default = default,
+ comment = current_comment,
+ })
+ return
+ end
+
+ if setting_type == "flags" then
+ local default, possible = remaining_line:match("^"
+ -- first value (default) may be empty (i.e. is optional)
+ -- this is implemented by making the last value optional, and
+ -- swapping them around if it turns out empty.
+ .. "(" .. CHAR_CLASSES.FLAGS .. "+)" .. CHAR_CLASSES.SPACE .. "*"
+ .. "(" .. CHAR_CLASSES.FLAGS .. "*)"
+ .. "$")
+
+ if not default or not possible then
+ return "Invalid flags setting"
+ end
+
+ if possible == "" then
+ possible = default
+ default = ""
+ end
+
+ table.insert(settings, {
+ name = name,
+ readable_name = readable_name,
+ type = "flags",
+ default = default,
+ possible = possible,
+ comment = current_comment,
+ })
+ return
+ end
+
+ return "Invalid setting type \"" .. setting_type .. "\""
+end
+
+local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure)
+ -- store this helper variable in the table so it's easier to pass to parse_setting_line()
+ result.current_comment = ""
+
+ local line = file:read("*line")
+ while line do
+ local error_msg = parse_setting_line(result, line, read_all, base_level, allow_secure)
+ if error_msg then
+ core.log("error", error_msg .. " in " .. filepath .. " \"" .. line .. "\"")
+ end
+ line = file:read("*line")
+ end
+
+ result.current_comment = nil
+end
+
+-- read_all: whether to ignore certain setting types for GUI or not
+-- parse_mods: whether to parse settingtypes.txt in mods and games
+local function parse_config_file(read_all, parse_mods)
+ local builtin_path = core.get_builtin_path() .. DIR_DELIM .. FILENAME
+ local file = io.open(builtin_path, "r")
+ local settings = {}
+ if not file then
+ core.log("error", "Can't load " .. FILENAME)
+ return settings
+ end
+
+ parse_single_file(file, builtin_path, read_all, settings, 0, true)
+
+ file:close()
+
+ if parse_mods then
+ -- Parse games
+ local games_category_initialized = false
+ local index = 1
+ local game = gamemgr.get_game(index)
+ while game do
+ local path = game.path .. DIR_DELIM .. FILENAME
+ local file = io.open(path, "r")
+ if file then
+ if not games_category_initialized then
+ local translation = fgettext_ne("Games"), -- not used, but needed for xgettext
+ table.insert(settings, {
+ name = "Games",
+ level = 0,
+ type = "category",
+ })
+ games_category_initialized = true
+ end
+
+ table.insert(settings, {
+ name = game.name,
+ level = 1,
+ type = "category",
+ })
+
+ parse_single_file(file, path, read_all, settings, 2, false)
+
+ file:close()
+ end
+
+ index = index + 1
+ game = gamemgr.get_game(index)
+ end
+
+ -- Parse mods
+ local mods_category_initialized = false
+ local mods = {}
+ get_mods(core.get_modpath(), mods)
+ for _, mod in ipairs(mods) do
+ local path = mod.path .. DIR_DELIM .. FILENAME
+ local file = io.open(path, "r")
+ if file then
+ if not mods_category_initialized then
+ local translation = fgettext_ne("Mods"), -- not used, but needed for xgettext
+ table.insert(settings, {
+ name = "Mods",
+ level = 0,
+ type = "category",
+ })
+ mods_category_initialized = true
+ end
+
+ table.insert(settings, {
+ name = mod.name,
+ level = 1,
+ type = "category",
+ })
+
+ parse_single_file(file, path, read_all, settings, 2, false)
+
+ file:close()
+ end
+ end
+ end
+
+ return settings
+end
+
+local function filter_settings(settings, searchstring)
+ if not searchstring or searchstring == "" then
+ return settings, -1
+ end
+
+ -- Setup the keyword list
+ local keywords = {}
+ for word in searchstring:lower():gmatch("%S+") do
+ table.insert(keywords, word)
+ end
+
+ local result = {}
+ local category_stack = {}
+ local current_level = 0
+ local best_setting = nil
+ for _, entry in pairs(settings) do
+ if entry.type == "category" then
+ -- Remove all settingless categories
+ while #category_stack > 0 and entry.level <= current_level do
+ table.remove(category_stack, #category_stack)
+ if #category_stack > 0 then
+ current_level = category_stack[#category_stack].level
+ else
+ current_level = 0
+ end
+ end
+
+ -- Push category onto stack
+ category_stack[#category_stack + 1] = entry
+ current_level = entry.level
+ else
+ -- See if setting matches keywords
+ local setting_score = 0
+ for k = 1, #keywords do
+ local keyword = keywords[k]
+
+ if string.find(entry.name:lower(), keyword, 1, true) then
+ setting_score = setting_score + 1
+ end
+
+ if entry.readable_name and
+ string.find(fgettext(entry.readable_name):lower(), keyword, 1, true) then
+ setting_score = setting_score + 1
+ end
+
+ if entry.comment and
+ string.find(fgettext_ne(entry.comment):lower(), keyword, 1, true) then
+ setting_score = setting_score + 1
+ end
+ end
+
+ -- Add setting to results if match
+ if setting_score > 0 then
+ -- Add parent categories
+ for _, category in pairs(category_stack) do
+ result[#result + 1] = category
+ end
+ category_stack = {}
+
+ -- Add setting
+ result[#result + 1] = entry
+ entry.score = setting_score
+
+ if not best_setting or
+ setting_score > result[best_setting].score then
+ best_setting = #result
+ end
+ end
+ end
+ end
+ return result, best_setting or -1
+end
+
+local full_settings = parse_config_file(false, true)
+local search_string = ""
+local settings = full_settings
+local selected_setting = 1
+
+local function get_current_value(setting)
+ local value = core.settings:get(setting.name)
+ if value == nil then
+ value = setting.default
+ end
+ return value
+end
+
+local function create_change_setting_formspec(dialogdata)
+ local setting = settings[selected_setting]
+ local formspec = "size[10,5.2,true]" ..
+ "button[5,4.5;2,1;btn_done;" .. fgettext("Save") .. "]" ..
+ "button[3,4.5;2,1;btn_cancel;" .. fgettext("Cancel") .. "]" ..
+ "tablecolumns[color;text]" ..
+ "tableoptions[background=#00000000;highlight=#00000000;border=false]" ..
+ "table[0,0;10,3;info;"
+
+ if setting.readable_name then
+ formspec = formspec .. "#FFFF00," .. fgettext(setting.readable_name)
+ .. " (" .. core.formspec_escape(setting.name) .. "),"
+ else
+ formspec = formspec .. "#FFFF00," .. core.formspec_escape(setting.name) .. ","
+ end
+
+ formspec = formspec .. ",,"
+
+ local comment_text = ""
+
+ if setting.comment == "" then
+ comment_text = fgettext_ne("(No description of setting given)")
+ else
+ comment_text = fgettext_ne(setting.comment)
+ end
+ for _, comment_line in ipairs(comment_text:split("\n", true)) do
+ formspec = formspec .. "," .. core.formspec_escape(comment_line) .. ","
+ end
+
+ if setting.type == "flags" then
+ formspec = formspec .. ",,"
+ .. "," .. fgettext("Please enter a comma seperated list of flags.") .. ","
+ .. "," .. fgettext("Possible values are: ")
+ .. core.formspec_escape(setting.possible:gsub(",", ", ")) .. ","
+ elseif setting.type == "noise_params" then
+ formspec = formspec .. ",,"
+ .. "," .. fgettext("Format: <offset>, <scale>, (<spreadX>, <spreadY>, <spreadZ>), <seed>, <octaves>, <persistence>") .. ","
+ .. "," .. fgettext("Optionally the lacunarity can be appended with a leading comma.") .. ","
+ elseif setting.type == "v3f" then
+ formspec = formspec .. ",,"
+ .. "," .. fgettext_ne("Format is 3 numbers separated by commas and inside brackets.") .. ","
+ end
+
+ formspec = formspec:sub(1, -2) -- remove trailing comma
+
+ formspec = formspec .. ";1]"
+
+ if setting.type == "bool" then
+ local selected_index
+ if core.is_yes(get_current_value(setting)) then
+ selected_index = 2
+ else
+ selected_index = 1
+ end
+ formspec = formspec .. "dropdown[0.5,3.5;3,1;dd_setting_value;"
+ .. fgettext("Disabled") .. "," .. fgettext("Enabled") .. ";"
+ .. selected_index .. "]"
+
+ elseif setting.type == "enum" then
+ local selected_index = 0
+ formspec = formspec .. "dropdown[0.5,3.5;3,1;dd_setting_value;"
+ for index, value in ipairs(setting.values) do
+ -- translating value is not possible, since it's the value
+ -- that we set the setting to
+ formspec = formspec .. core.formspec_escape(value) .. ","
+ if get_current_value(setting) == value then
+ selected_index = index
+ end
+ end
+ if #setting.values > 0 then
+ formspec = formspec:sub(1, -2) -- remove trailing comma
+ end
+ formspec = formspec .. ";" .. selected_index .. "]"
+
+ elseif setting.type == "path" then
+ local current_value = dialogdata.selected_path
+ if not current_value then
+ current_value = get_current_value(setting)
+ end
+ formspec = formspec .. "field[0.5,4;7.5,1;te_setting_value;;"
+ .. core.formspec_escape(current_value) .. "]"
+ .. "button[8,3.75;2,1;btn_browser_path;" .. fgettext("Browse") .. "]"
+
+ else
+ -- TODO: fancy input for float, int, flags, noise_params, v3f
+ local width = 10
+ local text = get_current_value(setting)
+ if dialogdata.error_message then
+ formspec = formspec .. "tablecolumns[color;text]" ..
+ "tableoptions[background=#00000000;highlight=#00000000;border=false]" ..
+ "table[5,3.9;5,0.6;error_message;#FF0000,"
+ .. core.formspec_escape(dialogdata.error_message) .. ";0]"
+ width = 5
+ if dialogdata.entered_text then
+ text = dialogdata.entered_text
+ end
+ end
+ formspec = formspec .. "field[0.5,4;" .. width .. ",1;te_setting_value;;"
+ .. core.formspec_escape(text) .. "]"
+ end
+ return formspec
+end
+
+local function handle_change_setting_buttons(this, fields)
+ if fields["btn_done"] or fields["key_enter"] then
+ local setting = settings[selected_setting]
+ if setting.type == "bool" then
+ local new_value = fields["dd_setting_value"]
+ -- Note: new_value is the actual (translated) value shown in the dropdown
+ core.settings:set_bool(setting.name, new_value == fgettext("Enabled"))
+
+ elseif setting.type == "enum" then
+ local new_value = fields["dd_setting_value"]
+ core.settings:set(setting.name, new_value)
+
+ elseif setting.type == "int" then
+ local new_value = tonumber(fields["te_setting_value"])
+ if not new_value or math.floor(new_value) ~= new_value then
+ this.data.error_message = fgettext_ne("Please enter a valid integer.")
+ this.data.entered_text = fields["te_setting_value"]
+ core.update_formspec(this:get_formspec())
+ return true
+ end
+ if setting.min and new_value < setting.min then
+ this.data.error_message = fgettext_ne("The value must be at least $1.", setting.min)
+ this.data.entered_text = fields["te_setting_value"]
+ core.update_formspec(this:get_formspec())
+ return true
+ end
+ if setting.max and new_value > setting.max then
+ this.data.error_message = fgettext_ne("The value must not be larger than $1.", setting.max)
+ this.data.entered_text = fields["te_setting_value"]
+ core.update_formspec(this:get_formspec())
+ return true
+ end
+ core.settings:set(setting.name, new_value)
+
+ elseif setting.type == "float" then
+ local new_value = tonumber(fields["te_setting_value"])
+ if not new_value then
+ this.data.error_message = fgettext_ne("Please enter a valid number.")
+ this.data.entered_text = fields["te_setting_value"]
+ core.update_formspec(this:get_formspec())
+ return true
+ end
+ core.settings:set(setting.name, new_value)
+
+ elseif setting.type == "flags" then
+ local new_value = fields["te_setting_value"]
+ for _,value in ipairs(new_value:split(",", true)) do
+ value = value:trim()
+ local possible = "," .. setting.possible .. ","
+ if not possible:find("," .. value .. ",", 0, true) then
+ this.data.error_message = fgettext_ne("\"$1\" is not a valid flag.", value)
+ this.data.entered_text = fields["te_setting_value"]
+ core.update_formspec(this:get_formspec())
+ return true
+ end
+ end
+ core.settings:set(setting.name, new_value)
+
+ else
+ local new_value = fields["te_setting_value"]
+ core.settings:set(setting.name, new_value)
+ end
+ core.settings:write()
+ this:delete()
+ return true
+ end
+
+ if fields["btn_cancel"] then
+ this:delete()
+ return true
+ end
+
+ if fields["btn_browser_path"] then
+ core.show_file_open_dialog("dlg_browse_path", fgettext_ne("Select path"))
+ end
+
+ if fields["dlg_browse_path_accepted"] then
+ this.data.selected_path = fields["dlg_browse_path_accepted"]
+ core.update_formspec(this:get_formspec())
+ end
+
+ return false
+end
+
+local function create_settings_formspec(tabview, name, tabdata)
+ local formspec = "size[12,6.5;true]" ..
+ "tablecolumns[color;tree;text,width=32;text]" ..
+ "tableoptions[background=#00000000;border=false]" ..
+ "field[0.3,0.1;10.2,1;search_string;;" .. core.formspec_escape(search_string) .. "]" ..
+ "field_close_on_enter[search_string;false]" ..
+ "button[10.2,-0.2;2,1;search;" .. fgettext("Search") .. "]" ..
+ "table[0,0.8;12,4.5;list_settings;"
+
+ local current_level = 0
+ for _, entry in ipairs(settings) do
+ local name
+ if not core.settings:get_bool("main_menu_technical_settings") and entry.readable_name then
+ name = fgettext_ne(entry.readable_name)
+ else
+ name = entry.name
+ end
+
+ if entry.type == "category" then
+ current_level = entry.level
+ formspec = formspec .. "#FFFF00," .. current_level .. "," .. fgettext(name) .. ",,"
+
+ elseif entry.type == "bool" then
+ local value = get_current_value(entry)
+ if core.is_yes(value) then
+ value = fgettext("Enabled")
+ else
+ value = fgettext("Disabled")
+ end
+ formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
+ .. value .. ","
+
+ elseif entry.type == "key" then
+ -- ignore key settings, since we have a special dialog for them
+
+ else
+ formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. ","
+ .. core.formspec_escape(get_current_value(entry)) .. ","
+ end
+ end
+
+ if #settings > 0 then
+ formspec = formspec:sub(1, -2) -- remove trailing comma
+ end
+ formspec = formspec .. ";" .. selected_setting .. "]" ..
+ "button[0,6;4,1;btn_back;".. fgettext("< Back to Settings page") .. "]" ..
+ "button[10,6;2,1;btn_edit;" .. fgettext("Edit") .. "]" ..
+ "button[7,6;3,1;btn_restore;" .. fgettext("Restore Default") .. "]" ..
+ "checkbox[0,5.3;cb_tech_settings;" .. fgettext("Show technical names") .. ";"
+ .. dump(core.settings:get_bool("main_menu_technical_settings")) .. "]"
+
+ return formspec
+end
+
+local function handle_settings_buttons(this, fields, tabname, tabdata)
+ local list_enter = false
+ if fields["list_settings"] then
+ selected_setting = core.get_table_index("list_settings")
+ if core.explode_table_event(fields["list_settings"]).type == "DCL" then
+ -- Directly toggle booleans
+ local setting = settings[selected_setting]
+ if setting and setting.type == "bool" then
+ local current_value = get_current_value(setting)
+ core.settings:set_bool(setting.name, not core.is_yes(current_value))
+ core.settings:write()
+ return true
+ else
+ list_enter = true
+ end
+ else
+ return true
+ end
+ end
+
+ if fields.search or fields.key_enter_field == "search_string" then
+ if search_string == fields.search_string then
+ if selected_setting > 0 then
+ -- Go to next result on enter press
+ local i = selected_setting + 1
+ local looped = false
+ while i > #settings or settings[i].type == "category" do
+ i = i + 1
+ if i > #settings then
+ -- Stop infinte looping
+ if looped then
+ return false
+ end
+ i = 1
+ looped = true
+ end
+ end
+ selected_setting = i
+ core.update_formspec(this:get_formspec())
+ return true
+ end
+ else
+ -- Search for setting
+ search_string = fields.search_string
+ settings, selected_setting = filter_settings(full_settings, search_string)
+ core.update_formspec(this:get_formspec())
+ end
+ return true
+ end
+
+ if fields["btn_edit"] or list_enter then
+ local setting = settings[selected_setting]
+ if setting and setting.type ~= "category" then
+ local edit_dialog = dialog_create("change_setting", create_change_setting_formspec,
+ handle_change_setting_buttons)
+ edit_dialog:set_parent(this)
+ this:hide()
+ edit_dialog:show()
+ end
+ return true
+ end
+
+ if fields["btn_restore"] then
+ local setting = settings[selected_setting]
+ if setting and setting.type ~= "category" then
+ core.settings:set(setting.name, setting.default)
+ core.settings:write()
+ core.update_formspec(this:get_formspec())
+ end
+ return true
+ end
+
+ if fields["btn_back"] then
+ this:delete()
+ return true
+ end
+
+ if fields["cb_tech_settings"] then
+ core.settings:set("main_menu_technical_settings", fields["cb_tech_settings"])
+ core.settings:write()
+ core.update_formspec(this:get_formspec())
+ return true
+ end
+
+ return false
+end
+
+function create_adv_settings_dlg()
+ local dlg = dialog_create("settings_advanced",
+ create_settings_formspec,
+ handle_settings_buttons,
+ nil)
+
+ return dlg
+end
+
+-- Generate minetest.conf.example and settings_translation_file.cpp
+
+--assert(loadfile(core.get_builtin_path()..DIR_DELIM.."mainmenu"..DIR_DELIM.."generate_from_settingtypes.lua"))(parse_config_file(true, false))
diff --git a/builtin/mainmenu/gamemgr.lua b/builtin/mainmenu/gamemgr.lua
new file mode 100644
index 000000000..fd6025fcd
--- /dev/null
+++ b/builtin/mainmenu/gamemgr.lua
@@ -0,0 +1,83 @@
+--Minetest
+--Copyright (C) 2013 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+gamemgr = {}
+
+--------------------------------------------------------------------------------
+function gamemgr.find_by_gameid(gameid)
+ for i=1,#gamemgr.games,1 do
+ if gamemgr.games[i].id == gameid then
+ return gamemgr.games[i], i
+ end
+ end
+ return nil, nil
+end
+
+--------------------------------------------------------------------------------
+function gamemgr.get_game_mods(gamespec, retval)
+ if gamespec ~= nil and
+ gamespec.gamemods_path ~= nil and
+ gamespec.gamemods_path ~= "" then
+ get_mods(gamespec.gamemods_path, retval)
+ end
+end
+
+--------------------------------------------------------------------------------
+function gamemgr.get_game_modlist(gamespec)
+ local retval = ""
+ local game_mods = {}
+ gamemgr.get_game_mods(gamespec, game_mods)
+ for i=1,#game_mods,1 do
+ if retval ~= "" then
+ retval = retval..","
+ end
+ retval = retval .. game_mods[i].name
+ end
+ return retval
+end
+
+--------------------------------------------------------------------------------
+function gamemgr.get_game(index)
+ if index > 0 and index <= #gamemgr.games then
+ return gamemgr.games[index]
+ end
+
+ return nil
+end
+
+--------------------------------------------------------------------------------
+function gamemgr.update_gamelist()
+ gamemgr.games = core.get_games()
+end
+
+--------------------------------------------------------------------------------
+function gamemgr.gamelist()
+ local retval = ""
+ if #gamemgr.games > 0 then
+ retval = retval .. core.formspec_escape(gamemgr.games[1].name)
+
+ for i=2,#gamemgr.games,1 do
+ retval = retval .. "," .. core.formspec_escape(gamemgr.games[i].name)
+ end
+ end
+ return retval
+end
+
+--------------------------------------------------------------------------------
+-- read initial data
+--------------------------------------------------------------------------------
+gamemgr.update_gamelist()
diff --git a/builtin/mainmenu/generate_from_settingtypes.lua b/builtin/mainmenu/generate_from_settingtypes.lua
new file mode 100644
index 000000000..6c9ba27fb
--- /dev/null
+++ b/builtin/mainmenu/generate_from_settingtypes.lua
@@ -0,0 +1,99 @@
+local settings = ...
+
+local concat = table.concat
+local insert = table.insert
+local sprintf = string.format
+local rep = string.rep
+
+local minetest_example_header = [[
+# This file contains a list of all available settings and their default value for minetest.conf
+
+# By default, all the settings are commented and not functional.
+# Uncomment settings by removing the preceding #.
+
+# minetest.conf is read by default from:
+# ../minetest.conf
+# ../../minetest.conf
+# Any other path can be chosen by passing the path as a parameter
+# to the program, eg. "minetest.exe --config ../minetest.conf.example".
+
+# Further documentation:
+# http://wiki.minetest.net/
+
+]]
+
+local function create_minetest_conf_example()
+ local result = { minetest_example_header }
+ for _, entry in ipairs(settings) do
+ if entry.type == "category" then
+ if entry.level == 0 then
+ insert(result, "#\n# " .. entry.name .. "\n#\n\n")
+ else
+ insert(result, rep("#", entry.level))
+ insert(result, "# " .. entry.name .. "\n\n")
+ end
+ else
+ if entry.comment ~= "" then
+ for _, comment_line in ipairs(entry.comment:split("\n", true)) do
+ insert(result, "# " .. comment_line .. "\n")
+ end
+ end
+ insert(result, "# type: " .. entry.type)
+ if entry.min then
+ insert(result, " min: " .. entry.min)
+ end
+ if entry.max then
+ insert(result, " max: " .. entry.max)
+ end
+ if entry.values then
+ insert(result, " values: " .. concat(entry.values, ", "))
+ end
+ if entry.possible then
+ insert(result, " possible values: " .. entry.possible:gsub(",", ", "))
+ end
+ insert(result, "\n")
+ local append
+ if entry.default ~= "" then
+ append = " " .. entry.default
+ end
+ insert(result, sprintf("# %s =%s\n\n", entry.name, append or ""))
+ end
+ end
+ return concat(result)
+end
+
+local translation_file_header = [[
+// This file is automatically generated
+// It conatins a bunch of fake gettext calls, to tell xgettext about the strings in config files
+// To update it, refer to the bottom of builtin/mainmenu/dlg_settings_advanced.lua
+
+fake_function() {]]
+
+local function create_translation_file()
+ local result = { translation_file_header }
+ for _, entry in ipairs(settings) do
+ if entry.type == "category" then
+ insert(result, sprintf("\tgettext(%q);", entry.name))
+ else
+ if entry.readable_name then
+ insert(result, sprintf("\tgettext(%q);", entry.readable_name))
+ end
+ if entry.comment ~= "" then
+ local comment_escaped = entry.comment:gsub("\n", "\\n")
+ comment_escaped = comment_escaped:gsub("\"", "\\\"")
+ insert(result, "\tgettext(\"" .. comment_escaped .. "\");")
+ end
+ end
+ end
+ insert(result, "}\n")
+ return concat(result, "\n")
+end
+
+local file = assert(io.open("minetest.conf.example", "w"))
+file:write(create_minetest_conf_example())
+file:close()
+
+file = assert(io.open("src/settings_translation_file.cpp", "w"))
+file:write(create_translation_file())
+file:close()
+
diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua
new file mode 100644
index 000000000..7c6af7d27
--- /dev/null
+++ b/builtin/mainmenu/init.lua
@@ -0,0 +1,167 @@
+--Minetest
+--Copyright (C) 2014 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+mt_color_grey = "#AAAAAA"
+mt_color_blue = "#6389FF"
+mt_color_green = "#72FF63"
+mt_color_dark_green = "#25C191"
+
+--for all other colors ask sfan5 to complete his work!
+
+local menupath = core.get_mainmenu_path()
+local basepath = core.get_builtin_path()
+defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" ..
+ DIR_DELIM .. "pack" .. DIR_DELIM
+
+dofile(basepath .. DIR_DELIM .. "common" .. DIR_DELIM .. "async_event.lua")
+dofile(basepath .. DIR_DELIM .. "common" .. DIR_DELIM .. "filterlist.lua")
+dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "buttonbar.lua")
+dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "dialog.lua")
+dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "tabview.lua")
+dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "ui.lua")
+dofile(menupath .. DIR_DELIM .. "common.lua")
+dofile(menupath .. DIR_DELIM .. "gamemgr.lua")
+dofile(menupath .. DIR_DELIM .. "modmgr.lua")
+dofile(menupath .. DIR_DELIM .. "store.lua")
+dofile(menupath .. DIR_DELIM .. "textures.lua")
+
+dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua")
+dofile(menupath .. DIR_DELIM .. "dlg_settings_advanced.lua")
+if PLATFORM ~= "Android" then
+ dofile(menupath .. DIR_DELIM .. "dlg_create_world.lua")
+ dofile(menupath .. DIR_DELIM .. "dlg_delete_mod.lua")
+ dofile(menupath .. DIR_DELIM .. "dlg_delete_world.lua")
+ dofile(menupath .. DIR_DELIM .. "dlg_rename_modpack.lua")
+end
+
+local tabs = {}
+
+tabs.settings = dofile(menupath .. DIR_DELIM .. "tab_settings.lua")
+tabs.mods = dofile(menupath .. DIR_DELIM .. "tab_mods.lua")
+tabs.credits = dofile(menupath .. DIR_DELIM .. "tab_credits.lua")
+if PLATFORM == "Android" then
+ tabs.simple_main = dofile(menupath .. DIR_DELIM .. "tab_simple_main.lua")
+else
+ tabs.local_game = dofile(menupath .. DIR_DELIM .. "tab_local.lua")
+ tabs.play_online = dofile(menupath .. DIR_DELIM .. "tab_online.lua")
+ tabs.texturepacks = dofile(menupath .. DIR_DELIM .. "tab_texturepacks.lua")
+end
+
+--------------------------------------------------------------------------------
+local function main_event_handler(tabview, event)
+ if event == "MenuQuit" then
+ core.close()
+ end
+ return true
+end
+
+--------------------------------------------------------------------------------
+local function init_globals()
+ -- Init gamedata
+ gamedata.worldindex = 0
+
+ if PLATFORM == "Android" then
+ local world_list = core.get_worlds()
+ local world_index
+
+ local found_singleplayerworld = false
+ for i, world in ipairs(world_list) do
+ if world.name == "singleplayerworld" then
+ found_singleplayerworld = true
+ world_index = i
+ break
+ end
+ end
+
+ if not found_singleplayerworld then
+ core.create_world("singleplayerworld", 1)
+
+ world_list = core.get_worlds()
+
+ for i, world in ipairs(world_list) do
+ if world.name == "singleplayerworld" then
+ world_index = i
+ break
+ end
+ end
+ end
+
+ gamedata.worldindex = world_index
+ else
+ menudata.worldlist = filterlist.create(
+ core.get_worlds,
+ compare_worlds,
+ -- Unique id comparison function
+ function(element, uid)
+ return element.name == uid
+ end,
+ -- Filter function
+ function(element, gameid)
+ return element.gameid == gameid
+ end
+ )
+
+ menudata.worldlist:add_sort_mechanism("alphabetic", sort_worlds_alphabetic)
+ menudata.worldlist:set_sortmode("alphabetic")
+
+ if not core.settings:get("menu_last_game") then
+ local default_game = core.settings:get("default_game") or "minetest"
+ core.settings:set("menu_last_game", default_game)
+ end
+
+ mm_texture.init()
+ end
+
+ -- Create main tabview
+ local tv_main = tabview_create("maintab", {x = 12, y = 5.4}, {x = 0, y = 0})
+
+ if PLATFORM == "Android" then
+ tv_main:add(tabs.simple_main)
+ tv_main:add(tabs.settings)
+ else
+ tv_main:set_autosave_tab(true)
+ tv_main:add(tabs.local_game)
+ tv_main:add(tabs.play_online)
+ tv_main:add(tabs.settings)
+ tv_main:add(tabs.texturepacks)
+ end
+
+ tv_main:add(tabs.mods)
+ tv_main:add(tabs.credits)
+
+ tv_main:set_global_event_handler(main_event_handler)
+ tv_main:set_fixed_size(false)
+
+ if PLATFORM ~= "Android" then
+ tv_main:set_tab(core.settings:get("maintab_LAST"))
+ end
+ ui.set_default("maintab")
+ tv_main:show()
+
+ -- Create modstore ui
+ if PLATFORM == "Android" then
+ modstore.init({x = 12, y = 6}, 3, 2)
+ else
+ modstore.init({x = 12, y = 8}, 4, 3)
+ end
+
+ ui.update()
+
+ core.sound_play("main_menu", true)
+end
+
+init_globals()
diff --git a/builtin/mainmenu/init_simple.lua b/builtin/mainmenu/init_simple.lua
new file mode 100644
index 000000000..298bd834e
--- /dev/null
+++ b/builtin/mainmenu/init_simple.lua
@@ -0,0 +1,4 @@
+-- helper file to be able to debug the simple menu on PC
+-- without messing around with actual menu code!
+PLATFORM = "Android"
+dofile("builtin/mainmenu/init.lua")
diff --git a/builtin/mainmenu/modmgr.lua b/builtin/mainmenu/modmgr.lua
new file mode 100644
index 000000000..dee048982
--- /dev/null
+++ b/builtin/mainmenu/modmgr.lua
@@ -0,0 +1,570 @@
+--Minetest
+--Copyright (C) 2013 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+--------------------------------------------------------------------------------
+function get_mods(path,retval,modpack)
+ local mods = core.get_dir_list(path, true)
+
+ for _, name in ipairs(mods) do
+ if name:sub(1, 1) ~= "." then
+ local prefix = path .. DIR_DELIM .. name .. DIR_DELIM
+ local toadd = {}
+ retval[#retval + 1] = toadd
+
+ local mod_conf = Settings(prefix .. "mod.conf"):to_table()
+ if mod_conf.name then
+ name = mod_conf.name
+ end
+
+ toadd.name = name
+ toadd.path = prefix
+
+ if modpack ~= nil and modpack ~= "" then
+ toadd.modpack = modpack
+ else
+ local modpackfile = io.open(prefix .. "modpack.txt")
+ if modpackfile then
+ modpackfile:close()
+ toadd.is_modpack = true
+ get_mods(prefix, retval, name)
+ end
+ end
+ end
+ end
+end
+
+--modmanager implementation
+modmgr = {}
+
+--------------------------------------------------------------------------------
+function modmgr.extract(modfile)
+ if modfile.type == "zip" then
+ local tempfolder = os.tempfolder()
+
+ if tempfolder ~= nil and
+ tempfolder ~= "" then
+ core.create_dir(tempfolder)
+ if core.extract_zip(modfile.name,tempfolder) then
+ return tempfolder
+ end
+ end
+ end
+ return nil
+end
+
+-------------------------------------------------------------------------------
+function modmgr.getbasefolder(temppath)
+
+ if temppath == nil then
+ return {
+ type = "invalid",
+ path = ""
+ }
+ end
+
+ local testfile = io.open(temppath .. DIR_DELIM .. "init.lua","r")
+ if testfile ~= nil then
+ testfile:close()
+ return {
+ type="mod",
+ path=temppath
+ }
+ end
+
+ testfile = io.open(temppath .. DIR_DELIM .. "modpack.txt","r")
+ if testfile ~= nil then
+ testfile:close()
+ return {
+ type="modpack",
+ path=temppath
+ }
+ end
+
+ local subdirs = core.get_dir_list(temppath, true)
+
+ --only single mod or modpack allowed
+ if #subdirs ~= 1 then
+ return {
+ type = "invalid",
+ path = ""
+ }
+ end
+
+ testfile =
+ io.open(temppath .. DIR_DELIM .. subdirs[1] ..DIR_DELIM .."init.lua","r")
+ if testfile ~= nil then
+ testfile:close()
+ return {
+ type="mod",
+ path= temppath .. DIR_DELIM .. subdirs[1]
+ }
+ end
+
+ testfile =
+ io.open(temppath .. DIR_DELIM .. subdirs[1] ..DIR_DELIM .."modpack.txt","r")
+ if testfile ~= nil then
+ testfile:close()
+ return {
+ type="modpack",
+ path=temppath .. DIR_DELIM .. subdirs[1]
+ }
+ end
+
+ return {
+ type = "invalid",
+ path = ""
+ }
+end
+
+--------------------------------------------------------------------------------
+function modmgr.isValidModname(modpath)
+ if modpath:find("-") ~= nil then
+ return false
+ end
+
+ return true
+end
+
+--------------------------------------------------------------------------------
+function modmgr.parse_register_line(line)
+ local pos1 = line:find("\"")
+ local pos2 = nil
+ if pos1 ~= nil then
+ pos2 = line:find("\"",pos1+1)
+ end
+
+ if pos1 ~= nil and pos2 ~= nil then
+ local item = line:sub(pos1+1,pos2-1)
+
+ if item ~= nil and
+ item ~= "" then
+ local pos3 = item:find(":")
+
+ if pos3 ~= nil then
+ local retval = item:sub(1,pos3-1)
+ if retval ~= nil and
+ retval ~= "" then
+ return retval
+ end
+ end
+ end
+ end
+ return nil
+end
+
+--------------------------------------------------------------------------------
+function modmgr.parse_dofile_line(modpath,line)
+ local pos1 = line:find("\"")
+ local pos2 = nil
+ if pos1 ~= nil then
+ pos2 = line:find("\"",pos1+1)
+ end
+
+ if pos1 ~= nil and pos2 ~= nil then
+ local filename = line:sub(pos1+1,pos2-1)
+
+ if filename ~= nil and
+ filename ~= "" and
+ filename:find(".lua") then
+ return modmgr.identify_modname(modpath,filename)
+ end
+ end
+ return nil
+end
+
+--------------------------------------------------------------------------------
+function modmgr.identify_modname(modpath,filename)
+ local testfile = io.open(modpath .. DIR_DELIM .. filename,"r")
+ if testfile ~= nil then
+ local line = testfile:read()
+
+ while line~= nil do
+ local modname = nil
+
+ if line:find("minetest.register_tool") then
+ modname = modmgr.parse_register_line(line)
+ end
+
+ if line:find("minetest.register_craftitem") then
+ modname = modmgr.parse_register_line(line)
+ end
+
+
+ if line:find("minetest.register_node") then
+ modname = modmgr.parse_register_line(line)
+ end
+
+ if line:find("dofile") then
+ modname = modmgr.parse_dofile_line(modpath,line)
+ end
+
+ if modname ~= nil then
+ testfile:close()
+ return modname
+ end
+
+ line = testfile:read()
+ end
+ testfile:close()
+ end
+
+ return nil
+end
+--------------------------------------------------------------------------------
+function modmgr.render_modlist(render_list)
+ local retval = ""
+
+ if render_list == nil then
+ if modmgr.global_mods == nil then
+ modmgr.refresh_globals()
+ end
+ render_list = modmgr.global_mods
+ end
+
+ local list = render_list:get_list()
+ local last_modpack = nil
+ local retval = {}
+ for i, v in ipairs(list) do
+ local color = ""
+ if v.is_modpack then
+ local rawlist = render_list:get_raw_list()
+ color = mt_color_dark_green
+
+ for j = 1, #rawlist, 1 do
+ if rawlist[j].modpack == list[i].name and
+ rawlist[j].enabled ~= true then
+ -- Modpack not entirely enabled so showing as grey
+ color = mt_color_grey
+ break
+ end
+ end
+ elseif v.is_game_content then
+ color = mt_color_blue
+ elseif v.enabled then
+ color = mt_color_green
+ end
+
+ retval[#retval + 1] = color
+ if v.modpack ~= nil or v.typ == "game_mod" then
+ retval[#retval + 1] = "1"
+ else
+ retval[#retval + 1] = "0"
+ end
+ retval[#retval + 1] = core.formspec_escape(v.name)
+ end
+
+ return table.concat(retval, ",")
+end
+
+--------------------------------------------------------------------------------
+function modmgr.get_dependencies(modfolder)
+ local toadd_hard = ""
+ local toadd_soft = ""
+ if modfolder ~= nil then
+ local filename = modfolder ..
+ DIR_DELIM .. "depends.txt"
+
+ local hard_dependencies = {}
+ local soft_dependencies = {}
+ local dependencyfile = io.open(filename,"r")
+ if dependencyfile then
+ local dependency = dependencyfile:read("*l")
+ while dependency do
+ dependency = dependency:gsub("\r", "")
+ if string.sub(dependency, -1, -1) == "?" then
+ table.insert(soft_dependencies, string.sub(dependency, 1, -2))
+ else
+ table.insert(hard_dependencies, dependency)
+ end
+ dependency = dependencyfile:read()
+ end
+ dependencyfile:close()
+ end
+ toadd_hard = table.concat(hard_dependencies, ",")
+ toadd_soft = table.concat(soft_dependencies, ",")
+ end
+
+ return toadd_hard, toadd_soft
+end
+
+--------------------------------------------------------------------------------
+function modmgr.get_worldconfig(worldpath)
+ local filename = worldpath ..
+ DIR_DELIM .. "world.mt"
+
+ local worldfile = Settings(filename)
+
+ local worldconfig = {}
+ worldconfig.global_mods = {}
+ worldconfig.game_mods = {}
+
+ for key,value in pairs(worldfile:to_table()) do
+ if key == "gameid" then
+ worldconfig.id = value
+ elseif key:sub(0, 9) == "load_mod_" then
+ worldconfig.global_mods[key] = core.is_yes(value)
+ else
+ worldconfig[key] = value
+ end
+ end
+
+ --read gamemods
+ local gamespec = gamemgr.find_by_gameid(worldconfig.id)
+ gamemgr.get_game_mods(gamespec, worldconfig.game_mods)
+
+ return worldconfig
+end
+
+--------------------------------------------------------------------------------
+function modmgr.installmod(modfilename,basename)
+ local modfile = modmgr.identify_filetype(modfilename)
+ local modpath = modmgr.extract(modfile)
+
+ if modpath == nil then
+ gamedata.errormessage = fgettext("Install Mod: file: \"$1\"", modfile.name) ..
+ fgettext("\nInstall Mod: unsupported filetype \"$1\" or broken archive", modfile.type)
+ return
+ end
+
+ local basefolder = modmgr.getbasefolder(modpath)
+
+ if basefolder.type == "modpack" then
+ local clean_path = nil
+
+ if basename ~= nil then
+ clean_path = "mp_" .. basename
+ end
+
+ if clean_path == nil then
+ clean_path = get_last_folder(cleanup_path(basefolder.path))
+ end
+
+ if clean_path ~= nil then
+ local targetpath = core.get_modpath() .. DIR_DELIM .. clean_path
+ if not core.copy_dir(basefolder.path,targetpath) then
+ gamedata.errormessage = fgettext("Failed to install $1 to $2", basename, targetpath)
+ end
+ else
+ gamedata.errormessage = fgettext("Install Mod: unable to find suitable foldername for modpack $1", modfilename)
+ end
+ end
+
+ if basefolder.type == "mod" then
+ local targetfolder = basename
+
+ if targetfolder == nil then
+ targetfolder = modmgr.identify_modname(basefolder.path,"init.lua")
+ end
+
+ --if heuristic failed try to use current foldername
+ if targetfolder == nil then
+ targetfolder = get_last_folder(basefolder.path)
+ end
+
+ if targetfolder ~= nil and modmgr.isValidModname(targetfolder) then
+ local targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder
+ core.copy_dir(basefolder.path,targetpath)
+ else
+ gamedata.errormessage = fgettext("Install Mod: unable to find real modname for: $1", modfilename)
+ end
+ end
+
+ core.delete_dir(modpath)
+
+ modmgr.refresh_globals()
+
+end
+
+--------------------------------------------------------------------------------
+function modmgr.preparemodlist(data)
+ local retval = {}
+
+ local global_mods = {}
+ local game_mods = {}
+
+ --read global mods
+ local modpath = core.get_modpath()
+
+ if modpath ~= nil and
+ modpath ~= "" then
+ get_mods(modpath,global_mods)
+ end
+
+ for i=1,#global_mods,1 do
+ global_mods[i].typ = "global_mod"
+ retval[#retval + 1] = global_mods[i]
+ end
+
+ --read game mods
+ local gamespec = gamemgr.find_by_gameid(data.gameid)
+ gamemgr.get_game_mods(gamespec, game_mods)
+
+ if #game_mods > 0 then
+ -- Add title
+ retval[#retval + 1] = {
+ typ = "game",
+ is_game_content = true,
+ name = fgettext("Subgame Mods")
+ }
+ end
+
+ for i=1,#game_mods,1 do
+ game_mods[i].typ = "game_mod"
+ game_mods[i].is_game_content = true
+ retval[#retval + 1] = game_mods[i]
+ end
+
+ if data.worldpath == nil then
+ return retval
+ end
+
+ --read world mod configuration
+ local filename = data.worldpath ..
+ DIR_DELIM .. "world.mt"
+
+ local worldfile = Settings(filename)
+
+ for key,value in pairs(worldfile:to_table()) do
+ if key:sub(1, 9) == "load_mod_" then
+ key = key:sub(10)
+ local element = nil
+ for i=1,#retval,1 do
+ if retval[i].name == key and
+ not retval[i].is_modpack then
+ element = retval[i]
+ break
+ end
+ end
+ if element ~= nil then
+ element.enabled = core.is_yes(value)
+ else
+ core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
+ end
+ end
+ end
+
+ return retval
+end
+
+--------------------------------------------------------------------------------
+function modmgr.comparemod(elem1,elem2)
+ if elem1 == nil or elem2 == nil then
+ return false
+ end
+ if elem1.name ~= elem2.name then
+ return false
+ end
+ if elem1.is_modpack ~= elem2.is_modpack then
+ return false
+ end
+ if elem1.typ ~= elem2.typ then
+ return false
+ end
+ if elem1.modpack ~= elem2.modpack then
+ return false
+ end
+
+ if elem1.path ~= elem2.path then
+ return false
+ end
+
+ return true
+end
+
+--------------------------------------------------------------------------------
+function modmgr.mod_exists(basename)
+
+ if modmgr.global_mods == nil then
+ modmgr.refresh_globals()
+ end
+
+ if modmgr.global_mods:raw_index_by_uid(basename) > 0 then
+ return true
+ end
+
+ return false
+end
+
+--------------------------------------------------------------------------------
+function modmgr.get_global_mod(idx)
+
+ if modmgr.global_mods == nil then
+ return nil
+ end
+
+ if idx == nil or idx < 1 or
+ idx > modmgr.global_mods:size() then
+ return nil
+ end
+
+ return modmgr.global_mods:get_list()[idx]
+end
+
+--------------------------------------------------------------------------------
+function modmgr.refresh_globals()
+ modmgr.global_mods = filterlist.create(
+ modmgr.preparemodlist, --refresh
+ modmgr.comparemod, --compare
+ function(element,uid) --uid match
+ if element.name == uid then
+ return true
+ end
+ end,
+ nil, --filter
+ {}
+ )
+ modmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list)
+ modmgr.global_mods:set_sortmode("alphabetic")
+end
+
+--------------------------------------------------------------------------------
+function modmgr.identify_filetype(name)
+
+ if name:sub(-3):lower() == "zip" then
+ return {
+ name = name,
+ type = "zip"
+ }
+ end
+
+ if name:sub(-6):lower() == "tar.gz" or
+ name:sub(-3):lower() == "tgz"then
+ return {
+ name = name,
+ type = "tgz"
+ }
+ end
+
+ if name:sub(-6):lower() == "tar.bz2" then
+ return {
+ name = name,
+ type = "tbz"
+ }
+ end
+
+ if name:sub(-2):lower() == "7z" then
+ return {
+ name = name,
+ type = "7z"
+ }
+ end
+
+ return {
+ name = name,
+ type = "ukn"
+ }
+end
diff --git a/builtin/mainmenu/store.lua b/builtin/mainmenu/store.lua
new file mode 100644
index 000000000..59391f8bc
--- /dev/null
+++ b/builtin/mainmenu/store.lua
@@ -0,0 +1,614 @@
+--Minetest
+--Copyright (C) 2013 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+--------------------------------------------------------------------------------
+
+--modstore implementation
+modstore = {}
+
+--------------------------------------------------------------------------------
+-- @function [parent=#modstore] init
+function modstore.init(size, unsortedmods, searchmods)
+
+ modstore.mods_on_unsorted_page = unsortedmods
+ modstore.mods_on_search_page = searchmods
+ modstore.modsperpage = modstore.mods_on_unsorted_page
+
+ modstore.basetexturedir = core.get_texturepath() .. DIR_DELIM .. "base" ..
+ DIR_DELIM .. "pack" .. DIR_DELIM
+
+ modstore.lastmodtitle = ""
+ modstore.last_search = ""
+
+ modstore.searchlist = filterlist.create(
+ function()
+ if modstore.modlist_unsorted ~= nil and
+ modstore.modlist_unsorted.data ~= nil then
+ return modstore.modlist_unsorted.data
+ end
+ return {}
+ end,
+ function(element,modid)
+ if element.id == modid then
+ return true
+ end
+ return false
+ end, --compare fct
+ nil, --uid match fct
+ function(element,substring)
+ if substring == nil or
+ substring == "" then
+ return false
+ end
+ substring = substring:upper()
+
+ if element.title ~= nil and
+ element.title:upper():find(substring) ~= nil then
+ return true
+ end
+
+ if element.details ~= nil and
+ element.details.author ~= nil and
+ element.details.author:upper():find(substring) ~= nil then
+ return true
+ end
+
+ if element.details ~= nil and
+ element.details.description ~= nil and
+ element.details.description:upper():find(substring) ~= nil then
+ return true
+ end
+ return false
+ end --filter fct
+ )
+
+ modstore.current_list = nil
+
+ modstore.tv_store = tabview_create("modstore",size,{x=0,y=0})
+
+ modstore.tv_store:set_global_event_handler(modstore.handle_events)
+
+ modstore.tv_store:add(
+ {
+ name = "unsorted",
+ caption = fgettext("Unsorted"),
+ cbf_formspec = modstore.unsorted_tab,
+ cbf_button_handler = modstore.handle_buttons,
+ on_change =
+ function() modstore.modsperpage = modstore.mods_on_unsorted_page end
+ }
+ )
+
+ modstore.tv_store:add(
+ {
+ name = "search",
+ caption = fgettext("Search"),
+ cbf_formspec = modstore.getsearchpage,
+ cbf_button_handler = modstore.handle_buttons,
+ on_change = modstore.activate_search_tab
+ }
+ )
+end
+
+--------------------------------------------------------------------------------
+-- @function [parent=#modstore] nametoindex
+function modstore.nametoindex(name)
+
+ for i=1,#modstore.tabnames,1 do
+ if modstore.tabnames[i] == name then
+ return i
+ end
+ end
+
+ return 1
+end
+
+--------------------------------------------------------------------------------
+-- @function [parent=#modstore] showdownloading
+function modstore.showdownloading(title)
+ local new_dlg = dialog_create("store_downloading",
+ function(data)
+ return "size[6,2]label[0.25,0.75;" ..
+ fgettext("Downloading $1, please wait...", data.title) .. "]"
+ end,
+ function(this,fields)
+ if fields["btn_hidden_close_download"] ~= nil then
+ if fields["btn_hidden_close_download"].successfull then
+ modstore.lastmodentry = fields["btn_hidden_close_download"]
+ modstore.successfulldialog(this)
+ else
+ this.parent:show()
+ this:delete()
+ modstore.lastmodtitle = ""
+ end
+
+ return true
+ end
+
+ return false
+ end,
+ nil)
+
+ new_dlg:set_parent(modstore.tv_store)
+ modstore.tv_store:hide()
+ new_dlg.data.title = title
+ new_dlg:show()
+end
+
+--------------------------------------------------------------------------------
+-- @function [parent=#modstore] successfulldialog
+function modstore.successfulldialog(downloading_dlg)
+ local new_dlg = dialog_create("store_downloading",
+ function(data)
+ local retval = ""
+ retval = retval .. "size[6,2,true]"
+ if modstore.lastmodentry ~= nil then
+ retval = retval .. "label[0,0.25;" .. fgettext("Successfully installed:") .. "]"
+ retval = retval .. "label[3,0.25;" .. modstore.lastmodentry.moddetails.title .. "]"
+ retval = retval .. "label[0,0.75;" .. fgettext("Shortname:") .. "]"
+ retval = retval .. "label[3,0.75;" .. core.formspec_escape(modstore.lastmodentry.moddetails.basename) .. "]"
+ end
+ retval = retval .. "button[2.2,1.5;1.5,0.5;btn_confirm_mod_successfull;" .. fgettext("Ok") .. "]"
+ return retval
+ end,
+ function(this,fields)
+ if fields["btn_confirm_mod_successfull"] ~= nil then
+ this.parent:show()
+ downloading_dlg:delete()
+ this:delete()
+
+ return true
+ end
+
+ return false
+ end,
+ nil)
+
+ new_dlg:set_parent(modstore.tv_store)
+ modstore.tv_store:hide()
+ new_dlg:show()
+end
+
+--------------------------------------------------------------------------------
+-- @function [parent=#modstore] handle_buttons
+function modstore.handle_buttons(parent, fields, name, data)
+
+ if fields["btn_modstore_page_up"] then
+ if modstore.current_list ~= nil and modstore.current_list.page > 0 then
+ modstore.current_list.page = modstore.current_list.page - 1
+ end
+ return true
+ end
+
+ if fields["btn_modstore_page_down"] then
+ if modstore.current_list ~= nil and
+ modstore.current_list.page <modstore.current_list.pagecount-1 then
+ modstore.current_list.page = modstore.current_list.page +1
+ end
+ return true
+ end
+
+ if fields["btn_modstore_search"] or
+ (fields["key_enter"] and fields["te_modstore_search"] ~= nil) then
+ modstore.last_search = fields["te_modstore_search"]
+ filterlist.set_filtercriteria(modstore.searchlist,fields["te_modstore_search"])
+ filterlist.refresh(modstore.searchlist)
+ modstore.currentlist = {
+ page = 0,
+ pagecount = math.ceil(filterlist.size(modstore.searchlist) / modstore.modsperpage),
+ data = filterlist.get_list(modstore.searchlist),
+ }
+ return true
+ end
+
+ if fields["btn_modstore_close"] then
+ local maintab = ui.find_by_name("maintab")
+ parent:hide()
+ maintab:show()
+ return true
+ end
+
+ for key,value in pairs(fields) do
+ local foundat = key:find("btn_install_mod_")
+ if ( foundat == 1) then
+ local modid = tonumber(key:sub(17))
+ for i=1,#modstore.modlist_unsorted.data,1 do
+ if modstore.modlist_unsorted.data[i].id == modid then
+ local moddetails = modstore.modlist_unsorted.data[i].details
+ modstore.lastmodtitle = moddetails.title
+
+ if not core.handle_async(
+ function(param)
+ local fullurl = core.settings:get("modstore_download_url") ..
+ param.moddetails.download_url
+
+ if param.version ~= nil then
+ local found = false
+ for i=1,#param.moddetails.versions, 1 do
+ if param.moddetails.versions[i].date:sub(1,10) == param.version then
+ fullurl = core.settings:get("modstore_download_url") ..
+ param.moddetails.versions[i].download_url
+ found = true
+ end
+ end
+
+ if not found then
+ core.log("error","no download url found for version " .. dump(param.version))
+ return {
+ moddetails = param.moddetails,
+ successfull = false
+ }
+ end
+ end
+
+ if core.download_file(fullurl,param.filename) then
+ return {
+ texturename = param.texturename,
+ moddetails = param.moddetails,
+ filename = param.filename,
+ successfull = true
+ }
+ else
+ core.log("error","downloading " .. dump(fullurl) .. " failed")
+ return {
+ moddetails = param.moddetails,
+ successfull = false
+ }
+ end
+ end,
+ {
+ moddetails = moddetails,
+ version = fields["dd_version" .. modid],
+ filename = os.tempfolder() .. "_MODNAME_" .. moddetails.basename .. ".zip",
+ texturename = modstore.modlist_unsorted.data[i].texturename
+ },
+ function(result)
+ --print("Result from async: " .. dump(result.successfull))
+ if result.successfull then
+ modmgr.installmod(result.filename,result.moddetails.basename)
+ os.remove(result.filename)
+ else
+ gamedata.errormessage = "Failed to download " .. result.moddetails.title
+ end
+
+ if gamedata.errormessage == nil then
+ core.button_handler({btn_hidden_close_download=result})
+ else
+ core.button_handler({btn_hidden_close_download={successfull=false}})
+ end
+ end
+ ) then
+ print("ERROR: async event failed")
+ gamedata.errormessage = "Failed to download " .. modstore.lastmodtitle
+ end
+
+ modstore.showdownloading(modstore.lastmodtitle)
+ return true
+ end
+ end
+ return true
+ end
+ end
+
+ return false
+end
+
+--------------------------------------------------------------------------------
+-- @function [parent=#modstore] handle_events
+function modstore.handle_events(this,event)
+ if (event == "MenuQuit") then
+ this:hide()
+ return true
+ end
+end
+
+--------------------------------------------------------------------------------
+-- @function [parent=#modstore] update_modlist
+function modstore.update_modlist()
+ modstore.modlist_unsorted = {}
+ modstore.modlist_unsorted.data = {}
+ modstore.modlist_unsorted.pagecount = 1
+ modstore.modlist_unsorted.page = 0
+
+ core.handle_async(
+ function(param)
+ return core.get_modstore_list()
+ end,
+ nil,
+ function(result)
+ if result ~= nil then
+ modstore.modlist_unsorted = {}
+ modstore.modlist_unsorted.data = result
+
+ if modstore.modlist_unsorted.data ~= nil then
+ modstore.modlist_unsorted.pagecount =
+ math.ceil((#modstore.modlist_unsorted.data / modstore.modsperpage))
+ else
+ modstore.modlist_unsorted.data = {}
+ modstore.modlist_unsorted.pagecount = 1
+ end
+ modstore.modlist_unsorted.page = 0
+ modstore.fetchdetails()
+ core.event_handler("Refresh")
+ end
+ end
+ )
+end
+
+--------------------------------------------------------------------------------
+-- @function [parent=#modstore] fetchdetails
+function modstore.fetchdetails()
+
+ for i=1,#modstore.modlist_unsorted.data,1 do
+ core.handle_async(
+ function(param)
+ param.details = core.get_modstore_details(tostring(param.modid))
+ return param
+ end,
+ {
+ modid=modstore.modlist_unsorted.data[i].id,
+ listindex=i
+ },
+ function(result)
+ if result ~= nil and
+ modstore.modlist_unsorted ~= nil
+ and modstore.modlist_unsorted.data ~= nil and
+ modstore.modlist_unsorted.data[result.listindex] ~= nil and
+ modstore.modlist_unsorted.data[result.listindex].id ~= nil then
+
+ modstore.modlist_unsorted.data[result.listindex].details = result.details
+ core.event_handler("Refresh")
+ end
+ end
+ )
+ end
+end
+
+--------------------------------------------------------------------------------
+-- @function [parent=#modstore] getscreenshot
+function modstore.getscreenshot(ypos,listentry)
+
+ if listentry.details ~= nil and
+ (listentry.details.screenshot_url == nil or
+ listentry.details.screenshot_url == "") then
+
+ if listentry.texturename == nil then
+ listentry.texturename = defaulttexturedir .. "no_screenshot.png"
+ end
+
+ return "image[0,".. ypos .. ";3,2;" ..
+ core.formspec_escape(listentry.texturename) .. "]"
+ end
+
+ if listentry.details ~= nil and
+ listentry.texturename == nil then
+ --make sure we don't download multiple times
+ listentry.texturename = "in progress"
+
+ --prepare url and filename
+ local fullurl = core.settings:get("modstore_download_url") ..
+ listentry.details.screenshot_url
+ local filename = os.tempfolder() .. "_MID_" .. listentry.id
+
+ --trigger download
+ core.handle_async(
+ --first param is downloadfct
+ function(param)
+ param.successfull = core.download_file(param.fullurl,param.filename)
+ return param
+ end,
+ --second parameter is data passed to async job
+ {
+ fullurl = fullurl,
+ filename = filename,
+ modid = listentry.id
+ },
+ --integrate result to raw list
+ function(result)
+ if result.successfull then
+ local found = false
+ for i=1,#modstore.modlist_unsorted.data,1 do
+ if modstore.modlist_unsorted.data[i].id == result.modid then
+ found = true
+ modstore.modlist_unsorted.data[i].texturename = result.filename
+ break
+ end
+ end
+ if found then
+ core.event_handler("Refresh")
+ else
+ core.log("error","got screenshot but didn't find matching mod: " .. result.modid)
+ end
+ end
+ end
+ )
+ end
+
+ if listentry.texturename ~= nil and
+ listentry.texturename ~= "in progress" then
+ return "image[0,".. ypos .. ";3,2;" ..
+ core.formspec_escape(listentry.texturename) .. "]"
+ end
+
+ return ""
+end
+
+--------------------------------------------------------------------------------
+--@function [parent=#modstore] getshortmodinfo
+function modstore.getshortmodinfo(ypos,listentry,details)
+ local retval = ""
+
+ retval = retval .. "box[0," .. ypos .. ";11.4,1.75;#FFFFFF]"
+
+ --screenshot
+ retval = retval .. modstore.getscreenshot(ypos,listentry)
+
+ --title + author
+ retval = retval .."label[2.75," .. ypos .. ";" ..
+ core.formspec_escape(details.title) .. " (" .. details.author .. ")]"
+
+ --description
+ local descriptiony = ypos + 0.5
+ retval = retval .. "textarea[3," .. descriptiony .. ";6.5,1.55;;" ..
+ core.formspec_escape(details.description) .. ";]"
+
+ --rating
+ local ratingy = ypos
+ retval = retval .."label[7," .. ratingy .. ";" ..
+ fgettext("Rating") .. ":]"
+ retval = retval .. "label[8.7," .. ratingy .. ";" .. details.rating .."]"
+
+ --versions (IMPORTANT has to be defined AFTER rating)
+ if details.versions ~= nil and
+ #details.versions > 1 then
+ local versiony = ypos + 0.05
+ retval = retval .. "dropdown[9.1," .. versiony .. ";2.48,0.25;dd_version" .. details.id .. ";"
+ local versions = ""
+ for i=1,#details.versions , 1 do
+ if versions ~= "" then
+ versions = versions .. ","
+ end
+
+ versions = versions .. details.versions[i].date:sub(1,10)
+ end
+ retval = retval .. versions .. ";1]"
+ end
+
+ if details.basename then
+ --install button
+ local buttony = ypos + 1.2
+ retval = retval .."button[9.1," .. buttony .. ";2.5,0.5;btn_install_mod_" .. details.id .. ";"
+
+ if modmgr.mod_exists(details.basename) then
+ retval = retval .. fgettext("re-Install") .."]"
+ else
+ retval = retval .. fgettext("Install") .."]"
+ end
+ end
+
+ return retval
+end
+
+--------------------------------------------------------------------------------
+--@function [parent=#modstore] getmodlist
+function modstore.getmodlist(list,yoffset)
+ modstore.current_list = list
+
+ if yoffset == nil then
+ yoffset = 0
+ end
+
+ local sb_y_start = 0.2 + yoffset
+ local sb_y_end = (modstore.modsperpage * 1.75) + ((modstore.modsperpage-1) * 0.15)
+ local close_button = "button[4," .. (sb_y_end + 0.3 + yoffset) ..
+ ";4,0.5;btn_modstore_close;" .. fgettext("Close store") .. "]"
+
+ if #list.data == 0 then
+ return close_button
+ end
+
+ local scrollbar = ""
+ scrollbar = scrollbar .. "label[0.1,".. (sb_y_end + 0.25 + yoffset) ..";"
+ .. fgettext("Page $1 of $2", list.page+1, list.pagecount) .. "]"
+ scrollbar = scrollbar .. "box[11.6," .. sb_y_start .. ";0.28," .. sb_y_end .. ";#000000]"
+ local scrollbarpos = (sb_y_start + 0.5) +
+ ((sb_y_end -1.6)/(list.pagecount-1)) * list.page
+ scrollbar = scrollbar .. "box[11.6," ..scrollbarpos .. ";0.28,0.5;#32CD32]"
+ scrollbar = scrollbar .. "button[11.6," .. (sb_y_start)
+ .. ";0.5,0.5;btn_modstore_page_up;^]"
+ scrollbar = scrollbar .. "button[11.6," .. (sb_y_start + sb_y_end - 0.5)
+ .. ";0.5,0.5;btn_modstore_page_down;v]"
+
+ local retval = ""
+
+ local endmod = (list.page * modstore.modsperpage) + modstore.modsperpage
+
+ if (endmod > #list.data) then
+ endmod = #list.data
+ end
+
+ for i=(list.page * modstore.modsperpage) +1, endmod, 1 do
+ --getmoddetails
+ local details = list.data[i].details
+
+ if details == nil then
+ details = {}
+ details.title = list.data[i].title
+ details.author = ""
+ details.rating = -1
+ details.description = ""
+ end
+
+ if details ~= nil then
+ local screenshot_ypos =
+ yoffset +(i-1 - (list.page * modstore.modsperpage))*1.9 +0.2
+
+ retval = retval .. modstore.getshortmodinfo(screenshot_ypos,
+ list.data[i],
+ details)
+ end
+ end
+
+ return retval .. scrollbar .. close_button
+end
+
+--------------------------------------------------------------------------------
+--@function [parent=#modstore] getsearchpage
+function modstore.getsearchpage(tabview, name, tabdata)
+ local retval = ""
+ local search = ""
+
+ if modstore.last_search ~= nil then
+ search = modstore.last_search
+ end
+
+ retval = retval ..
+ "button[9.5,0.2;2.5,0.5;btn_modstore_search;".. fgettext("Search") .. "]" ..
+ "field[0.5,0.5;9,0.5;te_modstore_search;;" .. search .. "]"
+
+ retval = retval ..
+ modstore.getmodlist(
+ modstore.currentlist,
+ 1.75)
+
+ return retval;
+end
+
+--------------------------------------------------------------------------------
+--@function [parent=#modstore] unsorted_tab
+function modstore.unsorted_tab()
+ return modstore.getmodlist(modstore.modlist_unsorted)
+end
+
+--------------------------------------------------------------------------------
+--@function [parent=#modstore] activate_search_tab
+function modstore.activate_search_tab(type, old_tab, new_tab)
+
+ if old_tab == new_tab then
+ return
+ end
+ filterlist.set_filtercriteria(modstore.searchlist,modstore.last_search)
+ filterlist.refresh(modstore.searchlist)
+ modstore.modsperpage = modstore.mods_on_search_page
+ modstore.currentlist = {
+ page = 0,
+ pagecount =
+ math.ceil(filterlist.size(modstore.searchlist) / modstore.modsperpage),
+ data = filterlist.get_list(modstore.searchlist),
+ }
+end
+
diff --git a/builtin/mainmenu/tab_credits.lua b/builtin/mainmenu/tab_credits.lua
new file mode 100644
index 000000000..b02352257
--- /dev/null
+++ b/builtin/mainmenu/tab_credits.lua
@@ -0,0 +1,106 @@
+--Minetest
+--Copyright (C) 2013 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+--------------------------------------------------------------------------------
+
+local core_developers = {
+ "Perttu Ahola (celeron55) <celeron55@gmail.com>",
+ "sfan5 <sfan5@live.de>",
+ "ShadowNinja <shadowninja@minetest.net>",
+ "Nathanaël Courant (Nore/Ekdohibs) <nore@mesecons.net>",
+ "Loic Blot (nerzhul/nrz) <loic.blot@unix-experience.fr>",
+ "paramat",
+ "Craig Robbins (Zeno) <craig.d.robbins@gmail.com>",
+ "Auke Kok (sofar) <sofar@foo-projects.org>",
+ "rubenwardy <rw@rubenwardy.com>",
+ "Krock/SmallJoker <mk939@ymail.com>",
+}
+
+local active_contributors = {
+ "red-001 <red-001@outlook.ie> [CSM & Menu fixes]",
+ "Dániel Juhász (juhdanad) <juhdanad@gmail.com> [Audiovisuals: lighting]",
+ "numberZero [Audiovisuals: meshgen]",
+ "Lars Hofhansl <larsh@apache.org> [Occulusion culling, fixes]",
+ "Jean-Patrick G (kilbith) <jeanpatrick.guerrero@gmail.com> [Audiovisuals]",
+ "Vincent Glize (Dumbeldor) <vincent.glize@live.fr> [CSM]",
+ "bigfoot547 [CSM]",
+ "Rogier <rogier777@gmail.com> [Fixes]",
+ "Wuzzy [Audiovisuals]",
+ "Shara/Ezhh [Settings]",
+}
+
+local previous_core_developers = {
+ "BlockMen",
+ "Maciej Kasatkin (RealBadAngel) [RIP]",
+ "Lisa Milne (darkrose) <lisa@ltmnet.com>",
+ "proller",
+ "Ilya Zhuravlev (xyz) <xyz@minetest.net>",
+ "PilzAdam <pilzadam@minetest.net>",
+ "est31 <MTest31@outlook.com>",
+ "kahrl <kahrl@gmx.net>",
+ "Ryan Kwolek (kwolekr) <kwolekr@minetest.net>",
+ "sapier",
+}
+
+local previous_contributors = {
+ "Gregory Currie (gregorycu) [optimisation]",
+ "Diego Martínez (kaeza) <kaeza@users.sf.net>",
+ "T4im [Profiler]",
+ "TeTpaAka [Hand overriding, nametag colors]",
+ "HybridDog [Fixes]",
+ "Duane Robertson <duane@duanerobertson.com> [MGValleys]",
+ "neoascetic [OS X Fixes]",
+ "TriBlade9 <triblade9@mail.com> [Audiovisuals]",
+ "Jurgen Doser (doserj) <jurgen.doser@gmail.com> [Fixes]",
+ "MirceaKitsune <mirceakitsune@gmail.com> [Audiovisuals]",
+ "Guiseppe Bilotta (Oblomov) <guiseppe.bilotta@gmail.com> [Fixes]",
+ "matttpt <matttpt@gmail.com> [Fixes]",
+ "Nils Dagsson Moskopp (erlehmann) <nils@dieweltistgarnichtso.net> [Minetest Logo]",
+ "Jeija <jeija@mesecons.net> [HTTP, particles]",
+}
+
+local function buildCreditList(source)
+ local ret = {}
+ for i = 1, #source do
+ ret[i] = core.formspec_escape(source[i])
+ end
+ return table.concat(ret, ",,")
+end
+
+return {
+ name = "credits",
+ caption = fgettext("Credits"),
+ cbf_formspec = function(tabview, name, tabdata)
+ local logofile = defaulttexturedir .. "logo.png"
+ local version = core.get_version()
+ return "image[0.5,1;" .. core.formspec_escape(logofile) .. "]" ..
+ "label[0.5,3.2;" .. version.project .. " " .. version.string .. "]" ..
+ "label[0.5,3.5;http://minetest.net]" ..
+ "tablecolumns[color;text]" ..
+ "tableoptions[background=#00000000;highlight=#00000000;border=false]" ..
+ "table[3.5,-0.25;8.5,6.05;list_credits;" ..
+ "#FFFF00," .. fgettext("Core Developers") .. ",," ..
+ buildCreditList(core_developers) .. ",,," ..
+ "#FFFF00," .. fgettext("Active Contributors") .. ",," ..
+ buildCreditList(active_contributors) .. ",,," ..
+ "#FFFF00," .. fgettext("Previous Core Developers") ..",," ..
+ buildCreditList(previous_core_developers) .. ",,," ..
+ "#FFFF00," .. fgettext("Previous Contributors") .. ",," ..
+ buildCreditList(previous_contributors) .. "," ..
+ ";1]"
+ end
+}
diff --git a/builtin/mainmenu/tab_local.lua b/builtin/mainmenu/tab_local.lua
new file mode 100644
index 000000000..3e62078ce
--- /dev/null
+++ b/builtin/mainmenu/tab_local.lua
@@ -0,0 +1,317 @@
+--Minetest
+--Copyright (C) 2014 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+local function current_game()
+ local last_game_id = core.settings:get("menu_last_game")
+ local game, index = gamemgr.find_by_gameid(last_game_id)
+
+ return game
+end
+
+local function singleplayer_refresh_gamebar()
+
+ local old_bar = ui.find_by_name("game_button_bar")
+
+ if old_bar ~= nil then
+ old_bar:delete()
+ end
+
+ local function game_buttonbar_button_handler(fields)
+ for key,value in pairs(fields) do
+ for j=1,#gamemgr.games,1 do
+ if ("game_btnbar_" .. gamemgr.games[j].id == key) then
+ mm_texture.update("singleplayer", gamemgr.games[j])
+ core.set_topleft_text(gamemgr.games[j].name)
+ core.settings:set("menu_last_game",gamemgr.games[j].id)
+ menudata.worldlist:set_filtercriteria(gamemgr.games[j].id)
+ local index = filterlist.get_current_index(menudata.worldlist,
+ tonumber(core.settings:get("mainmenu_last_selected_world")))
+ if not index or index < 1 then
+ local selected = core.get_textlist_index("sp_worlds")
+ if selected ~= nil and selected < #menudata.worldlist:get_list() then
+ index = selected
+ else
+ index = #menudata.worldlist:get_list()
+ end
+ end
+ menu_worldmt_legacy(index)
+ return true
+ end
+ end
+ end
+ end
+
+ local btnbar = buttonbar_create("game_button_bar",
+ game_buttonbar_button_handler,
+ {x=-0.3,y=5.9}, "horizontal", {x=12.4,y=1.15})
+
+ for i=1,#gamemgr.games,1 do
+ local btn_name = "game_btnbar_" .. gamemgr.games[i].id
+
+ local image = nil
+ local text = nil
+ local tooltip = core.formspec_escape(gamemgr.games[i].name)
+
+ if gamemgr.games[i].menuicon_path ~= nil and
+ gamemgr.games[i].menuicon_path ~= "" then
+ image = core.formspec_escape(gamemgr.games[i].menuicon_path)
+ else
+
+ local part1 = gamemgr.games[i].id:sub(1,5)
+ local part2 = gamemgr.games[i].id:sub(6,10)
+ local part3 = gamemgr.games[i].id:sub(11)
+
+ text = part1 .. "\n" .. part2
+ if part3 ~= nil and
+ part3 ~= "" then
+ text = text .. "\n" .. part3
+ end
+ end
+ btnbar:add_button(btn_name, text, image, tooltip)
+ end
+end
+
+local function get_formspec(tabview, name, tabdata)
+ local retval = ""
+
+ local index = filterlist.get_current_index(menudata.worldlist,
+ tonumber(core.settings:get("mainmenu_last_selected_world"))
+ )
+
+ retval = retval ..
+ "button[4,4.15;2.6,0.5;world_delete;".. fgettext("Delete") .. "]" ..
+ "button[6.5,4.15;2.8,0.5;world_create;".. fgettext("New") .. "]" ..
+ "button[9.2,4.15;2.55,0.5;world_configure;".. fgettext("Configure") .. "]" ..
+ "label[4,-0.25;".. fgettext("Select World:") .. "]"..
+ "checkbox[0.25,0.25;cb_creative_mode;".. fgettext("Creative Mode") .. ";" ..
+ dump(core.settings:get_bool("creative_mode")) .. "]"..
+ "checkbox[0.25,0.7;cb_enable_damage;".. fgettext("Enable Damage") .. ";" ..
+ dump(core.settings:get_bool("enable_damage")) .. "]"..
+ "checkbox[0.25,1.15;cb_server;".. fgettext("Host Server") ..";" ..
+ dump(core.settings:get_bool("enable_server")) .. "]" ..
+ "textlist[4,0.25;7.5,3.7;sp_worlds;" ..
+ menu_render_worldlist() ..
+ ";" .. index .. "]"
+
+ if core.settings:get_bool("enable_server") then
+ retval = retval ..
+ "button[8.5,5;3.25,0.5;play;".. fgettext("Host Game") .. "]" ..
+ "checkbox[0.25,1.6;cb_server_announce;" .. fgettext("Announce Server") .. ";" ..
+ dump(core.settings:get_bool("server_announce")) .. "]" ..
+ "label[0.25,2.2;" .. fgettext("Name/Password") .. "]" ..
+ "field[0.55,3.2;3.5,0.5;te_playername;;" ..
+ core.formspec_escape(core.settings:get("name")) .. "]" ..
+ "pwdfield[0.55,4;3.5,0.5;te_passwd;]"
+
+ local bind_addr = core.settings:get("bind_address")
+ if bind_addr ~= nil and bind_addr ~= "" then
+ retval = retval ..
+ "field[0.55,5.2;2.25,0.5;te_serveraddr;" .. fgettext("Bind Address") .. ";" ..
+ core.formspec_escape(core.settings:get("bind_address")) .. "]" ..
+ "field[2.8,5.2;1.25,0.5;te_serverport;" .. fgettext("Port") .. ";" ..
+ core.formspec_escape(core.settings:get("port")) .. "]"
+ else
+ retval = retval ..
+ "field[0.55,5.2;3.5,0.5;te_serverport;" .. fgettext("Server Port") .. ";" ..
+ core.formspec_escape(core.settings:get("port")) .. "]"
+ end
+ else
+ retval = retval ..
+ "button[8.5,5;3.25,0.5;play;".. fgettext("Play Game") .. "]"
+ end
+
+ return retval
+end
+
+local function main_button_handler(this, fields, name, tabdata)
+
+ assert(name == "local")
+
+ local world_doubleclick = false
+
+ if fields["sp_worlds"] ~= nil then
+ local event = core.explode_textlist_event(fields["sp_worlds"])
+ local selected = core.get_textlist_index("sp_worlds")
+
+ menu_worldmt_legacy(selected)
+
+ if event.type == "DCL" then
+ world_doubleclick = true
+ end
+
+ if event.type == "CHG" and selected ~= nil then
+ core.settings:set("mainmenu_last_selected_world",
+ menudata.worldlist:get_raw_index(selected))
+ return true
+ end
+ end
+
+ if menu_handle_key_up_down(fields,"sp_worlds","mainmenu_last_selected_world") then
+ return true
+ end
+
+ if fields["cb_creative_mode"] then
+ core.settings:set("creative_mode", fields["cb_creative_mode"])
+ local selected = core.get_textlist_index("sp_worlds")
+ menu_worldmt(selected, "creative_mode", fields["cb_creative_mode"])
+
+ return true
+ end
+
+ if fields["cb_enable_damage"] then
+ core.settings:set("enable_damage", fields["cb_enable_damage"])
+ local selected = core.get_textlist_index("sp_worlds")
+ menu_worldmt(selected, "enable_damage", fields["cb_enable_damage"])
+
+ return true
+ end
+
+ if fields["cb_server"] then
+ core.settings:set("enable_server", fields["cb_server"])
+
+ return true
+ end
+
+ if fields["cb_server_announce"] then
+ core.settings:set("server_announce", fields["cb_server_announce"])
+ local selected = core.get_textlist_index("srv_worlds")
+ menu_worldmt(selected, "server_announce", fields["cb_server_announce"])
+
+ return true
+ end
+
+ if fields["play"] ~= nil or world_doubleclick or fields["key_enter"] then
+ local selected = core.get_textlist_index("sp_worlds")
+ gamedata.selected_world = menudata.worldlist:get_raw_index(selected)
+
+ if core.settings:get_bool("enable_server") then
+ if selected ~= nil and gamedata.selected_world ~= 0 then
+ gamedata.playername = fields["te_playername"]
+ gamedata.password = fields["te_passwd"]
+ gamedata.port = fields["te_serverport"]
+ gamedata.address = ""
+
+ core.settings:set("port",gamedata.port)
+ if fields["te_serveraddr"] ~= nil then
+ core.settings:set("bind_address",fields["te_serveraddr"])
+ end
+
+ --update last game
+ local world = menudata.worldlist:get_raw_element(gamedata.selected_world)
+ if world then
+ local game, index = gamemgr.find_by_gameid(world.gameid)
+ core.settings:set("menu_last_game", game.id)
+ end
+
+ core.start()
+ else
+ gamedata.errormessage =
+ fgettext("No world created or selected!")
+ end
+ else
+ if selected ~= nil and gamedata.selected_world ~= 0 then
+ gamedata.singleplayer = true
+ core.start()
+ else
+ gamedata.errormessage =
+ fgettext("No world created or selected!")
+ end
+ return true
+ end
+ end
+
+ if fields["world_create"] ~= nil then
+ local create_world_dlg = create_create_world_dlg(true)
+ create_world_dlg:set_parent(this)
+ this:hide()
+ create_world_dlg:show()
+ mm_texture.update("singleplayer",current_game())
+ return true
+ end
+
+ if fields["world_delete"] ~= nil then
+ local selected = core.get_textlist_index("sp_worlds")
+ if selected ~= nil and
+ selected <= menudata.worldlist:size() then
+ local world = menudata.worldlist:get_list()[selected]
+ if world ~= nil and
+ world.name ~= nil and
+ world.name ~= "" then
+ local index = menudata.worldlist:get_raw_index(selected)
+ local delete_world_dlg = create_delete_world_dlg(world.name,index)
+ delete_world_dlg:set_parent(this)
+ this:hide()
+ delete_world_dlg:show()
+ mm_texture.update("singleplayer",current_game())
+ end
+ end
+
+ return true
+ end
+
+ if fields["world_configure"] ~= nil then
+ local selected = core.get_textlist_index("sp_worlds")
+ if selected ~= nil then
+ local configdialog =
+ create_configure_world_dlg(
+ menudata.worldlist:get_raw_index(selected))
+
+ if (configdialog ~= nil) then
+ configdialog:set_parent(this)
+ this:hide()
+ configdialog:show()
+ mm_texture.update("singleplayer",current_game())
+ end
+ end
+
+ return true
+ end
+end
+
+local function on_change(type, old_tab, new_tab)
+ local buttonbar = ui.find_by_name("game_button_bar")
+
+ if ( buttonbar == nil ) then
+ singleplayer_refresh_gamebar()
+ buttonbar = ui.find_by_name("game_button_bar")
+ end
+
+ if (type == "ENTER") then
+ local game = current_game()
+
+ if game then
+ menudata.worldlist:set_filtercriteria(game.id)
+ core.set_topleft_text(game.name)
+ mm_texture.update("singleplayer",game)
+ end
+ buttonbar:show()
+ else
+ menudata.worldlist:set_filtercriteria(nil)
+ buttonbar:hide()
+ core.set_topleft_text("")
+ mm_texture.update(new_tab,nil)
+ end
+end
+
+--------------------------------------------------------------------------------
+return {
+ name = "local",
+ caption = fgettext("Local Game"),
+ cbf_formspec = get_formspec,
+ cbf_button_handler = main_button_handler,
+ on_change = on_change
+}
diff --git a/builtin/mainmenu/tab_mods.lua b/builtin/mainmenu/tab_mods.lua
new file mode 100644
index 000000000..7f95355a9
--- /dev/null
+++ b/builtin/mainmenu/tab_mods.lua
@@ -0,0 +1,185 @@
+--Minetest
+--Copyright (C) 2014 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+--------------------------------------------------------------------------------
+local function get_formspec(tabview, name, tabdata)
+
+ if modmgr.global_mods == nil then
+ modmgr.refresh_globals()
+ end
+
+ if tabdata.selected_mod == nil then
+ tabdata.selected_mod = 1
+ end
+
+ local retval =
+ "label[0.05,-0.25;".. fgettext("Installed Mods:") .. "]" ..
+ "tablecolumns[color;tree;text]" ..
+ "table[0,0.25;5.1,5;modlist;" ..
+ modmgr.render_modlist(modmgr.global_mods) ..
+ ";" .. tabdata.selected_mod .. "]"
+
+ retval = retval ..
+-- "label[0.8,4.2;" .. fgettext("Add mod:") .. "]" ..
+-- TODO Disabled due to upcoming release 0.4.8 and irrlicht messing up localization
+-- "button[0.75,4.85;1.8,0.5;btn_mod_mgr_install_local;".. fgettext("Local install") .. "]" ..
+
+-- TODO Disabled due to service being offline, and not likely to come online again, in this form
+-- "button[0,4.85;5.25,0.5;btn_modstore;".. fgettext("Online mod repository") .. "]"
+ ""
+
+ local selected_mod = nil
+
+ if filterlist.size(modmgr.global_mods) >= tabdata.selected_mod then
+ selected_mod = modmgr.global_mods:get_list()[tabdata.selected_mod]
+ end
+
+ if selected_mod ~= nil then
+ local modscreenshot = nil
+
+ --check for screenshot beeing available
+ local screenshotfilename = selected_mod.path .. DIR_DELIM .. "screenshot.png"
+ local error = nil
+ local screenshotfile,error = io.open(screenshotfilename,"r")
+ if error == nil then
+ screenshotfile:close()
+ modscreenshot = screenshotfilename
+ end
+
+ if modscreenshot == nil then
+ modscreenshot = defaulttexturedir .. "no_screenshot.png"
+ end
+
+ retval = retval
+ .. "image[5.5,0;3,2;" .. core.formspec_escape(modscreenshot) .. "]"
+ .. "label[8.25,0.6;" .. selected_mod.name .. "]"
+
+ local descriptionlines = nil
+ error = nil
+ local descriptionfilename = selected_mod.path .. "description.txt"
+ local descriptionfile,error = io.open(descriptionfilename,"r")
+ if error == nil then
+ local descriptiontext = descriptionfile:read("*all")
+
+ descriptionlines = core.wrap_text(descriptiontext, 42, true)
+ descriptionfile:close()
+ else
+ descriptionlines = {}
+ descriptionlines[#descriptionlines + 1] = fgettext("No mod description available")
+ end
+
+ retval = retval ..
+ "label[5.5,1.7;".. fgettext("Mod information:") .. "]" ..
+ "textlist[5.5,2.2;6.2,2.4;description;"
+
+ for i=1,#descriptionlines,1 do
+ retval = retval .. core.formspec_escape(descriptionlines[i]) .. ","
+ end
+
+
+ if selected_mod.is_modpack then
+ retval = retval .. ";0]" ..
+ "button[10,4.85;2,0.5;btn_mod_mgr_rename_modpack;" ..
+ fgettext("Rename") .. "]"
+ retval = retval .. "button[5.5,4.85;4.5,0.5;btn_mod_mgr_delete_mod;"
+ .. fgettext("Uninstall selected modpack") .. "]"
+ else
+ --show dependencies
+ local toadd_hard, toadd_soft = modmgr.get_dependencies(selected_mod.path)
+ if toadd_hard == "" and toadd_soft == "" then
+ retval = retval .. "," .. fgettext("No dependencies.")
+ else
+ if toadd_hard ~= "" then
+ retval = retval .. "," .. fgettext("Dependencies:") .. ","
+ retval = retval .. toadd_hard
+ end
+ if toadd_soft ~= "" then
+ if toadd_hard ~= "" then
+ retval = retval .. ","
+ end
+ retval = retval .. "," .. fgettext("Optional dependencies:") .. ","
+ retval = retval .. toadd_soft
+ end
+ end
+
+ retval = retval .. ";0]"
+
+ retval = retval .. "button[5.5,4.85;4.5,0.5;btn_mod_mgr_delete_mod;"
+ .. fgettext("Uninstall selected mod") .. "]"
+ end
+ end
+ return retval
+end
+
+--------------------------------------------------------------------------------
+local function handle_buttons(tabview, fields, tabname, tabdata)
+ if fields["modlist"] ~= nil then
+ local event = core.explode_table_event(fields["modlist"])
+ tabdata.selected_mod = event.row
+ return true
+ end
+
+ if fields["btn_mod_mgr_install_local"] ~= nil then
+ core.show_file_open_dialog("mod_mgt_open_dlg",fgettext("Select Mod File:"))
+ return true
+ end
+
+ if fields["btn_modstore"] ~= nil then
+ local modstore_ui = ui.find_by_name("modstore")
+ if modstore_ui ~= nil then
+ tabview:hide()
+ modstore.update_modlist()
+ modstore_ui:show()
+ else
+ print("modstore ui element not found")
+ end
+ return true
+ end
+
+ if fields["btn_mod_mgr_rename_modpack"] ~= nil then
+ local dlg_renamemp = create_rename_modpack_dlg(tabdata.selected_mod)
+ dlg_renamemp:set_parent(tabview)
+ tabview:hide()
+ dlg_renamemp:show()
+ return true
+ end
+
+ if fields["btn_mod_mgr_delete_mod"] ~= nil then
+ local dlg_delmod = create_delete_mod_dlg(tabdata.selected_mod)
+ dlg_delmod:set_parent(tabview)
+ tabview:hide()
+ dlg_delmod:show()
+ return true
+ end
+
+ if fields["mod_mgt_open_dlg_accepted"] ~= nil and
+ fields["mod_mgt_open_dlg_accepted"] ~= "" then
+ modmgr.installmod(fields["mod_mgt_open_dlg_accepted"],nil)
+ return true
+ end
+
+ return false
+end
+
+--------------------------------------------------------------------------------
+return {
+ name = "mods",
+ caption = fgettext("Mods"),
+ cbf_formspec = get_formspec,
+ cbf_button_handler = handle_buttons,
+ on_change = gamemgr.update_gamelist
+}
diff --git a/builtin/mainmenu/tab_online.lua b/builtin/mainmenu/tab_online.lua
new file mode 100644
index 000000000..ab23a4b7c
--- /dev/null
+++ b/builtin/mainmenu/tab_online.lua
@@ -0,0 +1,350 @@
+--Minetest
+--Copyright (C) 2014 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+--------------------------------------------------------------------------------
+local function get_formspec(tabview, name, tabdata)
+ -- Update the cached supported proto info,
+ -- it may have changed after a change by the settings menu.
+ common_update_cached_supp_proto()
+ local fav_selected = nil
+ if menudata.search_result then
+ fav_selected = menudata.search_result[tabdata.fav_selected]
+ else
+ fav_selected = menudata.favorites[tabdata.fav_selected]
+ end
+
+ if not tabdata.search_for then
+ tabdata.search_for = ""
+ end
+
+ local retval =
+ -- Search
+ "field[0.15,0.35;6.05,0.27;te_search;;"..core.formspec_escape(tabdata.search_for).."]"..
+ "button[5.8,0.1;2,0.1;btn_mp_search;" .. fgettext("Search") .. "]" ..
+
+ -- Address / Port
+ "label[7.75,-0.25;" .. fgettext("Address / Port") .. "]" ..
+ "field[8,0.65;3.25,0.5;te_address;;" ..
+ core.formspec_escape(core.settings:get("address")) .. "]" ..
+ "field[11.1,0.65;1.4,0.5;te_port;;" ..
+ core.formspec_escape(core.settings:get("remote_port")) .. "]" ..
+
+ -- Name / Password
+ "label[7.75,0.95;" .. fgettext("Name / Password") .. "]" ..
+ "field[8,1.85;2.9,0.5;te_name;;" ..
+ core.formspec_escape(core.settings:get("name")) .. "]" ..
+ "pwdfield[10.73,1.85;1.77,0.5;te_pwd;]" ..
+
+ -- Description Background
+ "box[7.73,2.25;4.25,2.6;#999999]"..
+
+ -- Connect
+ "button[10.1,5.15;2,0.5;btn_mp_connect;" .. fgettext("Connect") .. "]"
+
+ if tabdata.fav_selected and fav_selected then
+ if gamedata.fav then
+ retval = retval .. "button[7.75,5.15;2.3,0.5;btn_delete_favorite;" ..
+ fgettext("Del. Favorite") .. "]"
+ end
+ if fav_selected.description then
+ retval = retval .. "textarea[8.1,2.3;4.23,2.9;;" ..
+ core.formspec_escape((gamedata.serverdescription or ""), true) .. ";]"
+ end
+ end
+
+ --favourites
+ retval = retval .. "tablecolumns[" ..
+ image_column(fgettext("Favorite"), "favorite") .. ";" ..
+ image_column(fgettext("Ping")) .. ",padding=0.25;" ..
+ "color,span=3;" ..
+ "text,align=right;" .. -- clients
+ "text,align=center,padding=0.25;" .. -- "/"
+ "text,align=right,padding=0.25;" .. -- clients_max
+ image_column(fgettext("Creative mode"), "creative") .. ",padding=1;" ..
+ image_column(fgettext("Damage enabled"), "damage") .. ",padding=0.25;" ..
+ image_column(fgettext("PvP enabled"), "pvp") .. ",padding=0.25;" ..
+ "color,span=1;" ..
+ "text,padding=1]" ..
+ "table[-0.15,0.6;7.75,5.15;favourites;"
+
+ if menudata.search_result then
+ for i = 1, #menudata.search_result do
+ local favs = core.get_favorites("local")
+ local server = menudata.search_result[i]
+
+ for fav_id = 1, #favs do
+ if server.address == favs[fav_id].address and
+ server.port == favs[fav_id].port then
+ server.is_favorite = true
+ end
+ end
+
+ if i ~= 1 then
+ retval = retval .. ","
+ end
+
+ retval = retval .. render_serverlist_row(server, server.is_favorite)
+ end
+ elseif #menudata.favorites > 0 then
+ local favs = core.get_favorites("local")
+ if #favs > 0 then
+ for i = 1, #favs do
+ for j = 1, #menudata.favorites do
+ if menudata.favorites[j].address == favs[i].address and
+ menudata.favorites[j].port == favs[i].port then
+ table.insert(menudata.favorites, i, table.remove(menudata.favorites, j))
+ end
+ end
+ if favs[i].address ~= menudata.favorites[i].address then
+ table.insert(menudata.favorites, i, favs[i])
+ end
+ end
+ end
+ retval = retval .. render_serverlist_row(menudata.favorites[1], (#favs > 0))
+ for i = 2, #menudata.favorites do
+ retval = retval .. "," .. render_serverlist_row(menudata.favorites[i], (i <= #favs))
+ end
+ end
+
+ if tabdata.fav_selected then
+ retval = retval .. ";" .. tabdata.fav_selected .. "]"
+ else
+ retval = retval .. ";0]"
+ end
+
+ return retval
+end
+
+--------------------------------------------------------------------------------
+local function main_button_handler(tabview, fields, name, tabdata)
+ local serverlist = menudata.search_result or menudata.favorites
+
+ if fields.te_name then
+ gamedata.playername = fields.te_name
+ core.settings:set("name", fields.te_name)
+ end
+
+ if fields.favourites then
+ local event = core.explode_table_event(fields.favourites)
+ local fav = serverlist[event.row]
+
+ if event.type == "DCL" then
+ if event.row <= #serverlist then
+ if menudata.favorites_is_public and
+ not is_server_protocol_compat_or_error(
+ fav.proto_min, fav.proto_max) then
+ return true
+ end
+
+ gamedata.address = fav.address
+ gamedata.port = fav.port
+ gamedata.playername = fields.te_name
+ gamedata.selected_world = 0
+
+ if fields.te_pwd then
+ gamedata.password = fields.te_pwd
+ end
+
+ gamedata.servername = fav.name
+ gamedata.serverdescription = fav.description
+
+ if gamedata.address and gamedata.port then
+ core.settings:set("address", gamedata.address)
+ core.settings:set("remote_port", gamedata.port)
+ core.start()
+ end
+ end
+ return true
+ end
+
+ if event.type == "CHG" then
+ if event.row <= #serverlist then
+ gamedata.fav = false
+ local favs = core.get_favorites("local")
+ local address = fav.address
+ local port = fav.port
+ gamedata.serverdescription = fav.description
+
+ for i = 1, #favs do
+ if fav.address == favs[i].address and
+ fav.port == favs[i].port then
+ gamedata.fav = true
+ end
+ end
+
+ if address and port then
+ core.settings:set("address", address)
+ core.settings:set("remote_port", port)
+ end
+ tabdata.fav_selected = event.row
+ end
+ return true
+ end
+ end
+
+ if fields.key_up or fields.key_down then
+ local fav_idx = core.get_table_index("favourites")
+ local fav = serverlist[fav_idx]
+
+ if fav_idx then
+ if fields.key_up and fav_idx > 1 then
+ fav_idx = fav_idx - 1
+ elseif fields.key_down and fav_idx < #menudata.favorites then
+ fav_idx = fav_idx + 1
+ end
+ else
+ fav_idx = 1
+ end
+
+ if not menudata.favorites or not fav then
+ tabdata.fav_selected = 0
+ return true
+ end
+
+ local address = fav.address
+ local port = fav.port
+ gamedata.serverdescription = fav.description
+ if address and port then
+ core.settings:set("address", address)
+ core.settings:set("remote_port", port)
+ end
+
+ tabdata.fav_selected = fav_idx
+ return true
+ end
+
+ if fields.btn_delete_favorite then
+ local current_favourite = core.get_table_index("favourites")
+ if not current_favourite then return end
+
+ core.delete_favorite(current_favourite)
+ asyncOnlineFavourites()
+ tabdata.fav_selected = nil
+
+ core.settings:set("address", "")
+ core.settings:set("remote_port", "30000")
+ return true
+ end
+
+ if fields.btn_mp_search or fields.key_enter_field == "te_search" then
+ tabdata.fav_selected = 1
+ local input = fields.te_search:lower()
+ tabdata.search_for = fields.te_search
+
+ if #menudata.favorites < 2 then
+ return true
+ end
+
+ menudata.search_result = {}
+
+ -- setup the keyword list
+ local keywords = {}
+ for word in input:gmatch("%S+") do
+ table.insert(keywords, word)
+ end
+
+ if #keywords == 0 then
+ menudata.search_result = nil
+ return true
+ end
+
+ -- Search the serverlist
+ local search_result = {}
+ for i = 1, #menudata.favorites do
+ local server = menudata.favorites[i]
+ local found = 0
+ for k = 1, #keywords do
+ local keyword = keywords[k]
+ if server.name then
+ local name = server.name:lower()
+ local _, count = name:gsub(keyword, keyword)
+ found = found + count * 4
+ end
+
+ if server.description then
+ local desc = server.description:lower()
+ local _, count = desc:gsub(keyword, keyword)
+ found = found + count * 2
+ end
+ end
+ if found > 0 then
+ local points = (#menudata.favorites - i) / 5 + found
+ server.points = points
+ table.insert(search_result, server)
+ end
+ end
+ if #search_result > 0 then
+ table.sort(search_result, function(a, b)
+ return a.points > b.points
+ end)
+ menudata.search_result = search_result
+ local first_server = search_result[1]
+ core.settings:set("address", first_server.address)
+ core.settings:set("remote_port", first_server.port)
+ end
+ return true
+ end
+
+ if (fields.btn_mp_connect or fields.key_enter)
+ and fields.te_address ~= "" and fields.te_port then
+ gamedata.playername = fields.te_name
+ gamedata.password = fields.te_pwd
+ gamedata.address = fields.te_address
+ gamedata.port = fields.te_port
+ gamedata.selected_world = 0
+ local fav_idx = core.get_table_index("favourites")
+ local fav = serverlist[fav_idx]
+
+ if fav_idx and fav_idx <= #serverlist and
+ fav.address == fields.te_address and
+ fav.port == fields.te_port then
+
+ gamedata.servername = fav.name
+ gamedata.serverdescription = fav.description
+
+ if menudata.favorites_is_public and
+ not is_server_protocol_compat_or_error(
+ fav.proto_min, fav.proto_max) then
+ return true
+ end
+ else
+ gamedata.servername = ""
+ gamedata.serverdescription = ""
+ end
+
+ core.settings:set("address", fields.te_address)
+ core.settings:set("remote_port", fields.te_port)
+
+ core.start()
+ return true
+ end
+ return false
+end
+
+local function on_change(type, old_tab, new_tab)
+ if type == "LEAVE" then return end
+ asyncOnlineFavourites()
+end
+
+--------------------------------------------------------------------------------
+return {
+ name = "online",
+ caption = fgettext("Play Online"),
+ cbf_formspec = get_formspec,
+ cbf_button_handler = main_button_handler,
+ on_change = on_change
+}
diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua
new file mode 100644
index 000000000..52bc8ead1
--- /dev/null
+++ b/builtin/mainmenu/tab_settings.lua
@@ -0,0 +1,410 @@
+--Minetest
+--Copyright (C) 2013 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+--------------------------------------------------------------------------------
+
+local labels = {
+ leaves = {
+ fgettext("Opaque Leaves"),
+ fgettext("Simple Leaves"),
+ fgettext("Fancy Leaves")
+ },
+ node_highlighting = {
+ fgettext("Node Outlining"),
+ fgettext("Node Highlighting"),
+ fgettext("None")
+ },
+ filters = {
+ fgettext("No Filter"),
+ fgettext("Bilinear Filter"),
+ fgettext("Trilinear Filter")
+ },
+ mipmap = {
+ fgettext("No Mipmap"),
+ fgettext("Mipmap"),
+ fgettext("Mipmap + Aniso. Filter")
+ },
+ antialiasing = {
+ fgettext("None"),
+ fgettext("2x"),
+ fgettext("4x"),
+ fgettext("8x")
+ }
+}
+
+local dd_options = {
+ leaves = {
+ table.concat(labels.leaves, ","),
+ {"opaque", "simple", "fancy"}
+ },
+ node_highlighting = {
+ table.concat(labels.node_highlighting, ","),
+ {"box", "halo", "none"}
+ },
+ filters = {
+ table.concat(labels.filters, ","),
+ {"", "bilinear_filter", "trilinear_filter"}
+ },
+ mipmap = {
+ table.concat(labels.mipmap, ","),
+ {"", "mip_map", "anisotropic_filter"}
+ },
+ antialiasing = {
+ table.concat(labels.antialiasing, ","),
+ {"0", "2", "4", "8"}
+ }
+}
+
+local getSettingIndex = {
+ Leaves = function()
+ local style = core.settings:get("leaves_style")
+ for idx, name in pairs(dd_options.leaves[2]) do
+ if style == name then return idx end
+ end
+ return 1
+ end,
+ NodeHighlighting = function()
+ local style = core.settings:get("node_highlighting")
+ for idx, name in pairs(dd_options.node_highlighting[2]) do
+ if style == name then return idx end
+ end
+ return 1
+ end,
+ Filter = function()
+ if core.settings:get(dd_options.filters[2][3]) == "true" then
+ return 3
+ elseif core.settings:get(dd_options.filters[2][3]) == "false" and
+ core.settings:get(dd_options.filters[2][2]) == "true" then
+ return 2
+ end
+ return 1
+ end,
+ Mipmap = function()
+ if core.settings:get(dd_options.mipmap[2][3]) == "true" then
+ return 3
+ elseif core.settings:get(dd_options.mipmap[2][3]) == "false" and
+ core.settings:get(dd_options.mipmap[2][2]) == "true" then
+ return 2
+ end
+ return 1
+ end,
+ Antialiasing = function()
+ local antialiasing_setting = core.settings:get("fsaa")
+ for i = 1, #dd_options.antialiasing[2] do
+ if antialiasing_setting == dd_options.antialiasing[2][i] then
+ return i
+ end
+ end
+ return 1
+ end
+}
+
+local function antialiasing_fname_to_name(fname)
+ for i = 1, #labels.antialiasing do
+ if fname == labels.antialiasing[i] then
+ return dd_options.antialiasing[2][i]
+ end
+ end
+ return 0
+end
+
+local function dlg_confirm_reset_formspec(data)
+ return "size[8,3]" ..
+ "label[1,1;" .. fgettext("Are you sure to reset your singleplayer world?") .. "]" ..
+ "button[1,2;2.6,0.5;dlg_reset_singleplayer_confirm;" .. fgettext("Yes") .. "]" ..
+ "button[4,2;2.8,0.5;dlg_reset_singleplayer_cancel;" .. fgettext("No") .. "]"
+end
+
+local function dlg_confirm_reset_btnhandler(this, fields, dialogdata)
+
+ if fields["dlg_reset_singleplayer_confirm"] ~= nil then
+ local worldlist = core.get_worlds()
+ local found_singleplayerworld = false
+
+ for i = 1, #worldlist do
+ if worldlist[i].name == "singleplayerworld" then
+ found_singleplayerworld = true
+ gamedata.worldindex = i
+ end
+ end
+
+ if found_singleplayerworld then
+ core.delete_world(gamedata.worldindex)
+ end
+
+ core.create_world("singleplayerworld", 1)
+ worldlist = core.get_worlds()
+ found_singleplayerworld = false
+
+ for i = 1, #worldlist do
+ if worldlist[i].name == "singleplayerworld" then
+ found_singleplayerworld = true
+ gamedata.worldindex = i
+ end
+ end
+ end
+
+ this.parent:show()
+ this:hide()
+ this:delete()
+ return true
+end
+
+local function showconfirm_reset(tabview)
+ local new_dlg = dialog_create("reset_spworld",
+ dlg_confirm_reset_formspec,
+ dlg_confirm_reset_btnhandler,
+ nil)
+ new_dlg:set_parent(tabview)
+ tabview:hide()
+ new_dlg:show()
+end
+
+local function formspec(tabview, name, tabdata)
+ local tab_string =
+ "box[0,0;3.75,4.5;#999999]" ..
+ "checkbox[0.25,0;cb_smooth_lighting;" .. fgettext("Smooth Lighting") .. ";"
+ .. dump(core.settings:get_bool("smooth_lighting")) .. "]" ..
+ "checkbox[0.25,0.5;cb_particles;" .. fgettext("Particles") .. ";"
+ .. dump(core.settings:get_bool("enable_particles")) .. "]" ..
+ "checkbox[0.25,1;cb_3d_clouds;" .. fgettext("3D Clouds") .. ";"
+ .. dump(core.settings:get_bool("enable_3d_clouds")) .. "]" ..
+ "checkbox[0.25,1.5;cb_opaque_water;" .. fgettext("Opaque Water") .. ";"
+ .. dump(core.settings:get_bool("opaque_water")) .. "]" ..
+ "checkbox[0.25,2.0;cb_connected_glass;" .. fgettext("Connected Glass") .. ";"
+ .. dump(core.settings:get_bool("connected_glass")) .. "]" ..
+ "dropdown[0.25,2.8;3.5;dd_node_highlighting;" .. dd_options.node_highlighting[1] .. ";"
+ .. getSettingIndex.NodeHighlighting() .. "]" ..
+ "dropdown[0.25,3.6;3.5;dd_leaves_style;" .. dd_options.leaves[1] .. ";"
+ .. getSettingIndex.Leaves() .. "]" ..
+ "box[4,0;3.75,4.5;#999999]" ..
+ "label[4.25,0.1;" .. fgettext("Texturing:") .. "]" ..
+ "dropdown[4.25,0.55;3.5;dd_filters;" .. dd_options.filters[1] .. ";"
+ .. getSettingIndex.Filter() .. "]" ..
+ "dropdown[4.25,1.35;3.5;dd_mipmap;" .. dd_options.mipmap[1] .. ";"
+ .. getSettingIndex.Mipmap() .. "]" ..
+ "label[4.25,2.15;" .. fgettext("Antialiasing:") .. "]" ..
+ "dropdown[4.25,2.6;3.5;dd_antialiasing;" .. dd_options.antialiasing[1] .. ";"
+ .. getSettingIndex.Antialiasing() .. "]" ..
+ "label[4.25,3.45;" .. fgettext("Screen:") .. "]" ..
+ "checkbox[4.25,3.6;cb_autosave_screensize;" .. fgettext("Autosave screen size") .. ";"
+ .. dump(core.settings:get_bool("autosave_screensize")) .. "]" ..
+ "box[8,0;3.75,4.5;#999999]" ..
+ "checkbox[8.25,0;cb_shaders;" .. fgettext("Shaders") .. ";"
+ .. dump(core.settings:get_bool("enable_shaders")) .. "]"
+
+ if PLATFORM == "Android" then
+ tab_string = tab_string ..
+ "button[8,4.75;4.1,1;btn_reset_singleplayer;"
+ .. fgettext("Reset singleplayer world") .. "]"
+ else
+ tab_string = tab_string ..
+ "button[8,4.75;4,1;btn_change_keys;"
+ .. fgettext("Change keys") .. "]"
+ end
+
+ tab_string = tab_string ..
+ "button[0,4.75;4,1;btn_advanced_settings;"
+ .. fgettext("Advanced Settings") .. "]"
+
+
+ if core.settings:get("touchscreen_threshold") ~= nil then
+ tab_string = tab_string ..
+ "label[4.3,4.1;" .. fgettext("Touchthreshold (px)") .. "]" ..
+ "dropdown[3.85,4.55;3.85;dd_touchthreshold;0,10,20,30,40,50;" ..
+ ((tonumber(core.settings:get("touchscreen_threshold")) / 10) + 1) .. "]"
+ end
+
+ if core.settings:get_bool("enable_shaders") then
+ tab_string = tab_string ..
+ "checkbox[8.25,0.5;cb_bumpmapping;" .. fgettext("Bump Mapping") .. ";"
+ .. dump(core.settings:get_bool("enable_bumpmapping")) .. "]" ..
+ "checkbox[8.25,1;cb_tonemapping;" .. fgettext("Tone Mapping") .. ";"
+ .. dump(core.settings:get_bool("tone_mapping")) .. "]" ..
+ "checkbox[8.25,1.5;cb_generate_normalmaps;" .. fgettext("Normal Mapping") .. ";"
+ .. dump(core.settings:get_bool("generate_normalmaps")) .. "]" ..
+ "checkbox[8.25,2;cb_parallax;" .. fgettext("Parallax Occlusion") .. ";"
+ .. dump(core.settings:get_bool("enable_parallax_occlusion")) .. "]" ..
+ "checkbox[8.25,2.5;cb_waving_water;" .. fgettext("Waving Water") .. ";"
+ .. dump(core.settings:get_bool("enable_waving_water")) .. "]" ..
+ "checkbox[8.25,3;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";"
+ .. dump(core.settings:get_bool("enable_waving_leaves")) .. "]" ..
+ "checkbox[8.25,3.5;cb_waving_plants;" .. fgettext("Waving Plants") .. ";"
+ .. dump(core.settings:get_bool("enable_waving_plants")) .. "]"
+ else
+ tab_string = tab_string ..
+ "tablecolumns[color;text]" ..
+ "tableoptions[background=#00000000;highlight=#00000000;border=false]" ..
+ "table[8.33,0.7;3.5,4;shaders;" ..
+ "#888888," .. fgettext("Bump Mapping") .. "," ..
+ "#888888," .. fgettext("Tone Mapping") .. "," ..
+ "#888888," .. fgettext("Normal Mapping") .. "," ..
+ "#888888," .. fgettext("Parallax Occlusion") .. "," ..
+ "#888888," .. fgettext("Waving Water") .. "," ..
+ "#888888," .. fgettext("Waving Leaves") .. "," ..
+ "#888888," .. fgettext("Waving Plants") .. "," ..
+ ";1]"
+ end
+
+ return tab_string
+end
+
+--------------------------------------------------------------------------------
+local function handle_settings_buttons(this, fields, tabname, tabdata)
+
+ if fields["btn_advanced_settings"] ~= nil then
+ local adv_settings_dlg = create_adv_settings_dlg()
+ adv_settings_dlg:set_parent(this)
+ this:hide()
+ adv_settings_dlg:show()
+ --mm_texture.update("singleplayer", current_game())
+ return true
+ end
+ if fields["cb_smooth_lighting"] then
+ core.settings:set("smooth_lighting", fields["cb_smooth_lighting"])
+ return true
+ end
+ if fields["cb_particles"] then
+ core.settings:set("enable_particles", fields["cb_particles"])
+ return true
+ end
+ if fields["cb_3d_clouds"] then
+ core.settings:set("enable_3d_clouds", fields["cb_3d_clouds"])
+ return true
+ end
+ if fields["cb_opaque_water"] then
+ core.settings:set("opaque_water", fields["cb_opaque_water"])
+ return true
+ end
+ if fields["cb_connected_glass"] then
+ core.settings:set("connected_glass", fields["cb_connected_glass"])
+ return true
+ end
+ if fields["cb_autosave_screensize"] then
+ core.settings:set("autosave_screensize", fields["cb_autosave_screensize"])
+ return true
+ end
+ if fields["cb_shaders"] then
+ if (core.settings:get("video_driver") == "direct3d8" or
+ core.settings:get("video_driver") == "direct3d9") then
+ core.settings:set("enable_shaders", "false")
+ gamedata.errormessage = fgettext("To enable shaders the OpenGL driver needs to be used.")
+ else
+ core.settings:set("enable_shaders", fields["cb_shaders"])
+ end
+ return true
+ end
+ if fields["cb_bumpmapping"] then
+ core.settings:set("enable_bumpmapping", fields["cb_bumpmapping"])
+ return true
+ end
+ if fields["cb_tonemapping"] then
+ core.settings:set("tone_mapping", fields["cb_tonemapping"])
+ return true
+ end
+ if fields["cb_generate_normalmaps"] then
+ core.settings:set("generate_normalmaps", fields["cb_generate_normalmaps"])
+ return true
+ end
+ if fields["cb_parallax"] then
+ core.settings:set("enable_parallax_occlusion", fields["cb_parallax"])
+ return true
+ end
+ if fields["cb_waving_water"] then
+ core.settings:set("enable_waving_water", fields["cb_waving_water"])
+ return true
+ end
+ if fields["cb_waving_leaves"] then
+ core.settings:set("enable_waving_leaves", fields["cb_waving_leaves"])
+ end
+ if fields["cb_waving_plants"] then
+ core.settings:set("enable_waving_plants", fields["cb_waving_plants"])
+ return true
+ end
+ if fields["btn_change_keys"] then
+ core.show_keys_menu()
+ return true
+ end
+ if fields["cb_touchscreen_target"] then
+ core.settings:set("touchtarget", fields["cb_touchscreen_target"])
+ return true
+ end
+ if fields["btn_reset_singleplayer"] then
+ showconfirm_reset(this)
+ return true
+ end
+
+ --Note dropdowns have to be handled LAST!
+ local ddhandled = false
+
+ for i = 1, #labels.leaves do
+ if fields["dd_leaves_style"] == labels.leaves[i] then
+ core.settings:set("leaves_style", dd_options.leaves[2][i])
+ ddhandled = true
+ end
+ end
+ for i = 1, #labels.node_highlighting do
+ if fields["dd_node_highlighting"] == labels.node_highlighting[i] then
+ core.settings:set("node_highlighting", dd_options.node_highlighting[2][i])
+ ddhandled = true
+ end
+ end
+ if fields["dd_filters"] == labels.filters[1] then
+ core.settings:set("bilinear_filter", "false")
+ core.settings:set("trilinear_filter", "false")
+ ddhandled = true
+ elseif fields["dd_filters"] == labels.filters[2] then
+ core.settings:set("bilinear_filter", "true")
+ core.settings:set("trilinear_filter", "false")
+ ddhandled = true
+ elseif fields["dd_filters"] == labels.filters[3] then
+ core.settings:set("bilinear_filter", "false")
+ core.settings:set("trilinear_filter", "true")
+ ddhandled = true
+ end
+ if fields["dd_mipmap"] == labels.mipmap[1] then
+ core.settings:set("mip_map", "false")
+ core.settings:set("anisotropic_filter", "false")
+ ddhandled = true
+ elseif fields["dd_mipmap"] == labels.mipmap[2] then
+ core.settings:set("mip_map", "true")
+ core.settings:set("anisotropic_filter", "false")
+ ddhandled = true
+ elseif fields["dd_mipmap"] == labels.mipmap[3] then
+ core.settings:set("mip_map", "true")
+ core.settings:set("anisotropic_filter", "true")
+ ddhandled = true
+ end
+ if fields["dd_antialiasing"] then
+ core.settings:set("fsaa",
+ antialiasing_fname_to_name(fields["dd_antialiasing"]))
+ ddhandled = true
+ end
+ if fields["dd_touchthreshold"] then
+ core.settings:set("touchscreen_threshold", fields["dd_touchthreshold"])
+ ddhandled = true
+ end
+
+ return ddhandled
+end
+
+return {
+ name = "settings",
+ caption = fgettext("Settings"),
+ cbf_formspec = formspec,
+ cbf_button_handler = handle_settings_buttons
+}
diff --git a/builtin/mainmenu/tab_simple_main.lua b/builtin/mainmenu/tab_simple_main.lua
new file mode 100644
index 000000000..de4ae1751
--- /dev/null
+++ b/builtin/mainmenu/tab_simple_main.lua
@@ -0,0 +1,220 @@
+--Minetest
+--Copyright (C) 2013 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+--------------------------------------------------------------------------------
+local function get_formspec(tabview, name, tabdata)
+ -- Update the cached supported proto info,
+ -- it may have changed after a change by the settings menu.
+ common_update_cached_supp_proto()
+ local fav_selected = menudata.favorites[tabdata.fav_selected]
+
+ local retval =
+ "label[9.5,0;".. fgettext("Name / Password") .. "]" ..
+ "field[0.25,3.35;5.5,0.5;te_address;;" ..
+ core.formspec_escape(core.settings:get("address")) .."]" ..
+ "field[5.75,3.35;2.25,0.5;te_port;;" ..
+ core.formspec_escape(core.settings:get("remote_port")) .."]" ..
+ "button[10,2.6;2,1.5;btn_mp_connect;".. fgettext("Connect") .. "]" ..
+ "field[9.8,1;2.6,0.5;te_name;;" ..
+ core.formspec_escape(core.settings:get("name")) .."]" ..
+ "pwdfield[9.8,2;2.6,0.5;te_pwd;]"
+
+
+ if tabdata.fav_selected and fav_selected then
+ if gamedata.fav then
+ retval = retval .. "button[7.7,2.6;2.3,1.5;btn_delete_favorite;" ..
+ fgettext("Del. Favorite") .. "]"
+ end
+ end
+
+ retval = retval .. "tablecolumns[" ..
+ image_column(fgettext("Favorite"), "favorite") .. ";" ..
+ image_column(fgettext("Ping"), "") .. ",padding=0.25;" ..
+ "color,span=3;" ..
+ "text,align=right;" .. -- clients
+ "text,align=center,padding=0.25;" .. -- "/"
+ "text,align=right,padding=0.25;" .. -- clients_max
+ image_column(fgettext("Creative mode"), "creative") .. ",padding=1;" ..
+ image_column(fgettext("Damage enabled"), "damage") .. ",padding=0.25;" ..
+ image_column(fgettext("PvP enabled"), "pvp") .. ",padding=0.25;" ..
+ "color,span=1;" ..
+ "text,padding=1]" .. -- name
+ "table[-0.05,0;9.2,2.75;favourites;"
+
+ if #menudata.favorites > 0 then
+ local favs = core.get_favorites("local")
+ if #favs > 0 then
+ for i = 1, #favs do
+ for j = 1, #menudata.favorites do
+ if menudata.favorites[j].address == favs[i].address and
+ menudata.favorites[j].port == favs[i].port then
+ table.insert(menudata.favorites, i,
+ table.remove(menudata.favorites, j))
+ end
+ end
+ if favs[i].address ~= menudata.favorites[i].address then
+ table.insert(menudata.favorites, i, favs[i])
+ end
+ end
+ end
+ retval = retval .. render_serverlist_row(menudata.favorites[1], (#favs > 0))
+ for i = 2, #menudata.favorites do
+ retval = retval .. "," .. render_serverlist_row(menudata.favorites[i], (i <= #favs))
+ end
+ end
+
+ if tabdata.fav_selected then
+ retval = retval .. ";" .. tabdata.fav_selected .. "]"
+ else
+ retval = retval .. ";0]"
+ end
+
+ -- separator
+ retval = retval .. "box[-0.28,3.75;12.4,0.1;#FFFFFF]"
+
+ -- checkboxes
+ retval = retval ..
+ "checkbox[8.0,3.9;cb_creative;".. fgettext("Creative Mode") .. ";" ..
+ dump(core.settings:get_bool("creative_mode")) .. "]"..
+ "checkbox[8.0,4.4;cb_damage;".. fgettext("Enable Damage") .. ";" ..
+ dump(core.settings:get_bool("enable_damage")) .. "]"
+ -- buttons
+ retval = retval ..
+ "button[0,3.7;8,1.5;btn_start_singleplayer;" .. fgettext("Start Singleplayer") .. "]" ..
+ "button[0,4.5;8,1.5;btn_config_sp_world;" .. fgettext("Config mods") .. "]"
+
+ return retval
+end
+
+--------------------------------------------------------------------------------
+local function main_button_handler(tabview, fields, name, tabdata)
+ if fields.btn_start_singleplayer then
+ gamedata.selected_world = gamedata.worldindex
+ gamedata.singleplayer = true
+ core.start()
+ return true
+ end
+
+ if fields.favourites then
+ local event = core.explode_table_event(fields.favourites)
+ if event.type == "CHG" then
+ if event.row <= #menudata.favorites then
+ gamedata.fav = false
+ local favs = core.get_favorites("local")
+ local fav = menudata.favorites[event.row]
+ local address = fav.address
+ local port = fav.port
+ gamedata.serverdescription = fav.description
+
+ for i = 1, #favs do
+ if fav.address == favs[i].address and
+ fav.port == favs[i].port then
+ gamedata.fav = true
+ end
+ end
+
+ if address and port then
+ core.settings:set("address", address)
+ core.settings:set("remote_port", port)
+ end
+ tabdata.fav_selected = event.row
+ end
+ return true
+ end
+ end
+
+ if fields.btn_delete_favorite then
+ local current_favourite = core.get_table_index("favourites")
+ if not current_favourite then return end
+
+ core.delete_favorite(current_favourite)
+ asyncOnlineFavourites()
+ tabdata.fav_selected = nil
+
+ core.settings:set("address", "")
+ core.settings:set("remote_port", "30000")
+ return true
+ end
+
+ if fields.cb_creative then
+ core.settings:set("creative_mode", fields.cb_creative)
+ return true
+ end
+
+ if fields.cb_damage then
+ core.settings:set("enable_damage", fields.cb_damage)
+ return true
+ end
+
+ if fields.btn_mp_connect or fields.key_enter then
+ gamedata.playername = fields.te_name
+ gamedata.password = fields.te_pwd
+ gamedata.address = fields.te_address
+ gamedata.port = fields.te_port
+ local fav_idx = core.get_textlist_index("favourites")
+
+ if fav_idx and fav_idx <= #menudata.favorites and
+ menudata.favorites[fav_idx].address == fields.te_address and
+ menudata.favorites[fav_idx].port == fields.te_port then
+ local fav = menudata.favorites[fav_idx]
+ gamedata.servername = fav.name
+ gamedata.serverdescription = fav.description
+
+ if menudata.favorites_is_public and
+ not is_server_protocol_compat_or_error(
+ fav.proto_min, fav.proto_max) then
+ return true
+ end
+ else
+ gamedata.servername = ""
+ gamedata.serverdescription = ""
+ end
+
+ gamedata.selected_world = 0
+
+ core.settings:set("address", fields.te_address)
+ core.settings:set("remote_port", fields.te_port)
+
+ core.start()
+ return true
+ end
+
+ if fields.btn_config_sp_world then
+ local configdialog = create_configure_world_dlg(1)
+ if configdialog then
+ configdialog:set_parent(tabview)
+ tabview:hide()
+ configdialog:show()
+ end
+ return true
+ end
+end
+
+--------------------------------------------------------------------------------
+local function on_activate(type,old_tab,new_tab)
+ if type == "LEAVE" then return end
+ asyncOnlineFavourites()
+end
+
+--------------------------------------------------------------------------------
+return {
+ name = "main",
+ caption = fgettext("Main"),
+ cbf_formspec = get_formspec,
+ cbf_button_handler = main_button_handler,
+ on_change = on_activate
+}
diff --git a/builtin/mainmenu/tab_texturepacks.lua b/builtin/mainmenu/tab_texturepacks.lua
new file mode 100644
index 000000000..2957481cf
--- /dev/null
+++ b/builtin/mainmenu/tab_texturepacks.lua
@@ -0,0 +1,132 @@
+--Minetest
+--Copyright (C) 2014 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+--------------------------------------------------------------------------------
+local function filter_texture_pack_list(list)
+ local retval = {}
+
+ for _, item in ipairs(list) do
+ if item ~= "base" then
+ retval[#retval + 1] = item
+ end
+ end
+
+ table.sort(retval)
+ table.insert(retval, 1, fgettext("None"))
+
+ return retval
+end
+
+--------------------------------------------------------------------------------
+local function render_texture_pack_list(list)
+ local retval = ""
+
+ for i, v in ipairs(list) do
+ if v:sub(1, 1) ~= "." then
+ if retval ~= "" then
+ retval = retval .. ","
+ end
+
+ retval = retval .. core.formspec_escape(v)
+ end
+ end
+
+ return retval
+end
+
+--------------------------------------------------------------------------------
+local function get_formspec(tabview, name, tabdata)
+
+ local retval = "label[4,-0.25;" .. fgettext("Select texture pack:") .. "]" ..
+ "textlist[4,0.25;7.5,5.0;TPs;"
+
+ local current_texture_path = core.settings:get("texture_path")
+ local list = filter_texture_pack_list(core.get_dir_list(core.get_texturepath(), true))
+ local index = tonumber(core.settings:get("mainmenu_last_selected_TP"))
+
+ if not index then index = 1 end
+
+ if current_texture_path == "" then
+ retval = retval ..
+ render_texture_pack_list(list) ..
+ ";" .. index .. "]"
+ return retval
+ end
+
+ local infofile = current_texture_path .. DIR_DELIM .. "description.txt"
+ -- This adds backwards compatibility for old texture pack description files named
+ -- "info.txt", and should be removed once all such texture packs have been updated
+ if not file_exists(infofile) then
+ infofile = current_texture_path .. DIR_DELIM .. "info.txt"
+ if file_exists(infofile) then
+ core.log("deprecated", "info.txt is deprecated. description.txt should be used instead.")
+ end
+ end
+
+ local infotext = ""
+ local f = io.open(infofile, "r")
+ if not f then
+ infotext = fgettext("No information available")
+ else
+ infotext = f:read("*all")
+ f:close()
+ end
+
+ local screenfile = current_texture_path .. DIR_DELIM .. "screenshot.png"
+ local no_screenshot
+ if not file_exists(screenfile) then
+ screenfile = nil
+ no_screenshot = defaulttexturedir .. "no_screenshot.png"
+ end
+
+ return retval ..
+ render_texture_pack_list(list) ..
+ ";" .. index .. "]" ..
+ "image[0.25,0.25;4.05,2.7;" .. core.formspec_escape(screenfile or no_screenshot) .. "]" ..
+ "textarea[0.6,2.85;3.7,1.5;;" .. core.formspec_escape(infotext or "") .. ";]"
+end
+
+--------------------------------------------------------------------------------
+local function main_button_handler(tabview, fields, name, tabdata)
+ if fields["TPs"] then
+ local event = core.explode_textlist_event(fields["TPs"])
+ if event.type == "CHG" or event.type == "DCL" then
+ local index = core.get_textlist_index("TPs")
+ core.settings:set("mainmenu_last_selected_TP", index)
+ local list = filter_texture_pack_list(core.get_dir_list(core.get_texturepath(), true))
+ local current_index = core.get_textlist_index("TPs")
+ if current_index and #list >= current_index then
+ local new_path = core.get_texturepath() .. DIR_DELIM .. list[current_index]
+ if list[current_index] == fgettext("None") then
+ new_path = ""
+ end
+ core.settings:set("texture_path", new_path)
+ end
+ end
+ return true
+ end
+ return false
+end
+
+--------------------------------------------------------------------------------
+return {
+ name = "texturepacks",
+ caption = fgettext("Texturepacks"),
+ cbf_formspec = get_formspec,
+ cbf_button_handler = main_button_handler,
+ on_change = nil
+}
diff --git a/builtin/mainmenu/textures.lua b/builtin/mainmenu/textures.lua
new file mode 100644
index 000000000..9ba4ade7e
--- /dev/null
+++ b/builtin/mainmenu/textures.lua
@@ -0,0 +1,185 @@
+--Minetest
+--Copyright (C) 2013 sapier
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+mm_texture = {}
+
+--------------------------------------------------------------------------------
+function mm_texture.init()
+ mm_texture.defaulttexturedir = core.get_texturepath() .. DIR_DELIM .. "base" ..
+ DIR_DELIM .. "pack" .. DIR_DELIM
+ mm_texture.basetexturedir = mm_texture.defaulttexturedir
+
+ mm_texture.texturepack = core.settings:get("texture_path")
+
+ mm_texture.gameid = nil
+end
+
+--------------------------------------------------------------------------------
+function mm_texture.update(tab,gamedetails)
+ if tab ~= "singleplayer" then
+ mm_texture.reset()
+ return
+ end
+
+ if gamedetails == nil then
+ return
+ end
+
+ mm_texture.update_game(gamedetails)
+end
+
+--------------------------------------------------------------------------------
+function mm_texture.reset()
+ mm_texture.gameid = nil
+ local have_bg = false
+ local have_overlay = mm_texture.set_generic("overlay")
+
+ if not have_overlay then
+ have_bg = mm_texture.set_generic("background")
+ end
+
+ mm_texture.clear("header")
+ mm_texture.clear("footer")
+ core.set_clouds(false)
+
+ mm_texture.set_generic("footer")
+ mm_texture.set_generic("header")
+
+ if not have_bg then
+ if core.settings:get_bool("menu_clouds") then
+ core.set_clouds(true)
+ else
+ mm_texture.set_dirt_bg()
+ end
+ end
+end
+
+--------------------------------------------------------------------------------
+function mm_texture.update_game(gamedetails)
+ if mm_texture.gameid == gamedetails.id then
+ return
+ end
+
+ local have_bg = false
+ local have_overlay = mm_texture.set_game("overlay",gamedetails)
+
+ if not have_overlay then
+ have_bg = mm_texture.set_game("background",gamedetails)
+ end
+
+ mm_texture.clear("header")
+ mm_texture.clear("footer")
+ core.set_clouds(false)
+
+ if not have_bg then
+
+ if core.settings:get_bool("menu_clouds") then
+ core.set_clouds(true)
+ else
+ mm_texture.set_dirt_bg()
+ end
+ end
+
+ mm_texture.set_game("footer",gamedetails)
+ mm_texture.set_game("header",gamedetails)
+
+ mm_texture.gameid = gamedetails.id
+end
+
+--------------------------------------------------------------------------------
+function mm_texture.clear(identifier)
+ core.set_background(identifier,"")
+end
+
+--------------------------------------------------------------------------------
+function mm_texture.set_generic(identifier)
+ --try texture pack first
+ if mm_texture.texturepack ~= nil then
+ local path = mm_texture.texturepack .. DIR_DELIM .."menu_" ..
+ identifier .. ".png"
+ if core.set_background(identifier,path) then
+ return true
+ end
+ end
+
+ if mm_texture.defaulttexturedir ~= nil then
+ local path = mm_texture.defaulttexturedir .. DIR_DELIM .."menu_" ..
+ identifier .. ".png"
+ if core.set_background(identifier,path) then
+ return true
+ end
+ end
+
+ return false
+end
+
+--------------------------------------------------------------------------------
+function mm_texture.set_game(identifier, gamedetails)
+
+ if gamedetails == nil then
+ return false
+ end
+
+ if mm_texture.texturepack ~= nil then
+ local path = mm_texture.texturepack .. DIR_DELIM ..
+ gamedetails.id .. "_menu_" .. identifier .. ".png"
+ if core.set_background(identifier, path) then
+ return true
+ end
+ end
+
+ -- Find out how many randomized textures the subgame provides
+ local n = 0
+ local filename
+ local menu_files = core.get_dir_list(gamedetails.path .. DIR_DELIM .. "menu", false)
+ for i = 1, #menu_files do
+ filename = identifier .. "." .. i .. ".png"
+ if table.indexof(menu_files, filename) == -1 then
+ n = i - 1
+ break
+ end
+ end
+ -- Select random texture, 0 means standard texture
+ n = math.random(0, n)
+ if n == 0 then
+ filename = identifier .. ".png"
+ else
+ filename = identifier .. "." .. n .. ".png"
+ end
+
+ local path = gamedetails.path .. DIR_DELIM .. "menu" ..
+ DIR_DELIM .. filename
+ if core.set_background(identifier, path) then
+ return true
+ end
+
+ return false
+end
+
+function mm_texture.set_dirt_bg()
+ if mm_texture.texturepack ~= nil then
+ local path = mm_texture.texturepack .. DIR_DELIM .."default_dirt.png"
+ if core.set_background("background", path, true, 128) then
+ return true
+ end
+ end
+
+ -- Use universal fallback texture in textures/base/pack
+ local minimalpath = defaulttexturedir .. "menu_bg.png"
+ core.set_background("background", minimalpath, true, 128)
+end
diff --git a/builtin/profiler/init.lua b/builtin/profiler/init.lua
new file mode 100644
index 000000000..874950364
--- /dev/null
+++ b/builtin/profiler/init.lua
@@ -0,0 +1,80 @@
+--Minetest
+--Copyright (C) 2016 T4im
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+local function get_bool_default(name, default)
+ local val = core.settings:get_bool(name)
+ if val == nil then
+ return default
+ end
+ return val
+end
+
+local profiler_path = core.get_builtin_path()..DIR_DELIM.."profiler"..DIR_DELIM
+local profiler = {}
+local sampler = assert(loadfile(profiler_path .. "sampling.lua"))(profiler)
+local instrumentation = assert(loadfile(profiler_path .. "instrumentation.lua"))(profiler, sampler, get_bool_default)
+local reporter = dofile(profiler_path .. "reporter.lua")
+profiler.instrument = instrumentation.instrument
+
+---
+-- Delayed registration of the /profiler chat command
+-- Is called later, after `core.register_chatcommand` was set up.
+--
+function profiler.init_chatcommand()
+ local instrument_profiler = get_bool_default("instrument.profiler", false)
+ if instrument_profiler then
+ instrumentation.init_chatcommand()
+ end
+
+ local param_usage = "print [filter] | dump [filter] | save [format [filter]] | reset"
+ core.register_chatcommand("profiler", {
+ description = "handle the profiler and profiling data",
+ params = param_usage,
+ privs = { server=true },
+ func = function(name, param)
+ local command, arg0 = string.match(param, "([^ ]+) ?(.*)")
+ local args = arg0 and string.split(arg0, " ")
+
+ if command == "dump" then
+ core.log("action", reporter.print(sampler.profile, arg0))
+ return true, "Statistics written to action log"
+ elseif command == "print" then
+ return true, reporter.print(sampler.profile, arg0)
+ elseif command == "save" then
+ return reporter.save(sampler.profile, args[1] or "txt", args[2])
+ elseif command == "reset" then
+ sampler.reset()
+ return true, "Statistics were reset"
+ end
+
+ return false, string.format(
+ "Usage: %s\n" ..
+ "Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).",
+ param_usage
+ )
+ end
+ })
+
+ if not instrument_profiler then
+ instrumentation.init_chatcommand()
+ end
+end
+
+sampler.init()
+instrumentation.init()
+
+return profiler
diff --git a/builtin/profiler/instrumentation.lua b/builtin/profiler/instrumentation.lua
new file mode 100644
index 000000000..7c21859d3
--- /dev/null
+++ b/builtin/profiler/instrumentation.lua
@@ -0,0 +1,232 @@
+--Minetest
+--Copyright (C) 2016 T4im
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+local format, pairs, type = string.format, pairs, type
+local core, get_current_modname = core, core.get_current_modname
+local profiler, sampler, get_bool_default = ...
+
+local instrument_builtin = get_bool_default("instrument.builtin", false)
+
+local register_functions = {
+ register_globalstep = 0,
+ register_playerevent = 0,
+ register_on_placenode = 0,
+ register_on_dignode = 0,
+ register_on_punchnode = 0,
+ register_on_generated = 0,
+ register_on_newplayer = 0,
+ register_on_dieplayer = 0,
+ register_on_respawnplayer = 0,
+ register_on_prejoinplayer = 0,
+ register_on_joinplayer = 0,
+ register_on_leaveplayer = 0,
+ register_on_cheat = 0,
+ register_on_chat_message = 0,
+ register_on_player_receive_fields = 0,
+ register_on_craft = 0,
+ register_craft_predict = 0,
+ register_on_protection_violation = 0,
+ register_on_item_eat = 0,
+ register_on_punchplayer = 0,
+ register_on_player_hpchange = 0,
+}
+
+---
+-- Create an unique instrument name.
+-- Generate a missing label with a running index number.
+--
+local counts = {}
+local function generate_name(def)
+ local class, label, func_name = def.class, def.label, def.func_name
+ if label then
+ if class or func_name then
+ return format("%s '%s' %s", class or "", label, func_name or ""):trim()
+ end
+ return format("%s", label):trim()
+ elseif label == false then
+ return format("%s", class or func_name):trim()
+ end
+
+ local index_id = def.mod .. (class or func_name)
+ local index = counts[index_id] or 1
+ counts[index_id] = index + 1
+ return format("%s[%d] %s", class or func_name, index, class and func_name or ""):trim()
+end
+
+---
+-- Keep `measure` and the closure in `instrument` lean, as these, and their
+-- directly called functions are the overhead that is caused by instrumentation.
+--
+local time, log = core.get_us_time, sampler.log
+local function measure(modname, instrument_name, start, ...)
+ log(modname, instrument_name, time() - start)
+ return ...
+end
+--- Automatically instrument a function to measure and log to the sampler.
+-- def = {
+-- mod = "",
+-- class = "",
+-- func_name = "",
+-- -- if nil, will create a label based on registration order
+-- label = "" | false,
+-- }
+local function instrument(def)
+ if not def or not def.func then
+ return
+ end
+ def.mod = def.mod or get_current_modname()
+ local modname = def.mod
+ local instrument_name = generate_name(def)
+ local func = def.func
+
+ if not instrument_builtin and modname == "*builtin*" then
+ return func
+ end
+
+ return function(...)
+ -- This tail-call allows passing all return values of `func`
+ -- also called https://en.wikipedia.org/wiki/Continuation_passing_style
+ -- Compared to table creation and unpacking it won't lose `nil` returns
+ -- and is expected to be faster
+ -- `measure` will be executed after time() and func(...)
+ return measure(modname, instrument_name, time(), func(...))
+ end
+end
+
+local function can_be_called(func)
+ -- It has to be a function or callable table
+ return type(func) == "function" or
+ ((type(func) == "table" or type(func) == "userdata") and
+ getmetatable(func) and getmetatable(func).__call)
+end
+
+local function assert_can_be_called(func, func_name, level)
+ if not can_be_called(func) then
+ -- Then throw an *helpful* error, by pointing on our caller instead of us.
+ error(format("Invalid argument to %s. Expected function-like type instead of '%s'.", func_name, type(func)), level + 1)
+ end
+end
+
+---
+-- Wraps a registration function `func` in such a way,
+-- that it will automatically instrument any callback function passed as first argument.
+--
+local function instrument_register(func, func_name)
+ local register_name = func_name:gsub("^register_", "", 1)
+ return function(callback, ...)
+ assert_can_be_called(callback, func_name, 2)
+ register_functions[func_name] = register_functions[func_name] + 1
+ return func(instrument {
+ func = callback,
+ func_name = register_name
+ }, ...)
+ end
+end
+
+local function init_chatcommand()
+ if get_bool_default("instrument.chatcommand", true) then
+ local orig_register_chatcommand = core.register_chatcommand
+ core.register_chatcommand = function(cmd, def)
+ def.func = instrument {
+ func = def.func,
+ label = "/" .. cmd,
+ }
+ orig_register_chatcommand(cmd, def)
+ end
+ end
+end
+
+---
+-- Start instrumenting selected functions
+--
+local function init()
+ if get_bool_default("instrument.entity", true) then
+ -- Explicitly declare entity api-methods.
+ -- Simple iteration would ignore lookup via __index.
+ local entity_instrumentation = {
+ "on_activate",
+ "on_step",
+ "on_punch",
+ "rightclick",
+ "get_staticdata",
+ }
+ -- Wrap register_entity() to instrument them on registration.
+ local orig_register_entity = core.register_entity
+ core.register_entity = function(name, prototype)
+ local modname = get_current_modname()
+ for _, func_name in pairs(entity_instrumentation) do
+ prototype[func_name] = instrument {
+ func = prototype[func_name],
+ mod = modname,
+ func_name = func_name,
+ label = prototype.label,
+ }
+ end
+ orig_register_entity(name,prototype)
+ end
+ end
+
+ if get_bool_default("instrument.abm", true) then
+ -- Wrap register_abm() to automatically instrument abms.
+ local orig_register_abm = core.register_abm
+ core.register_abm = function(spec)
+ spec.action = instrument {
+ func = spec.action,
+ class = "ABM",
+ label = spec.label,
+ }
+ orig_register_abm(spec)
+ end
+ end
+
+ if get_bool_default("instrument.lbm", true) then
+ -- Wrap register_lbm() to automatically instrument lbms.
+ local orig_register_lbm = core.register_lbm
+ core.register_lbm = function(spec)
+ spec.action = instrument {
+ func = spec.action,
+ class = "LBM",
+ label = spec.label or spec.name,
+ }
+ orig_register_lbm(spec)
+ end
+ end
+
+ if get_bool_default("instrument.global_callback", true) then
+ for func_name, _ in pairs(register_functions) do
+ core[func_name] = instrument_register(core[func_name], func_name)
+ end
+ end
+
+ if get_bool_default("instrument.profiler", false) then
+ -- Measure overhead of instrumentation, but keep it down for functions
+ -- So keep the `return` for better optimization.
+ profiler.empty_instrument = instrument {
+ func = function() return end,
+ mod = "*profiler*",
+ class = "Instrumentation overhead",
+ label = false,
+ }
+ end
+end
+
+return {
+ register_functions = register_functions,
+ instrument = instrument,
+ init = init,
+ init_chatcommand = init_chatcommand,
+}
diff --git a/builtin/profiler/reporter.lua b/builtin/profiler/reporter.lua
new file mode 100644
index 000000000..fed47a36b
--- /dev/null
+++ b/builtin/profiler/reporter.lua
@@ -0,0 +1,277 @@
+--Minetest
+--Copyright (C) 2016 T4im
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+local DIR_DELIM, LINE_DELIM = DIR_DELIM, "\n"
+local table, unpack, string, pairs, io, os = table, unpack, string, pairs, io, os
+local rep, sprintf, tonumber = string.rep, string.format, tonumber
+local core, settings = core, core.settings
+local reporter = {}
+
+---
+-- Shorten a string. End on an ellipsis if shortened.
+--
+local function shorten(str, length)
+ if str and str:len() > length then
+ return "..." .. str:sub(-(length-3))
+ end
+ return str
+end
+
+local function filter_matches(filter, text)
+ return not filter or string.match(text, filter)
+end
+
+local function format_number(number, fmt)
+ number = tonumber(number)
+ if not number then
+ return "N/A"
+ end
+ return sprintf(fmt or "%d", number)
+end
+
+local Formatter = {
+ new = function(self, object)
+ object = object or {}
+ object.out = {} -- output buffer
+ self.__index = self
+ return setmetatable(object, self)
+ end,
+ __tostring = function (self)
+ return table.concat(self.out, LINE_DELIM)
+ end,
+ print = function(self, text, ...)
+ if (...) then
+ text = sprintf(text, ...)
+ end
+
+ if text then
+ -- Avoid format unicode issues.
+ text = text:gsub("Ms", "µs")
+ end
+
+ table.insert(self.out, text or LINE_DELIM)
+ end,
+ flush = function(self)
+ table.insert(self.out, LINE_DELIM)
+ local text = table.concat(self.out, LINE_DELIM)
+ self.out = {}
+ return text
+ end
+}
+
+local widths = { 55, 9, 9, 9, 5, 5, 5 }
+local txt_row_format = sprintf(" %%-%ds | %%%ds | %%%ds | %%%ds | %%%ds | %%%ds | %%%ds", unpack(widths))
+
+local HR = {}
+for i=1, #widths do
+ HR[i]= rep("-", widths[i])
+end
+-- ' | ' should break less with github than '-+-', when people are pasting there
+HR = sprintf("-%s-", table.concat(HR, " | "))
+
+local TxtFormatter = Formatter:new {
+ format_row = function(self, modname, instrument_name, statistics)
+ local label
+ if instrument_name then
+ label = shorten(instrument_name, widths[1] - 5)
+ label = sprintf(" - %s %s", label, rep(".", widths[1] - 5 - label:len()))
+ else -- Print mod_stats
+ label = shorten(modname, widths[1] - 2) .. ":"
+ end
+
+ self:print(txt_row_format, label,
+ format_number(statistics.time_min),
+ format_number(statistics.time_max),
+ format_number(statistics:get_time_avg()),
+ format_number(statistics.part_min, "%.1f"),
+ format_number(statistics.part_max, "%.1f"),
+ format_number(statistics:get_part_avg(), "%.1f")
+ )
+ end,
+ format = function(self, filter)
+ local profile = self.profile
+ self:print("Values below show absolute/relative times spend per server step by the instrumented function.")
+ self:print("A total of %d samples were taken", profile.stats_total.samples)
+
+ if filter then
+ self:print("The output is limited to '%s'", filter)
+ end
+
+ self:print()
+ self:print(
+ txt_row_format,
+ "instrumentation", "min Ms", "max Ms", "avg Ms", "min %", "max %", "avg %"
+ )
+ self:print(HR)
+ for modname,mod_stats in pairs(profile.stats) do
+ if filter_matches(filter, modname) then
+ self:format_row(modname, nil, mod_stats)
+
+ if mod_stats.instruments ~= nil then
+ for instrument_name, instrument_stats in pairs(mod_stats.instruments) do
+ self:format_row(nil, instrument_name, instrument_stats)
+ end
+ end
+ end
+ end
+ self:print(HR)
+ if not filter then
+ self:format_row("total", nil, profile.stats_total)
+ end
+ end
+}
+
+local CsvFormatter = Formatter:new {
+ format_row = function(self, modname, instrument_name, statistics)
+ self:print(
+ "%q,%q,%d,%d,%d,%d,%d,%f,%f,%f",
+ modname, instrument_name,
+ statistics.samples,
+ statistics.time_min,
+ statistics.time_max,
+ statistics:get_time_avg(),
+ statistics.time_all,
+ statistics.part_min,
+ statistics.part_max,
+ statistics:get_part_avg()
+ )
+ end,
+ format = function(self, filter)
+ self:print(
+ "%q,%q,%q,%q,%q,%q,%q,%q,%q,%q",
+ "modname", "instrumentation",
+ "samples",
+ "time min µs",
+ "time max µs",
+ "time avg µs",
+ "time all µs",
+ "part min %",
+ "part max %",
+ "part avg %"
+ )
+ for modname, mod_stats in pairs(self.profile.stats) do
+ if filter_matches(filter, modname) then
+ self:format_row(modname, "*", mod_stats)
+
+ if mod_stats.instruments ~= nil then
+ for instrument_name, instrument_stats in pairs(mod_stats.instruments) do
+ self:format_row(modname, instrument_name, instrument_stats)
+ end
+ end
+ end
+ end
+ end
+}
+
+local function format_statistics(profile, format, filter)
+ local formatter
+ if format == "csv" then
+ formatter = CsvFormatter:new {
+ profile = profile
+ }
+ else
+ formatter = TxtFormatter:new {
+ profile = profile
+ }
+ end
+ formatter:format(filter)
+ return formatter:flush()
+end
+
+---
+-- Format the profile ready for display and
+-- @return string to be printed to the console
+--
+function reporter.print(profile, filter)
+ if filter == "" then filter = nil end
+ return format_statistics(profile, "txt", filter)
+end
+
+---
+-- Serialize the profile data and
+-- @return serialized data to be saved to a file
+--
+local function serialize_profile(profile, format, filter)
+ if format == "lua" or format == "json" or format == "json_pretty" then
+ local stats = filter and {} or profile.stats
+ if filter then
+ for modname, mod_stats in pairs(profile.stats) do
+ if filter_matches(filter, modname) then
+ stats[modname] = mod_stats
+ end
+ end
+ end
+ if format == "lua" then
+ return core.serialize(stats)
+ elseif format == "json" then
+ return core.write_json(stats)
+ elseif format == "json_pretty" then
+ return core.write_json(stats, true)
+ end
+ end
+ -- Fall back to textual formats.
+ return format_statistics(profile, format, filter)
+end
+
+local worldpath = core.get_worldpath()
+local function get_save_path(format, filter)
+ local report_path = settings:get("profiler.report_path") or ""
+ if report_path ~= "" then
+ core.mkdir(sprintf("%s%s%s", worldpath, DIR_DELIM, report_path))
+ end
+ return (sprintf(
+ "%s/%s/profile-%s%s.%s",
+ worldpath,
+ report_path,
+ os.date("%Y%m%dT%H%M%S"),
+ filter and ("-" .. filter) or "",
+ format
+ ):gsub("[/\\]+", DIR_DELIM))-- Clean up delims
+end
+
+---
+-- Save the profile to the world path.
+-- @return success, log message
+--
+function reporter.save(profile, format, filter)
+ if not format or format == "" then
+ format = settings:get("profiler.default_report_format") or "txt"
+ end
+ if filter == "" then
+ filter = nil
+ end
+
+ local path = get_save_path(format, filter)
+
+ local output, io_err = io.open(path, "w")
+ if not output then
+ return false, "Saving of profile failed with: " .. io_err
+ end
+ local content, err = serialize_profile(profile, format, filter)
+ if not content then
+ output:close()
+ return false, "Saving of profile failed with: " .. err
+ end
+ output:write(content)
+ output:close()
+
+ local logmessage = "Profile saved to " .. path
+ core.log("action", logmessage)
+ return true, logmessage
+end
+
+return reporter
diff --git a/builtin/profiler/sampling.lua b/builtin/profiler/sampling.lua
new file mode 100644
index 000000000..4b53399a5
--- /dev/null
+++ b/builtin/profiler/sampling.lua
@@ -0,0 +1,206 @@
+--Minetest
+--Copyright (C) 2016 T4im
+--
+--This program is free software; you can redistribute it and/or modify
+--it under the terms of the GNU Lesser General Public License as published by
+--the Free Software Foundation; either version 2.1 of the License, or
+--(at your option) any later version.
+--
+--This program is distributed in the hope that it will be useful,
+--but WITHOUT ANY WARRANTY; without even the implied warranty of
+--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+--GNU Lesser General Public License for more details.
+--
+--You should have received a copy of the GNU Lesser General Public License along
+--with this program; if not, write to the Free Software Foundation, Inc.,
+--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+local setmetatable = setmetatable
+local pairs, format = pairs, string.format
+local min, max, huge = math.min, math.max, math.huge
+local core = core
+
+local profiler = ...
+-- Split sampler and profile up, to possibly allow for rotation later.
+local sampler = {}
+local profile
+local stats_total
+local logged_time, logged_data
+
+local _stat_mt = {
+ get_time_avg = function(self)
+ return self.time_all/self.samples
+ end,
+ get_part_avg = function(self)
+ if not self.part_all then
+ return 100 -- Extra handling for "total"
+ end
+ return self.part_all/self.samples
+ end,
+}
+_stat_mt.__index = _stat_mt
+
+function sampler.reset()
+ -- Accumulated logged time since last sample.
+ -- This helps determining, the relative time a mod used up.
+ logged_time = 0
+ -- The measurements taken through instrumentation since last sample.
+ logged_data = {}
+
+ profile = {
+ -- Current mod statistics (max/min over the entire mod lifespan)
+ -- Mod specific instrumentation statistics are nested within.
+ stats = {},
+ -- Current stats over all mods.
+ stats_total = setmetatable({
+ samples = 0,
+ time_min = huge,
+ time_max = 0,
+ time_all = 0,
+ part_min = 100,
+ part_max = 100
+ }, _stat_mt)
+ }
+ stats_total = profile.stats_total
+
+ -- Provide access to the most recent profile.
+ sampler.profile = profile
+end
+
+---
+-- Log a measurement for the sampler to pick up later.
+-- Keep `log` and its often called functions lean.
+-- It will directly add to the instrumentation overhead.
+--
+function sampler.log(modname, instrument_name, time_diff)
+ if time_diff <= 0 then
+ if time_diff < 0 then
+ -- This **might** have happened on a semi-regular basis with huge mods,
+ -- resulting in negative statistics (perhaps midnight time jumps or ntp corrections?).
+ core.log("warning", format(
+ "Time travel of %s::%s by %dµs.",
+ modname, instrument_name, time_diff
+ ))
+ end
+ -- Throwing these away is better, than having them mess with the overall result.
+ return
+ end
+
+ local mod_data = logged_data[modname]
+ if mod_data == nil then
+ mod_data = {}
+ logged_data[modname] = mod_data
+ end
+
+ mod_data[instrument_name] = (mod_data[instrument_name] or 0) + time_diff
+ -- Update logged time since last sample.
+ logged_time = logged_time + time_diff
+end
+
+---
+-- Return a requested statistic.
+-- Initialize if necessary.
+--
+local function get_statistic(stats_table, name)
+ local statistic = stats_table[name]
+ if statistic == nil then
+ statistic = setmetatable({
+ samples = 0,
+ time_min = huge,
+ time_max = 0,
+ time_all = 0,
+ part_min = 100,
+ part_max = 0,
+ part_all = 0,
+ }, _stat_mt)
+ stats_table[name] = statistic
+ end
+ return statistic
+end
+
+---
+-- Update a statistic table
+--
+local function update_statistic(stats_table, time)
+ stats_table.samples = stats_table.samples + 1
+
+ -- Update absolute time (µs) spend by the subject
+ stats_table.time_min = min(stats_table.time_min, time)
+ stats_table.time_max = max(stats_table.time_max, time)
+ stats_table.time_all = stats_table.time_all + time
+
+ -- Update relative time (%) of this sample spend by the subject
+ local current_part = (time/logged_time) * 100
+ stats_table.part_min = min(stats_table.part_min, current_part)
+ stats_table.part_max = max(stats_table.part_max, current_part)
+ stats_table.part_all = stats_table.part_all + current_part
+end
+
+---
+-- Sample all logged measurements each server step.
+-- Like any globalstep function, this should not be too heavy,
+-- but does not add to the instrumentation overhead.
+--
+local function sample(dtime)
+ -- Rare, but happens and is currently of no informational value.
+ if logged_time == 0 then
+ return
+ end
+
+ for modname, instruments in pairs(logged_data) do
+ local mod_stats = get_statistic(profile.stats, modname)
+ if mod_stats.instruments == nil then
+ -- Current statistics for each instrumentation component
+ mod_stats.instruments = {}
+ end
+
+ local mod_time = 0
+ for instrument_name, time in pairs(instruments) do
+ if time > 0 then
+ mod_time = mod_time + time
+ local instrument_stats = get_statistic(mod_stats.instruments, instrument_name)
+
+ -- Update time of this sample spend by the instrumented function.
+ update_statistic(instrument_stats, time)
+ -- Reset logged data for the next sample.
+ instruments[instrument_name] = 0
+ end
+ end
+
+ -- Update time of this sample spend by this mod.
+ update_statistic(mod_stats, mod_time)
+ end
+
+ -- Update the total time spend over all mods.
+ stats_total.time_min = min(stats_total.time_min, logged_time)
+ stats_total.time_max = max(stats_total.time_max, logged_time)
+ stats_total.time_all = stats_total.time_all + logged_time
+
+ stats_total.samples = stats_total.samples + 1
+ logged_time = 0
+end
+
+---
+-- Setup empty profile and register the sampling function
+--
+function sampler.init()
+ sampler.reset()
+
+ if core.settings:get_bool("instrument.profiler") then
+ core.register_globalstep(function()
+ if logged_time == 0 then
+ return
+ end
+ return profiler.empty_instrument()
+ end)
+ core.register_globalstep(profiler.instrument {
+ func = sample,
+ mod = "*profiler*",
+ class = "Sampler (update stats)",
+ label = false,
+ })
+ else
+ core.register_globalstep(sample)
+ end
+end
+
+return sampler
diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt
new file mode 100644
index 000000000..0d382df70
--- /dev/null
+++ b/builtin/settingtypes.txt
@@ -0,0 +1,1509 @@
+# This file contains all settings displayed in the settings menu.
+#
+# General format:
+# name (Readable name) type type_args
+#
+# Note that the parts are separated by exactly one space
+#
+# `type` can be:
+# - int
+# - string
+# - bool
+# - float
+# - enum
+# - path
+# - key (will be ignored in GUI, since a special key change dialog exists)
+# - flags
+# - noise_params
+#
+# `type_args` can be:
+# * int:
+# - default
+# - default min max
+# * string:
+# - default (if default is not specified then "" is set)
+# * bool:
+# - default
+# * float:
+# - default
+# - default min max
+# * enum:
+# - default value1,value2,...
+# * path:
+# - default (if default is not specified then "" is set)
+# * key:
+# - default
+# * flags:
+# Flags are always separated by comma without spaces.
+# - default possible_flags
+# * noise_params:
+# TODO: these are currently treated like strings
+#
+# Comments directly above a setting are bound to this setting.
+# All other comments are ignored.
+#
+# Comments and (Readable name) are handled by gettext.
+# Comments should be complete sentences that describe the setting and possibly
+# give the user additional useful insight.
+# Sections are marked by a single line in the format: [Section Name]
+# Sub-section are marked by adding * in front of the section name: [*Sub-section]
+# Sub-sub-sections have two * etc.
+# There shouldn't be too much settings per category; settings that shouldn't be
+# modified by the "average user" should be in (sub-)categories called "Advanced".
+
+[Client]
+
+[*Controls]
+# If enabled, you can place blocks at the position (feet + eye level) where you stand.
+# This is helpful when working with nodeboxes in small areas.
+enable_build_where_you_stand (Build inside player) bool false
+
+# Player is able to fly without being affected by gravity.
+# This requires the "fly" privilege on the server.
+free_move (Flying) bool false
+
+# Fast movement (via use key).
+# This requires the "fast" privilege on the server.
+fast_move (Fast movement) bool false
+
+# If enabled together with fly mode, player is able to fly through solid nodes.
+# This requires the "noclip" privilege on the server.
+noclip (Noclip) bool false
+
+# Smooths camera when looking around. Also called look or mouse smoothing.
+# Useful for recording videos.
+cinematic (Cinematic mode) bool false
+
+# Smooths rotation of camera. 0 to disable.
+camera_smoothing (Camera smoothing) float 0.0 0.0 0.99
+
+# Smooths rotation of camera in cinematic mode. 0 to disable.
+cinematic_camera_smoothing (Camera smoothing in cinematic mode) float 0.7 0.0 0.99
+
+# Invert vertical mouse movement.
+invert_mouse (Invert mouse) bool false
+
+# Mouse sensitivity multiplier.
+mouse_sensitivity (Mouse sensitivity) float 0.2
+
+# If enabled, "use" key instead of "sneak" key is used for climbing down and descending.
+aux1_descends (Key use for climbing/descending) bool false
+
+# Double-tapping the jump key toggles fly mode.
+doubletap_jump (Double tap jump for fly) bool false
+
+# If disabled "use" key is used to fly fast if both fly and fast mode are enabled.
+always_fly_fast (Always fly and fast) bool true
+
+# The time in seconds it takes between repeated right clicks when holding the right mouse button.
+repeat_rightclick_time (Rightclick repetition interval) float 0.25
+
+# Enable random user input (only used for testing).
+random_input (Random input) bool false
+
+# Continuous forward movement (only used for testing).
+continuous_forward (Continuous forward) bool false
+
+# Enable Joysticks
+enable_joysticks (Enable Joysticks) bool false
+
+# The identifier of the joystick to use
+joystick_id (Joystick ID) int 0
+
+# The type of joystick
+joystick_type (Joystick Type) enum auto auto,generic,xbox
+
+# The time in seconds it takes between repeated events
+# when holding down a joystick button combination.
+repeat_joystick_button_time (Joystick button repetition interval) float 0.17
+
+# The sensitivity of the joystick axes for moving the
+# ingame view frustum around.
+joystick_frustum_sensitivity (Joystick frustum sensitivity) float 170
+
+# Key for moving the player forward.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_forward (Forward key) key KEY_KEY_W
+
+# Key for moving the player backward.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_backward (Backward key) key KEY_KEY_S
+
+# Key for moving the player left.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_left (Left key) key KEY_KEY_A
+
+# Key for moving the player right.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_right (Right key) key KEY_KEY_D
+
+# Key for jumping.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_jump (Jump key) key KEY_SPACE
+
+# Key for sneaking.
+# Also used for climbing down and descending in water if aux1_descends is disabled.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_sneak (Sneak key) key KEY_LSHIFT
+
+# Key for opening the inventory.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_inventory (Inventory key) key KEY_KEY_I
+
+# Key for moving fast in fast mode.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_special1 (Use key) key KEY_KEY_E
+
+# Key for opening the chat window.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_chat (Chat key) key KEY_KEY_T
+
+# Key for opening the chat window to type commands.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_cmd (Command key) key /
+
+# Key for opening the chat window to type local commands.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_cmd_local (Command key) key .
+
+# Key for toggling unlimited view range.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_rangeselect (Range select key) key KEY_KEY_R
+
+# Key for toggling flying.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_freemove (Fly key) key KEY_KEY_K
+
+# Key for toggling fast mode.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_fastmove (Fast key) key KEY_KEY_J
+
+# Key for toggling noclip mode.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_noclip (Noclip key) key KEY_KEY_H
+
+# Key for selecting the next item in the hotbar.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_hotbar_next (Hotbar next key) key KEY_KEY_N
+
+# Key for selecting the previous item in the hotbar.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_hotbar_previous (Hotbar previous key) key KEY_KEY_B
+
+# Key for muting the game.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_mute (Mute key) key KEY_KEY_M
+
+# Key for increasing the volume.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_increase_volume (Inc. volume key) key
+
+# Key for decreasing the volume.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_decrease_volume (Dec. volume key) key
+
+# Key for toggling autorun.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_autorun (Autorun key) key
+
+# Key for toggling cinematic mode.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_cinematic (Cinematic mode key) key
+
+# Key for toggling display of minimap.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_minimap (Minimap key) key KEY_F9
+
+# Key for taking screenshots.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_screenshot (Screenshot) key KEY_F12
+
+# Key for dropping the currently selected item.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_drop (Drop item key) key KEY_KEY_Q
+
+# Key to use view zoom when possible.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_zoom (View zoom key) key KEY_KEY_Z
+
+# Key for toggling the display of the HUD.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_toggle_hud (HUD toggle key) key KEY_F1
+
+# Key for toggling the display of the chat.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_toggle_chat (Chat toggle key) key KEY_F2
+
+# Key for toggling the display of the large chat console.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_console (Large chat console key) key KEY_F10
+
+# Key for toggling the display of the fog.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_toggle_force_fog_off (Fog toggle key) key KEY_F3
+
+# Key for toggling the camera update. Only used for development
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_toggle_update_camera (Camera update toggle key) key
+
+# Key for toggling the display of debug info.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_toggle_debug (Debug info toggle key) key KEY_F5
+
+# Key for toggling the display of the profiler. Used for development.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_toggle_profiler (Profiler toggle key) key KEY_F6
+
+# Key for switching between first- and third-person camera.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_camera_mode (Toggle camera mode key) key KEY_F7
+
+# Key for increasing the viewing range.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_increase_viewing_range_min (View range increase key) key +
+
+# Key for decreasing the viewing range.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_decrease_viewing_range_min (View range decrease key) key -
+
+# Key for printing debug stacks. Used for development.
+# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
+keymap_print_debug_stacks (Print stacks) key KEY_KEY_P
+
+[*Network]
+
+# Address to connect to.
+# Leave this blank to start a local server.
+# Note that the address field in the main menu overrides this setting.
+address (Server address) string
+
+# Port to connect to (UDP).
+# Note that the port field in the main menu overrides this setting.
+remote_port (Remote port) int 30000 1 65535
+
+# Whether to support older servers before protocol version 25.
+# Enable if you want to connect to 0.4.12 servers and before.
+# Servers starting with 0.4.13 will work, 0.4.12-dev servers may work.
+# Disabling this option will protect your password better.
+send_pre_v25_init (Support older servers) bool false
+
+# Save the map received by the client on disk.
+enable_local_map_saving (Saving map received from server) bool false
+
+# Show entity selection boxes
+show_entity_selectionbox (Show entity selection boxes) bool true
+
+# Enable usage of remote media server (if provided by server).
+# Remote servers offer a significantly faster way to download media (e.g. textures)
+# when connecting to the server.
+enable_remote_media_server (Connect to external media server) bool true
+
+# Enable Lua modding support on client.
+# This support is experimental and API can change.
+enable_client_modding (Client modding) bool false
+
+# URL to the server list displayed in the Multiplayer Tab.
+serverlist_url (Serverlist URL) string servers.minetest.net
+
+# File in client/serverlist/ that contains your favorite servers displayed in the Multiplayer Tab.
+serverlist_file (Serverlist file) string favoriteservers.txt
+
+# Maximum size of the out chat queue. 0 to disable queueing and -1 to make the queue size unlimited
+max_out_chat_queue_size (Maximum size of the out chat queue) int 20
+
+[*Graphics]
+
+[**In-Game]
+
+[***Basic]
+
+# Enable VBO
+enable_vbo (VBO) bool true
+
+# Whether to fog out the end of the visible area.
+enable_fog (Fog) bool true
+
+# Leaves style:
+# - Fancy: all faces visible
+# - Simple: only outer faces, if defined special_tiles are used
+# - Opaque: disable transparency
+leaves_style (Leaves style) enum fancy fancy,simple,opaque
+
+# Connects glass if supported by node.
+connected_glass (Connect glass) bool false
+
+# Enable smooth lighting with simple ambient occlusion.
+# Disable for speed or for different looks.
+smooth_lighting (Smooth lighting) bool true
+
+# Clouds are a client side effect.
+enable_clouds (Clouds) bool true
+
+# Use 3D cloud look instead of flat.
+enable_3d_clouds (3D clouds) bool true
+
+# Method used to highlight selected object.
+node_highlighting (Node highlighting) enum box box,halo,none
+
+# Adds particles when digging a node.
+enable_particles (Digging particles) bool true
+
+[***Filtering]
+
+# Use mip mapping to scale textures. May slightly increase performance.
+mip_map (Mipmapping) bool false
+
+# Use anisotropic filtering when viewing at textures from an angle.
+anisotropic_filter (Anisotropic filtering) bool false
+
+# Use bilinear filtering when scaling textures.
+bilinear_filter (Bilinear filtering) bool false
+
+# Use trilinear filtering when scaling textures.
+trilinear_filter (Trilinear filtering) bool false
+
+# Filtered textures can blend RGB values with fully-transparent neighbors,
+# which PNG optimizers usually discard, sometimes resulting in a dark or
+# light edge to transparent textures. Apply this filter to clean that up
+# at texture load time.
+texture_clean_transparent (Clean transparent textures) bool false
+
+# When using bilinear/trilinear/anisotropic filters, low-resolution textures
+# can be blurred, so automatically upscale them with nearest-neighbor
+# interpolation to preserve crisp pixels. This sets the minimum texture size
+# for the upscaled textures; higher values look sharper, but require more
+# memory. Powers of 2 are recommended. Setting this higher than 1 may not
+# have a visible effect unless bilinear/trilinear/anisotropic filtering is
+# enabled.
+texture_min_size (Minimum texture size for filters) int 64
+
+# Experimental option, might cause visible spaces between blocks
+# when set to higher number than 0.
+fsaa (FSAA) enum 0 0,1,2,4,8,16
+
+# Undersampling is similar to using lower screen resolution, but it applies
+# to the game world only, keeping the GUI intact.
+# It should give significant performance boost at the cost of less detailed image.
+undersampling (Undersampling) enum 0 0,2,3,4
+
+[***Shaders]
+
+# Shaders allow advanced visual effects and may increase performance on some video cards.
+# This only works with the OpenGL video backend.
+enable_shaders (Shaders) bool true
+
+# Path to shader directory. If no path is defined, default location will be used.
+shader_path (Shader path) path
+
+[****Tone Mapping]
+
+# Enables filmic tone mapping
+tone_mapping (Filmic tone mapping) bool false
+
+[****Bumpmapping]
+
+# Enables bumpmapping for textures. Normalmaps need to be supplied by the texture pack
+# or need to be auto-generated.
+# Requires shaders to be enabled.
+enable_bumpmapping (Bumpmapping) bool false
+
+# Enables on the fly normalmap generation (Emboss effect).
+# Requires bumpmapping to be enabled.
+generate_normalmaps (Generate normalmaps) bool false
+
+# Strength of generated normalmaps.
+normalmaps_strength (Normalmaps strength) float 0.6
+
+# Defines sampling step of texture.
+# A higher value results in smoother normal maps.
+normalmaps_smooth (Normalmaps sampling) int 0 0 2
+
+[****Parallax Occlusion]
+
+# Enables parallax occlusion mapping.
+# Requires shaders to be enabled.
+enable_parallax_occlusion (Parallax occlusion) bool false
+
+# 0 = parallax occlusion with slope information (faster).
+# 1 = relief mapping (slower, more accurate).
+parallax_occlusion_mode (Parallax occlusion mode) int 1 0 1
+
+# Strength of parallax.
+3d_paralax_strength (Parallax occlusion strength) float 0.025
+
+# Number of parallax occlusion iterations.
+parallax_occlusion_iterations (Parallax occlusion iterations) int 4
+
+# Overall scale of parallax occlusion effect.
+parallax_occlusion_scale (Parallax occlusion Scale) float 0.08
+
+# Overall bias of parallax occlusion effect, usually scale/2.
+parallax_occlusion_bias (Parallax occlusion bias) float 0.04
+
+[****Waving Nodes]
+
+# Set to true enables waving water.
+# Requires shaders to be enabled.
+enable_waving_water (Waving water) bool false
+
+water_wave_height (Waving water height) float 1.0
+
+water_wave_length (Waving water length) float 20.0
+
+water_wave_speed (Waving water speed) float 5.0
+
+# Set to true enables waving leaves.
+# Requires shaders to be enabled.
+enable_waving_leaves (Waving leaves) bool false
+
+# Set to true enables waving plants.
+# Requires shaders to be enabled.
+enable_waving_plants (Waving plants) bool false
+
+[***Advanced]
+
+# If FPS would go higher than this, limit it by sleeping
+# to not waste CPU power for no benefit.
+fps_max (Maximum FPS) int 60
+
+# Maximum FPS when game is paused.
+pause_fps_max (FPS in pause menu) int 20
+
+# View distance in nodes.
+viewing_range (Viewing range) int 100 20 4000
+
+# Camera near plane distance in nodes, between 0 and 0.5
+# Most users will not need to change this.
+# Increasing can reduce artifacting on weaker GPUs.
+# 0.1 = Default, 0.25 = Good value for weaker tablets.
+near_plane (Near plane) float 0.1 0 0.5
+
+# Width component of the initial window size.
+screenW (Screen width) int 800
+
+# Height component of the initial window size.
+screenH (Screen height) int 600
+
+# Save window size automatically when modified.
+autosave_screensize (Autosave Screen Size) bool true
+
+# Fullscreen mode.
+fullscreen (Full screen) bool false
+
+# Bits per pixel (aka color depth) in fullscreen mode.
+fullscreen_bpp (Full screen BPP) int 24
+
+# Vertical screen synchronization.
+vsync (V-Sync) bool false
+
+# Field of view in degrees.
+fov (Field of view) int 72 30 160
+
+# Field of view while zooming in degrees.
+# This requires the "zoom" privilege on the server.
+zoom_fov (Field of view for zoom) int 15 7 160
+
+# Adjust the gamma encoding for the light tables. Higher numbers are brighter.
+# This setting is for the client only and is ignored by the server.
+display_gamma (Gamma) float 2.2 1.0 3.0
+
+# Path to texture directory. All textures are first searched from here.
+texture_path (Texture path) path
+
+# The rendering back-end for Irrlicht.
+video_driver (Video driver) enum opengl null,software,burningsvideo,direct3d8,direct3d9,opengl
+
+# Height on which clouds are appearing.
+cloud_height (Cloud height) int 120
+
+# Radius of cloud area stated in number of 64 node cloud squares.
+# Values larger than 26 will start to produce sharp cutoffs at cloud area corners.
+cloud_radius (Cloud radius) int 12
+
+# Enable view bobbing and amount of view bobbing.
+# For example: 0 for no view bobbing; 1.0 for normal; 2.0 for double.
+view_bobbing_amount (View bobbing factor) float 1.0
+
+# Multiplier for fall bobbing.
+# For example: 0 for no view bobbing; 1.0 for normal; 2.0 for double.
+fall_bobbing_amount (Fall bobbing factor) float 0.0
+
+# 3D support.
+# Currently supported:
+# - none: no 3d output.
+# - anaglyph: cyan/magenta color 3d.
+# - interlaced: odd/even line based polarisation screen support.
+# - topbottom: split screen top/bottom.
+# - sidebyside: split screen side by side.
+# - pageflip: quadbuffer based 3d.
+3d_mode (3D mode) enum none none,anaglyph,interlaced,topbottom,sidebyside,pageflip
+
+# In-game chat console height, between 0.1 (10%) and 1.0 (100%).
+console_height (Console height) float 1.0 0.1 1.0
+
+# In-game chat console background color (R,G,B).
+console_color (Console color) string (0,0,0)
+
+# In-game chat console background alpha (opaqueness, between 0 and 255).
+console_alpha (Console alpha) int 200 0 255
+
+# Selection box border color (R,G,B).
+selectionbox_color (Selection box color) string (0,0,0)
+
+# Width of the selectionbox's lines around nodes.
+selectionbox_width (Selection box width) int 2 1 5
+
+# Crosshair color (R,G,B).
+crosshair_color (Crosshair color) string (255,255,255)
+
+# Crosshair alpha (opaqueness, between 0 and 255).
+crosshair_alpha (Crosshair alpha) int 255 0 255
+
+# Whether node texture animations should be desynchronized per mapblock.
+desynchronize_mapblock_texture_animation (Desynchronize block animation) bool true
+
+# Maximum proportion of current window to be used for hotbar.
+# Useful if there's something to be displayed right or left of hotbar.
+hud_hotbar_max_width (Maximum hotbar width) float 1.0
+
+# Modifies the size of the hudbar elements.
+hud_scaling (HUD scale factor) float 1.0
+
+# Enables caching of facedir rotated meshes.
+enable_mesh_cache (Mesh cache) bool false
+
+# Delay between mesh updates on the client in ms. Increasing this will slow
+# down the rate of mesh updates, thus reducing jitter on slower clients.
+mesh_generation_interval (Mapblock mesh generation delay) int 0 0 50
+
+# Size of the MapBlock cache of the mesh generator. Increasing this will
+# increase the cache hit %, reducing the data being copied from the main
+# thread, thus reducing jitter.
+meshgen_block_cache_size (Mapblock mesh generator's MapBlock cache size MB) int 20 0 1000
+
+# Enables minimap.
+enable_minimap (Minimap) bool true
+
+# Shape of the minimap. Enabled = round, disabled = square.
+minimap_shape_round (Round minimap) bool true
+
+# True = 256
+# False = 128
+# Useable to make minimap smoother on slower machines.
+minimap_double_scan_height (Minimap scan height) bool true
+
+# Make fog and sky colors depend on daytime (dawn/sunset) and view direction.
+directional_colored_fog (Colored fog) bool true
+
+# The strength (darkness) of node ambient-occlusion shading.
+# Lower is darker, Higher is lighter. The valid range of values for this
+# setting is 0.25 to 4.0 inclusive. If the value is out of range it will be
+# set to the nearest valid value.
+ambient_occlusion_gamma (Ambient occlusion gamma) float 2.2 0.25 4.0
+
+# Enables animation of inventory items.
+inventory_items_animations (Inventory items animations) bool false
+
+# Android systems only: Tries to create inventory textures from meshes
+# when no supported render was found.
+inventory_image_hack (Inventory image hack) bool false
+
+# Fraction of the visible distance at which fog starts to be rendered
+fog_start (Fog Start) float 0.4 0.0 0.99
+
+# Makes all liquids opaque
+opaque_water (Opaque liquids) bool false
+
+[**Menus]
+
+# Use a cloud animation for the main menu background.
+menu_clouds (Clouds in menu) bool true
+
+# Scale gui by a user specified value.
+# Use a nearest-neighbor-anti-alias filter to scale the GUI.
+# This will smooth over some of the rough edges, and blend
+# pixels when scaling down, at the cost of blurring some
+# edge pixels when images are scaled by non-integer sizes.
+gui_scaling (GUI scaling) float 1.0
+
+# When gui_scaling_filter is true, all GUI images need to be
+# filtered in software, but some images are generated directly
+# to hardware (e.g. render-to-texture for nodes in inventory).
+gui_scaling_filter (GUI scaling filter) bool false
+
+# When gui_scaling_filter_txr2img is true, copy those images
+# from hardware to software for scaling. When false, fall back
+# to the old scaling method, for video drivers that don't
+# properly support downloading textures back from hardware.
+gui_scaling_filter_txr2img (GUI scaling filter txr2img) bool true
+
+# Delay showing tooltips, stated in milliseconds.
+tooltip_show_delay (Tooltip delay) int 400
+
+# Whether freetype fonts are used, requires freetype support to be compiled in.
+freetype (Freetype fonts) bool true
+
+# Path to TrueTypeFont or bitmap.
+font_path (Font path) path fonts/liberationsans.ttf
+
+font_size (Font size) int 16
+
+# Font shadow offset, if 0 then shadow will not be drawn.
+font_shadow (Font shadow) int 1
+
+# Font shadow alpha (opaqueness, between 0 and 255).
+font_shadow_alpha (Font shadow alpha) int 127 0 255
+
+mono_font_path (Monospace font path) path fonts/liberationmono.ttf
+
+mono_font_size (Monospace font size) int 15
+
+# This font will be used for certain languages.
+fallback_font_path (Fallback font) path fonts/DroidSansFallbackFull.ttf
+fallback_font_size (Fallback font size) int 15
+fallback_font_shadow (Fallback font shadow) int 1
+fallback_font_shadow_alpha (Fallback font shadow alpha) int 128 0 255
+
+# Path to save screenshots at.
+screenshot_path (Screenshot folder) path
+
+# Format of screenshots.
+screenshot_format (Screenshot format) enum png png,jpg,bmp,pcx,ppm,tga
+
+# Screenshot quality. Only used for JPEG format.
+# 1 means worst quality; 100 means best quality.
+# Use 0 for default quality.
+screenshot_quality (Screenshot quality) int 0 0 100
+
+[**Advanced]
+
+# Adjust dpi configuration to your screen (non X11/Android only) e.g. for 4k screens.
+screen_dpi (DPI) int 72
+
+# Windows systems only: Start Minetest with the command line window in the background.
+# Contains the same information as the file debug.txt (default name).
+enable_console (Enable console window) bool false
+
+[*Sound]
+
+enable_sound (Sound) bool true
+
+sound_volume (Volume) float 0.7 0.0 1.0
+
+[*Advanced]
+
+# Timeout for client to remove unused map data from memory.
+client_unload_unused_data_timeout (Mapblock unload timeout) int 600
+
+# Maximum number of mapblocks for client to be kept in memory.
+# Set to -1 for unlimited amount.
+client_mapblock_limit (Mapblock limit) int 5000
+
+# Whether to show the client debug info (has the same effect as hitting F5).
+show_debug (Show debug info) bool false
+
+[Server / Singleplayer]
+
+# Name of the server, to be displayed when players join and in the serverlist.
+server_name (Server name) string Minetest server
+
+# Description of server, to be displayed when players join and in the serverlist.
+server_description (Server description) string mine here
+
+# Domain name of server, to be displayed in the serverlist.
+server_address (Server address) string game.minetest.net
+
+# Homepage of server, to be displayed in the serverlist.
+server_url (Server URL) string http://minetest.net
+
+# Automaticaly report to the serverlist.
+server_announce (Announce server) bool false
+
+# Announce to this serverlist.
+# If you want to announce your ipv6 address, use serverlist_url = v6.servers.minetest.net.
+serverlist_url (Serverlist URL) string servers.minetest.net
+
+# Remove color codes from incoming chat messages
+# Use this to stop players from being able to use color in their messages
+strip_color_codes (Strip color codes) bool false
+
+[*Network]
+
+# Network port to listen (UDP).
+# This value will be overridden when starting from the main menu.
+port (Server port) int 30000
+
+# The network interface that the server listens on.
+bind_address (Bind address) string
+
+# Enable to disallow old clients from connecting.
+# Older clients are compatible in the sense that they will not crash when connecting
+# to new servers, but they may not support all new features that you are expecting.
+strict_protocol_version_checking (Strict protocol checking) bool false
+
+# Specifies URL from which client fetches media instead of using UDP.
+# $filename should be accessible from $remote_media$filename via cURL
+# (obviously, remote_media should end with a slash).
+# Files that are not present will be fetched the usual way.
+remote_media (Remote media) string
+
+# Enable/disable running an IPv6 server. An IPv6 server may be restricted
+# to IPv6 clients, depending on system configuration.
+# Ignored if bind_address is set.
+ipv6_server (IPv6 server) bool false
+
+[**Advanced]
+
+# Maximum number of blocks that are simultaneously sent per client.
+max_simultaneous_block_sends_per_client (Maximum simultaneous block sends per client) int 10
+
+# Maximum number of blocks that are simultaneously sent in total.
+max_simultaneous_block_sends_server_total (Maximum simultaneous block sends total) int 40
+
+# To reduce lag, block transfers are slowed down when a player is building something.
+# This determines how long they are slowed down after placing or removing a node.
+full_block_send_enable_min_time_from_building (Delay in sending blocks after building) float 2.0
+
+# Maximum number of packets sent per send step, if you have a slow connection
+# try reducing it, but don't reduce it to a number below double of targeted
+# client number.
+max_packets_per_iteration (Max. packets per iteration) int 1024
+
+[*Game]
+
+# Default game when creating a new world.
+# This will be overridden when creating a world from the main menu.
+default_game (Default game) string minetest
+
+# Message of the day displayed to players connecting.
+motd (Message of the day) string
+
+# Maximum number of players that can connect simultaneously.
+max_users (Maximum users) int 15
+
+# World directory (everything in the world is stored here).
+# Not needed if starting from the main menu.
+map-dir (Map directory) path
+
+# Time in seconds for item entity (dropped items) to live.
+# Setting it to -1 disables the feature.
+item_entity_ttl (Item entity TTL) int 900
+
+# If enabled, show the server status message on player connection.
+show_statusline_on_connect (Status message on connection) bool true
+
+# Enable players getting damage and dying.
+enable_damage (Damage) bool false
+
+# Enable creative mode for new created maps.
+creative_mode (Creative) bool false
+
+# A chosen map seed for a new map, leave empty for random.
+# Will be overridden when creating a new world in the main menu.
+fixed_map_seed (Fixed map seed) string
+
+# New users need to input this password.
+default_password (Default password) string
+
+# The privileges that new users automatically get.
+# See /privs in game for a full list on your server and mod configuration.
+default_privs (Default privileges) string interact, shout
+
+# Privileges that players with basic_privs can grant
+basic_privs (Basic Privileges) string interact, shout
+
+# Whether players are shown to clients without any range limit.
+# Deprecated, use the setting player_transfer_distance instead.
+unlimited_player_transfer_distance (Unlimited player transfer distance) bool true
+
+# Defines the maximal player transfer distance in blocks (0 = unlimited).
+player_transfer_distance (Player transfer distance) int 0
+
+# Whether to allow players to damage and kill each other.
+enable_pvp (Player versus Player) bool true
+
+# If this is set, players will always (re)spawn at the given position.
+static_spawnpoint (Static spawnpoint) string
+
+# If enabled, new players cannot join with an empty password.
+disallow_empty_password (Disallow empty passwords) bool false
+
+# If enabled, disable cheat prevention in multiplayer.
+disable_anticheat (Disable anticheat) bool false
+
+# If enabled, actions are recorded for rollback.
+# This option is only read when server starts.
+enable_rollback_recording (Rollback recording) bool false
+
+# A message to be displayed to all clients when the server shuts down.
+kick_msg_shutdown (Shutdown message) string Server shutting down.
+
+# A message to be displayed to all clients when the server crashes.
+kick_msg_crash (Crash message) string This server has experienced an internal error. You will now be disconnected.
+
+# Whether to ask clients to reconnect after a (Lua) crash.
+# Set this to true if your server is set up to restart automatically.
+ask_reconnect_on_crash (Ask to reconnect after crash) bool false
+
+# From how far clients know about objects, stated in mapblocks (16 nodes).
+active_object_send_range_blocks (Active object send range) int 3
+
+# How large area of blocks are subject to the active block stuff, stated in mapblocks (16 nodes).
+# In active blocks objects are loaded and ABMs run.
+active_block_range (Active block range) int 3
+
+# From how far blocks are sent to clients, stated in mapblocks (16 nodes).
+max_block_send_distance (Max block send distance) int 10
+
+# Maximum number of forceloaded mapblocks.
+max_forceloaded_blocks (Maximum forceloaded blocks) int 16
+
+# Interval of sending time of day to clients.
+time_send_interval (Time send interval) int 5
+
+# Controls length of day/night cycle.
+# Examples: 72 = 20min, 360 = 4min, 1 = 24hour, 0 = day/night/whatever stays unchanged.
+time_speed (Time speed) int 72
+
+# Interval of saving important changes in the world, stated in seconds.
+server_map_save_interval (Map save interval) float 5.3
+
+# Set the maximum character length of a chat message sent by clients.
+# chat_message_max_size int 500
+
+# Limit a single player to send X messages per 10 seconds.
+# chat_message_limit_per_10sec float 10.0
+
+# Kick player if send more than X messages per 10 seconds.
+# chat_message_limit_trigger_kick int 50
+
+[**Physics]
+
+movement_acceleration_default (Default acceleration) float 3
+movement_acceleration_air (Acceleration in air) float 2
+movement_acceleration_fast (Fast mode acceleration) float 10
+movement_speed_walk (Walking speed) float 4
+movement_speed_crouch (Crouch speed) float 1.35
+movement_speed_fast (Fast mode speed) float 20
+movement_speed_climb (Climbing speed) float 3
+movement_speed_jump (Jumping speed) float 6.5
+movement_liquid_fluidity (Liquid fluidity) float 1
+movement_liquid_fluidity_smooth (Liquid fluidity smoothing) float 0.5
+movement_liquid_sink (Liquid sink) float 10
+movement_gravity (Gravity) float 9.81
+
+[**Advanced]
+
+# Handling for deprecated lua api calls:
+# - legacy: (try to) mimic old behaviour (default for release).
+# - log: mimic and log backtrace of deprecated call (default for debug).
+# - error: abort on usage of deprecated call (suggested for mod developers).
+deprecated_lua_api_handling (Deprecated Lua API handling) enum legacy legacy,log,error
+
+# Number of extra blocks that can be loaded by /clearobjects at once.
+# This is a trade-off between sqlite transaction overhead and
+# memory consumption (4096=100MB, as a rule of thumb).
+max_clearobjects_extra_loaded_blocks (Max. clearobjects extra blocks) int 4096
+
+# How much the server will wait before unloading unused mapblocks.
+# Higher value is smoother, but will use more RAM.
+server_unload_unused_data_timeout (Unload unused server data) int 29
+
+# Maximum number of statically stored objects in a block.
+max_objects_per_block (Maximum objects per block) int 64
+
+# See http://www.sqlite.org/pragma.html#pragma_synchronous
+sqlite_synchronous (Synchronous SQLite) enum 2 0,1,2
+
+# Length of a server tick and the interval at which objects are generally updated over network.
+dedicated_server_step (Dedicated server step) float 0.1
+
+# Time in between active block management cycles
+active_block_mgmt_interval (Active Block Management interval) float 2.0
+
+# Length of time between ABM execution cycles
+abm_interval (Active Block Modifier interval) float 1.0
+
+# Length of time between NodeTimer execution cycles
+nodetimer_interval (NodeTimer interval) float 0.2
+
+# If enabled, invalid world data won't cause the server to shut down.
+# Only enable this if you know what you are doing.
+ignore_world_load_errors (Ignore world errors) bool false
+
+# Max liquids processed per step.
+liquid_loop_max (Liquid loop max) int 100000
+
+# The time (in seconds) that the liquids queue may grow beyond processing
+# capacity until an attempt is made to decrease its size by dumping old queue
+# items. A value of 0 disables the functionality.
+liquid_queue_purge_time (Liquid queue purge time) int 0
+
+# Liquid update interval in seconds.
+liquid_update (Liquid update tick) float 1.0
+
+# At this distance the server will aggressively optimize which blocks are sent to clients.
+# Small values potentially improve performance a lot, at the expense of visible rendering glitches.
+# (some blocks will not be rendered under water and in caves, as well as sometimes on land)
+# Setting this to a value greater than max_block_send_distance disables this optimization.
+# Stated in mapblocks (16 nodes)
+block_send_optimize_distance (block send optimize distance) int 4 2
+
+# If enabled the server will perform map block occlusion culling based on
+# on the eye position of the player. This can reduce the number of blocks
+# sent to the client 50-80%. The client will not longer receive most invisible
+# so that the utility of noclip mode is reduced.
+server_side_occlusion_culling (Server side occlusion culling) bool true
+
+[*Mapgen]
+
+# Name of map generator to be used when creating a new world.
+# Creating a world in the main menu will override this.
+mg_name (Mapgen name) enum v7 v5,v6,v7,flat,valleys,fractal,singlenode
+
+# Water surface level of the world.
+water_level (Water level) int 1
+
+# From how far blocks are generated for clients, stated in mapblocks (16 nodes).
+max_block_generate_distance (Max block generate distance) int 6
+
+# Limit of map generation, in nodes, in all 6 directions from (0, 0, 0).
+# Only mapchunks completely within the mapgen limit are generated.
+# Value is stored per-world.
+mapgen_limit (Map generation limit) int 31000 0 31000
+
+# Global map generation attributes.
+# In Mapgen v6 the 'decorations' flag controls all decorations except trees
+# and junglegrass, in all other mapgens this flag controls all decorations.
+# Flags that are not specified in the flag string are not modified from the default.
+# Flags starting with 'no' are used to explicitly disable them.
+mg_flags (Mapgen flags) flags caves,dungeons,light,decorations caves,dungeons,light,decorations,nocaves,nodungeons,nolight,nodecorations
+
+[**Advanced]
+
+# Size of chunks to be generated at once by mapgen, stated in mapblocks (16 nodes).
+chunksize (Chunk size) int 5
+
+# Dump the mapgen debug infos.
+enable_mapgen_debug_info (Mapgen debug) bool false
+
+# Maximum number of blocks that can be queued for loading.
+emergequeue_limit_total (Absolute limit of emerge queues) int 256
+
+# Maximum number of blocks to be queued that are to be loaded from file.
+# Set to blank for an appropriate amount to be chosen automatically.
+emergequeue_limit_diskonly (Limit of emerge queues on disk) int 32
+
+# Maximum number of blocks to be queued that are to be generated.
+# Set to blank for an appropriate amount to be chosen automatically.
+emergequeue_limit_generate (Limit of emerge queues to generate) int 32
+
+# Number of emerge threads to use. Make this field blank, or increase this number
+# to use multiple threads. On multiprocessor systems, this will improve mapgen speed greatly
+# at the cost of slightly buggy caves.
+num_emerge_threads (Number of emerge threads) int 1
+
+[***Biome API temperature and humidity noise parameters]
+
+# Temperature variation for biomes.
+mg_biome_np_heat (Heat noise) noise_params 50, 50, (1000, 1000, 1000), 5349, 3, 0.5, 2.0
+
+# Small-scale temperature variation for blending biomes on borders.
+mg_biome_np_heat_blend (Heat blend noise) noise_params 0, 1.5, (8, 8, 8), 13, 2, 1.0, 2.0
+
+# Humidity variation for biomes.
+mg_biome_np_humidity (Humidity noise) noise_params 50, 50, (1000, 1000, 1000), 842, 3, 0.5, 2.0
+
+# Small-scale humidity variation for blending biomes on borders.
+mg_biome_np_humidity_blend (Humidity blend noise) noise_params 0, 1.5, (8, 8, 8), 90003, 2, 1.0, 2.0
+
+[***Mapgen v5]
+
+# Map generation attributes specific to Mapgen v5.
+# Flags that are not specified in the flag string are not modified from the default.
+# Flags starting with 'no' are used to explicitly disable them.
+mgv5_spflags (Mapgen v5 specific flags) flags caverns caverns,nocaverns
+
+# Controls width of tunnels, a smaller value creates wider tunnels.
+mgv5_cave_width (Cave width) float 0.125
+
+# Y-level of cavern upper limit.
+mgv5_cavern_limit (Cavern limit) int -256
+
+# Y-distance over which caverns expand to full size.
+mgv5_cavern_taper (Cavern taper) int 256
+
+# Defines full size of caverns, smaller values create larger caverns.
+mgv5_cavern_threshold (Cavern threshold) float 0.7
+
+# Variation of biome filler depth.
+mgv5_np_filler_depth (Filler depth noise) noise_params 0, 1, (150, 150, 150), 261, 4, 0.7, 2.0
+
+# Variation of terrain vertical scale.
+# When noise is < -0.55 terrain is near-flat.
+mgv5_np_factor (Factor noise) noise_params 0, 1, (250, 250, 250), 920381, 3, 0.45, 2.0
+
+# Y-level of average terrain surface.
+mgv5_np_height (Height noise) noise_params 0, 10, (250, 250, 250), 84174, 4, 0.5, 2.0
+
+# First of 2 3D noises that together define tunnels.
+mgv5_np_cave1 (Cave1 noise) noise_params 0, 12, (50, 50, 50), 52534, 4, 0.5, 2.0
+
+# Second of 2 3D noises that together define tunnels.
+mgv5_np_cave2 (Cave2 noise) noise_params 0, 12, (50, 50, 50), 10325, 4, 0.5, 2.0
+
+# 3D noise defining giant caverns.
+mgv5_np_cavern (Cavern noise) noise_params 0, 1, (384, 128, 384), 723, 5, 0.63, 2.0
+
+# TODO
+# Noise parameters in group format, unsupported by advanced settings
+# menu but settable in minetest.conf.
+# See documentation of noise parameter formats in minetest.conf.example.
+# 3D noise defining terrain.
+#mgv5_np_ground = {
+# offset = 0
+# scale = 40
+# spread = (80, 80, 80)
+# seed = 983240
+# octaves = 4
+# persistence = 0.55
+# lacunarity = 2.0
+# flags = "eased"
+#}
+
+[***Mapgen v6]
+
+# Map generation attributes specific to Mapgen v6.
+# The 'snowbiomes' flag enables the new 5 biome system.
+# When the new biome system is enabled jungles are automatically enabled and
+# the 'jungles' flag is ignored.
+# Flags that are not specified in the flag string are not modified from the default.
+# Flags starting with 'no' are used to explicitly disable them.
+mgv6_spflags (Mapgen v6 specific flags) flags jungles,biomeblend,mudflow,snowbiomes,trees jungles,biomeblend,mudflow,snowbiomes,flat,trees,nojungles,nobiomeblend,nomudflow,nosnowbiomes,noflat,notrees
+
+# Deserts occur when np_biome exceeds this value.
+# When the new biome system is enabled, this is ignored.
+mgv6_freq_desert (Desert noise threshold) float 0.45
+
+# Sandy beaches occur when np_beach exceeds this value.
+mgv6_freq_beach (Beach noise threshold) float 0.15
+
+# Y-level of lower terrain and lakebeds.
+mgv6_np_terrain_base (Terrain base noise) noise_params -4, 20, (250, 250, 250), 82341, 5, 0.6, 2.0
+
+# Y-level of higher (cliff-top) terrain.
+mgv6_np_terrain_higher (Terrain higher noise) noise_params 20, 16, (500, 500, 500), 85039, 5, 0.6, 2.0
+
+# Varies steepness of cliffs.
+mgv6_np_steepness (Steepness noise) noise_params 0.85, 0.5, (125, 125, 125), -932, 5, 0.7, 2.0
+
+# Defines areas of 'terrain_higher' (cliff-top terrain).
+mgv6_np_height_select (Height select noise) noise_params 0.5, 1, (250, 250, 250), 4213, 5, 0.69, 2.0
+
+# Varies depth of biome surface nodes.
+mgv6_np_mud (Mud noise) noise_params 4, 2, (200, 200, 200), 91013, 3, 0.55, 2.0
+
+# Defines areas with sandy beaches.
+mgv6_np_beach (Beach noise) noise_params 0, 1, (250, 250, 250), 59420, 3, 0.50, 2.0
+
+# Temperature variation for biomes.
+mgv6_np_biome (Biome noise) noise_params 0, 1, (500, 500, 500), 9130, 3, 0.50, 2.0
+
+# Variation of number of caves.
+mgv6_np_cave (Cave noise) noise_params 6, 6, (250, 250, 250), 34329, 3, 0.50, 2.0
+
+# Humidity variation for biomes.
+mgv6_np_humidity (Humidity noise) noise_params 0.5, 0.5, (500, 500, 500), 72384, 3, 0.50, 2.0
+
+# Defines tree areas and tree density.
+mgv6_np_trees (Trees noise) noise_params 0, 1, (125, 125, 125), 2, 4, 0.66, 2.0
+
+# Defines areas where trees have apples.
+mgv6_np_apple_trees (Apple trees noise) noise_params 0, 1, (100, 100, 100), 342902, 3, 0.45, 2.0
+
+[***Mapgen v7]
+
+# Map generation attributes specific to Mapgen v7.
+# The 'ridges' flag enables the rivers.
+# Floatlands are currently experimental and subject to change.
+# Flags that are not specified in the flag string are not modified from the default.
+# Flags starting with 'no' are used to explicitly disable them.
+mgv7_spflags (Mapgen v7 specific flags) flags mountains,ridges,nofloatlands,caverns mountains,ridges,floatlands,caverns,nomountains,noridges,nofloatlands,nocaverns
+
+# Controls width of tunnels, a smaller value creates wider tunnels.
+mgv7_cave_width (Cave width) float 0.09
+
+# Controls the density of floatland mountain terrain.
+# Is an offset added to the 'np_mountain' noise value.
+mgv7_float_mount_density (Floatland mountain density) float 0.6
+
+# Typical maximum height, above and below midpoint, of floatland mountain terrain.
+mgv7_float_mount_height (Floatland mountain height) float 128.0
+
+# Y-level of floatland midpoint and lake surface.
+mgv7_floatland_level (Floatland level) int 1280
+
+# Y-level to which floatland shadows extend.
+mgv7_shadow_limit (Shadow limit) int 1024
+
+# Y-level of cavern upper limit.
+mgv7_cavern_limit (Cavern limit) int -256
+
+# Y-distance over which caverns expand to full size.
+mgv7_cavern_taper (Cavern taper) int 256
+
+# Defines full size of caverns, smaller values create larger caverns.
+mgv7_cavern_threshold (Cavern threshold) float 0.7
+
+# Y-level of higher (cliff-top) terrain.
+mgv7_np_terrain_base (Terrain base noise) noise_params 4, 70, (600, 600, 600), 82341, 5, 0.6, 2.0
+
+# Y-level of lower terrain and lakebeds.
+mgv7_np_terrain_alt (Terrain alt noise) noise_params 4, 25, (600, 600, 600), 5934, 5, 0.6, 2.0
+
+# Varies roughness of terrain.
+# Defines the 'persistence' value for terrain_base and terrain_alt noises.
+mgv7_np_terrain_persist (Terrain persistence noise) noise_params 0.6, 0.1, (2000, 2000, 2000), 539, 3, 0.6, 2.0
+
+# Defines areas of higher (cliff-top) terrain and affects steepness of cliffs.
+mgv7_np_height_select (Height select noise) noise_params -8, 16, (500, 500, 500), 4213, 6, 0.7, 2.0
+
+# Variation of biome filler depth.
+mgv7_np_filler_depth (Filler depth noise) noise_params 0, 1.2, (150, 150, 150), 261, 3, 0.7, 2.0
+
+# Variation of maximum mountain height (in nodes).
+mgv7_np_mount_height (Mountain height noise) noise_params 256, 112, (1000, 1000, 1000), 72449, 3, 0.6, 2.0
+
+# Defines large-scale river channel structure.
+mgv7_np_ridge_uwater (Ridge underwater noise) noise_params 0, 1, (1000, 1000, 1000), 85039, 5, 0.6, 2.0
+
+# Defines areas of floatland smooth terrain.
+# Smooth floatlands occur when noise > 0.
+mgv7_np_floatland_base (Floatland base noise) noise_params -0.6, 1.5, (600, 600, 600), 114, 5, 0.6, 2.0
+
+# Variation of hill height and lake depth on floatland smooth terrain.
+mgv7_np_float_base_height (Floatland base height noise) noise_params 48, 24, (300, 300, 300), 907, 4, 0.7, 2.0
+
+# 3D noise defining mountain structure and height.
+# Also defines structure of floatland mountain terrain.
+mgv7_np_mountain (Mountain noise) noise_params -0.6, 1, (250, 350, 250), 5333, 5, 0.63, 2.0
+
+# 3D noise defining structure of river canyon walls.
+mgv7_np_ridge (Ridge noise) noise_params 0, 1, (100, 100, 100), 6467, 4, 0.75, 2.0
+
+# 3D noise defining giant caverns.
+mgv7_np_cavern (Cavern noise) noise_params 0, 1, (384, 128, 384), 723, 5, 0.63, 2.0
+
+# First of 2 3D noises that together define tunnels.
+mgv7_np_cave1 (Cave1 noise) noise_params 0, 12, (61, 61, 61), 52534, 3, 0.5, 2.0
+
+# Second of 2 3D noises that together define tunnels.
+mgv7_np_cave2 (Cave2 noise) noise_params 0, 12, (67, 67, 67), 10325, 3, 0.5, 2.0
+
+[***Mapgen flat]
+
+# Map generation attributes specific to Mapgen flat.
+# Occasional lakes and hills can be added to the flat world.
+# Flags that are not specified in the flag string are not modified from the default.
+# Flags starting with 'no' are used to explicitly disable them.
+mgflat_spflags (Mapgen flat specific flags) flags nolakes,nohills lakes,hills,nolakes,nohills
+
+# Y of flat ground.
+mgflat_ground_level (Ground level) int 8
+
+# Y of upper limit of large pseudorandom caves.
+mgflat_large_cave_depth (Large cave depth) int -33
+
+# Controls width of tunnels, a smaller value creates wider tunnels.
+mgflat_cave_width (Cave width) float 0.09
+
+# Terrain noise threshold for lakes.
+# Controls proportion of world area covered by lakes.
+# Adjust towards 0.0 for a larger proportion.
+mgflat_lake_threshold (Lake threshold) float -0.45
+
+# Controls steepness/depth of lake depressions.
+mgflat_lake_steepness (Lake steepness) float 48.0
+
+# Terrain noise threshold for hills.
+# Controls proportion of world area covered by hills.
+# Adjust towards 0.0 for a larger proportion.
+mgflat_hill_threshold (Hill threshold) float 0.45
+
+# Controls steepness/height of hills.
+mgflat_hill_steepness (Hill steepness) float 64.0
+
+# Defines location and terrain of optional hills and lakes.
+mgflat_np_terrain (Terrain noise) noise_params 0, 1, (600, 600, 600), 7244, 5, 0.6, 2.0
+
+# Variation of biome filler depth.
+mgflat_np_filler_depth (Filler depth noise) noise_params 0, 1.2, (150, 150, 150), 261, 3, 0.7, 2.0
+
+# First of 2 3D noises that together define tunnels.
+mgflat_np_cave1 (Cave1 noise) noise_params 0, 12, (61, 61, 61), 52534, 3, 0.5, 2.0
+
+# Second of 2 3D noises that together define tunnels.
+mgflat_np_cave2 (Cave2 noise) noise_params 0, 12, (67, 67, 67), 10325, 3, 0.5, 2.0
+
+[***Mapgen fractal]
+
+# Controls width of tunnels, a smaller value creates wider tunnels.
+mgfractal_cave_width (Cave width) float 0.09
+
+# Choice of 18 fractals from 9 formulas.
+# 1 = 4D "Roundy" mandelbrot set.
+# 2 = 4D "Roundy" julia set.
+# 3 = 4D "Squarry" mandelbrot set.
+# 4 = 4D "Squarry" julia set.
+# 5 = 4D "Mandy Cousin" mandelbrot set.
+# 6 = 4D "Mandy Cousin" julia set.
+# 7 = 4D "Variation" mandelbrot set.
+# 8 = 4D "Variation" julia set.
+# 9 = 3D "Mandelbrot/Mandelbar" mandelbrot set.
+# 10 = 3D "Mandelbrot/Mandelbar" julia set.
+# 11 = 3D "Christmas Tree" mandelbrot set.
+# 12 = 3D "Christmas Tree" julia set.
+# 13 = 3D "Mandelbulb" mandelbrot set.
+# 14 = 3D "Mandelbulb" julia set.
+# 15 = 3D "Cosine Mandelbulb" mandelbrot set.
+# 16 = 3D "Cosine Mandelbulb" julia set.
+# 17 = 4D "Mandelbulb" mandelbrot set.
+# 18 = 4D "Mandelbulb" julia set.
+mgfractal_fractal (Fractal type) int 1 1 18
+
+# Iterations of the recursive function.
+# Controls the amount of fine detail.
+mgfractal_iterations (Iterations) int 11
+
+# Approximate (X,Y,Z) scale of fractal in nodes.
+mgfractal_scale (Scale) v3f (4096.0, 1024.0, 4096.0)
+
+# (X,Y,Z) offset of fractal from world centre in units of 'scale'.
+# Used to move a suitable spawn area of low land close to (0, 0).
+# The default is suitable for mandelbrot sets, it needs to be edited for julia sets.
+# Range roughly -2 to 2. Multiply by 'scale' for offset in nodes.
+mgfractal_offset (Offset) v3f (1.79, 0.0, 0.0)
+
+# W co-ordinate of the generated 3D slice of a 4D fractal.
+# Determines which 3D slice of the 4D shape is generated.
+# Has no effect on 3D fractals.
+# Range roughly -2 to 2.
+mgfractal_slice_w (Slice w) float 0.0
+
+# Julia set only: X component of hypercomplex constant determining julia shape.
+# Range roughly -2 to 2.
+mgfractal_julia_x (Julia x) float 0.33
+
+# Julia set only: Y component of hypercomplex constant determining julia shape.
+# Range roughly -2 to 2.
+mgfractal_julia_y (Julia y) float 0.33
+
+# Julia set only: Z component of hypercomplex constant determining julia shape.
+# Range roughly -2 to 2.
+mgfractal_julia_z (Julia z) float 0.33
+
+# Julia set only: W component of hypercomplex constant determining julia shape.
+# Has no effect on 3D fractals.
+# Range roughly -2 to 2.
+mgfractal_julia_w (Julia w) float 0.33
+
+# Y-level of seabed.
+mgfractal_np_seabed (Seabed noise) noise_params -14, 9, (600, 600, 600), 41900, 5, 0.6, 2.0
+
+# Variation of biome filler depth.
+mgfractal_np_filler_depth (Filler depth noise) noise_params 0, 1.2, (150, 150, 150), 261, 3, 0.7, 2.0
+
+# First of 2 3D noises that together define tunnels.
+mgfractal_np_cave1 (Cave1 noise) noise_params 0, 12, (61, 61, 61), 52534, 3, 0.5, 2.0
+
+# Second of 2 3D noises that together define tunnels.
+mgfractal_np_cave2 (Cave2 noise) noise_params 0, 12, (67, 67, 67), 10325, 3, 0.5, 2.0
+
+# Mapgen Valleys parameters
+[***Mapgen Valleys]
+
+# General parameters
+[****General]
+
+# Map generation attributes specific to Mapgen Valleys.
+# 'altitude_chill' makes higher elevations colder, which may cause biome issues.
+# 'humid_rivers' modifies the humidity around rivers and in areas where water would tend to pool,
+# it may interfere with delicately adjusted biomes.
+# Flags that are not specified in the flag string are not modified from the default.
+# Flags starting with 'no' are used to explicitly disable them.
+mg_valleys_spflags (Valleys C Flags) flags altitude_chill,humid_rivers altitude_chill,noaltitude_chill,humid_rivers,nohumid_rivers
+
+# The altitude at which temperature drops by 20C
+mgvalleys_altitude_chill (Altitude Chill) int 90
+
+# Depth below which you'll find large caves.
+mgvalleys_large_cave_depth (Large cave depth) int -33
+
+# Creates unpredictable lava features in caves.
+# These can make mining difficult. Zero disables them. (0-10)
+mgvalleys_lava_features (Lava Features) int 0
+
+# Depth below which you'll find massive caves.
+mgvalleys_massive_cave_depth (Massive cave depth) int -256
+
+# How deep to make rivers
+mgvalleys_river_depth (River Depth) int 4
+
+# How wide to make rivers
+mgvalleys_river_size (River Size) int 5
+
+# Creates unpredictable water features in caves.
+# These can make mining difficult. Zero disables them. (0-10)
+mgvalleys_water_features (Water Features) int 0
+
+# Controls width of tunnels, a smaller value creates wider tunnels.
+mgvalleys_cave_width (Cave width) float 0.09
+
+# Noise parameters
+[****Noises]
+
+# Caves and tunnels form at the intersection of the two noises
+mgvalleys_np_cave1 (Cave noise #1) noise_params 0, 12, (61, 61, 61), 52534, 3, 0.5, 2.0
+
+# Caves and tunnels form at the intersection of the two noises
+mgvalleys_np_cave2 (Cave noise #2) noise_params 0, 12, (67, 67, 67), 10325, 3, 0.5, 2.0
+
+# The depth of dirt or other filler
+mgvalleys_np_filler_depth (Filler Depth) noise_params 0, 1.2, (256, 256, 256), 1605, 3, 0.5, 2.0
+
+# Massive caves form here.
+mgvalleys_np_massive_caves (Massive cave noise) noise_params 0, 1, (768, 256, 768), 59033, 6, 0.63, 2.0
+
+# River noise -- rivers occur close to zero
+mgvalleys_np_rivers (River Noise) noise_params 0, 1, (256, 256, 256), -6050, 5, 0.6, 2.0
+
+# Base terrain height
+mgvalleys_np_terrain_height (Terrain Height) noise_params -10, 50, (1024, 1024, 1024), 5202, 6, 0.4, 2.0
+
+# Raises terrain to make valleys around the rivers
+mgvalleys_np_valley_depth (Valley Depth) noise_params 5, 4, (512, 512, 512), -1914, 1, 1.0, 2.0
+
+# Slope and fill work together to modify the heights
+mgvalleys_np_inter_valley_fill (Valley Fill) noise_params 0, 1, (256, 512, 256), 1993, 6, 0.8, 2.0
+
+# Amplifies the valleys
+mgvalleys_np_valley_profile (Valley Profile) noise_params 0.6, 0.5, (512, 512, 512), 777, 1, 1.0, 2.0
+
+# Slope and fill work together to modify the heights
+mgvalleys_np_inter_valley_slope (Valley Slope) noise_params 0.5, 0.5, (128, 128, 128), 746, 1, 1.0, 2.0
+
+[*Security]
+
+# Prevent mods from doing insecure things like running shell commands.
+secure.enable_security (Enable mod security) bool true
+
+# Comma-separated list of trusted mods that are allowed to access insecure
+# functions even when mod security is on (via request_insecure_environment()).
+secure.trusted_mods (Trusted mods) string
+
+# Comma-separated list of mods that are allowed to access HTTP APIs, which
+# allow them to upload and download data to/from the internet.
+secure.http_mods (HTTP Mods) string
+
+[*Advanced]
+
+[**Profiling]
+# Load the game profiler to collect game profiling data.
+# Provides a /profiler command to access the compiled profile.
+# Useful for mod developers and server operators.
+profiler.load (Load the game profiler) bool false
+
+# The default format in which profiles are being saved,
+# when calling `/profiler save [format]` without format.
+profiler.default_report_format (Default report format) enum txt txt,csv,lua,json,json_pretty
+
+# The file path relative to your worldpath in which profiles will be saved to.
+profiler.report_path (Report path) string ""
+
+[***Instrumentation]
+
+# Instrument the methods of entities on registration.
+instrument.entity (Entity methods) bool true
+
+# Instrument the action function of Active Block Modifiers on registration.
+instrument.abm (Active Block Modifiers) bool true
+
+# Instrument the action function of Loading Block Modifiers on registration.
+instrument.lbm (Loading Block Modifiers) bool true
+
+# Instrument chatcommands on registration.
+instrument.chatcommand (Chatcommands) bool true
+
+# Instrument global callback functions on registration.
+# (anything you pass to a minetest.register_*() function)
+instrument.global_callback (Global callbacks) bool true
+
+[****Advanced]
+# Instrument builtin.
+# This is usually only needed by core/builtin contributors
+instrument.builtin (Builtin) bool false
+
+# Have the profiler instrument itself:
+# * Instrument an empty function.
+# This estimates the overhead, that instrumentation is adding (+1 function call).
+# * Instrument the sampler being used to update the statistics.
+instrument.profiler (Profiler) bool false
+
+[Client and Server]
+
+# Name of the player.
+# When running a server, clients connecting with this name are admins.
+# When starting from the main menu, this is overridden.
+name (Player name) string
+
+# Set the language. Leave empty to use the system language.
+# A restart is required after changing this.
+language (Language) enum ,be,ca,cs,da,de,en,eo,es,et,fr,he,hu,id,it,ja,jbo,ko,ky,lt,nb,nl,pl,pt,pt_BR,ro,ru,sr_Cyrl,tr,uk,zh_CN,zh_TW
+
+# Level of logging to be written to debug.txt:
+# - <nothing> (no logging)
+# - none (messages with no level)
+# - error
+# - warning
+# - action
+# - info
+# - verbose
+debug_log_level (Debug log level) enum action ,none,error,warning,action,info,verbose
+
+# IPv6 support.
+enable_ipv6 (IPv6) bool true
+
+[*Advanced]
+
+# Default timeout for cURL, stated in milliseconds.
+# Only has an effect if compiled with cURL.
+curl_timeout (cURL timeout) int 5000
+
+# Limits number of parallel HTTP requests. Affects:
+# - Media fetch if server uses remote_media setting.
+# - Serverlist download and server announcement.
+# - Downloads performed by main menu (e.g. mod manager).
+# Only has an effect if compiled with cURL.
+curl_parallel_limit (cURL parallel limit) int 8
+
+# Maximum time in ms a file download (e.g. a mod download) may take.
+curl_file_download_timeout (cURL file download timeout) int 300000
+
+# Makes DirectX work with LuaJIT. Disable if it causes troubles.
+high_precision_fpu (High-precision FPU) bool true
+
+# Replaces the default main menu with a custom one.
+main_menu_script (Main menu script) string
+
+main_menu_game_mgr (Main menu game manager) int 0
+
+main_menu_mod_mgr (Main menu mod manager) int 1
+
+modstore_download_url (Modstore download URL) string https://forum.minetest.net/media/
+
+modstore_listmods_url (Modstore mods list URL) string https://forum.minetest.net/mmdb/mods/
+
+modstore_details_url (Modstore details URL) string https://forum.minetest.net/mmdb/mod/*/
+
+# Print the engine's profiling data in regular intervals (in seconds). 0 = disable. Useful for developers.
+profiler_print_interval (Engine profiling data print interval) int 0
diff --git a/client/serverlist/.gitignore b/client/serverlist/.gitignore
new file mode 100644
index 000000000..d6b7ef32c
--- /dev/null
+++ b/client/serverlist/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/client/shaders/default_shader/opengl_fragment.glsl b/client/shaders/default_shader/opengl_fragment.glsl
new file mode 100644
index 000000000..925ab6e1d
--- /dev/null
+++ b/client/shaders/default_shader/opengl_fragment.glsl
@@ -0,0 +1,4 @@
+void main(void)
+{
+ gl_FragColor = gl_Color;
+}
diff --git a/client/shaders/default_shader/opengl_vertex.glsl b/client/shaders/default_shader/opengl_vertex.glsl
new file mode 100644
index 000000000..d0b16c8b0
--- /dev/null
+++ b/client/shaders/default_shader/opengl_vertex.glsl
@@ -0,0 +1,9 @@
+uniform mat4 mWorldViewProj;
+
+void main(void)
+{
+ gl_TexCoord[0] = gl_MultiTexCoord0;
+ gl_Position = mWorldViewProj * gl_Vertex;
+
+ gl_FrontColor = gl_BackColor = gl_Color;
+}
diff --git a/client/shaders/minimap_shader/opengl_fragment.glsl b/client/shaders/minimap_shader/opengl_fragment.glsl
new file mode 100644
index 000000000..fa4f9cb1a
--- /dev/null
+++ b/client/shaders/minimap_shader/opengl_fragment.glsl
@@ -0,0 +1,32 @@
+uniform sampler2D baseTexture;
+uniform sampler2D normalTexture;
+uniform vec3 yawVec;
+
+void main (void)
+{
+ vec2 uv = gl_TexCoord[0].st;
+
+ //texture sampling rate
+ const float step = 1.0 / 256.0;
+ float tl = texture2D(normalTexture, vec2(uv.x - step, uv.y + step)).r;
+ float t = texture2D(normalTexture, vec2(uv.x - step, uv.y - step)).r;
+ float tr = texture2D(normalTexture, vec2(uv.x + step, uv.y + step)).r;
+ float r = texture2D(normalTexture, vec2(uv.x + step, uv.y)).r;
+ float br = texture2D(normalTexture, vec2(uv.x + step, uv.y - step)).r;
+ float b = texture2D(normalTexture, vec2(uv.x, uv.y - step)).r;
+ float bl = texture2D(normalTexture, vec2(uv.x - step, uv.y - step)).r;
+ float l = texture2D(normalTexture, vec2(uv.x - step, uv.y)).r;
+ float dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl);
+ float dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr);
+ vec4 bump = vec4 (normalize(vec3 (dX, dY, 0.1)),1.0);
+ float height = 2.0 * texture2D(normalTexture, vec2(uv.x, uv.y)).r - 1.0;
+ vec4 base = texture2D(baseTexture, uv).rgba;
+ vec3 L = normalize(vec3(0.0, 0.75, 1.0));
+ float specular = pow(clamp(dot(reflect(L, bump.xyz), yawVec), 0.0, 1.0), 1.0);
+ float diffuse = dot(yawVec, bump.xyz);
+
+ vec3 color = (1.1 * diffuse + 0.05 * height + 0.5 * specular) * base.rgb;
+ vec4 col = vec4(color.rgb, base.a);
+ col *= gl_Color;
+ gl_FragColor = vec4(col.rgb, base.a);
+}
diff --git a/client/shaders/minimap_shader/opengl_vertex.glsl b/client/shaders/minimap_shader/opengl_vertex.glsl
new file mode 100644
index 000000000..88f9356d5
--- /dev/null
+++ b/client/shaders/minimap_shader/opengl_vertex.glsl
@@ -0,0 +1,9 @@
+uniform mat4 mWorldViewProj;
+uniform mat4 mWorld;
+
+void main(void)
+{
+ gl_TexCoord[0] = gl_MultiTexCoord0;
+ gl_Position = mWorldViewProj * gl_Vertex;
+ gl_FrontColor = gl_BackColor = gl_Color;
+}
diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl
new file mode 100644
index 000000000..7c5b9b613
--- /dev/null
+++ b/client/shaders/nodes_shader/opengl_fragment.glsl
@@ -0,0 +1,219 @@
+uniform sampler2D baseTexture;
+uniform sampler2D normalTexture;
+uniform sampler2D textureFlags;
+
+uniform vec4 skyBgColor;
+uniform float fogDistance;
+uniform vec3 eyePosition;
+
+varying vec3 vPosition;
+varying vec3 worldPosition;
+varying float area_enable_parallax;
+
+varying vec3 eyeVec;
+varying vec3 tsEyeVec;
+varying vec3 lightVec;
+varying vec3 tsLightVec;
+
+bool normalTexturePresent = false;
+
+const float e = 2.718281828459;
+const float BS = 10.0;
+const float fogStart = FOG_START;
+const float fogShadingParameter = 1 / ( 1 - fogStart);
+
+#ifdef ENABLE_TONE_MAPPING
+
+/* Hable's UC2 Tone mapping parameters
+ A = 0.22;
+ B = 0.30;
+ C = 0.10;
+ D = 0.20;
+ E = 0.01;
+ F = 0.30;
+ W = 11.2;
+ equation used: ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F
+*/
+
+vec3 uncharted2Tonemap(vec3 x)
+{
+ return ((x * (0.22 * x + 0.03) + 0.002) / (x * (0.22 * x + 0.3) + 0.06)) - 0.03333;
+}
+
+vec4 applyToneMapping(vec4 color)
+{
+ color = vec4(pow(color.rgb, vec3(2.2)), color.a);
+ const float gamma = 1.6;
+ const float exposureBias = 5.5;
+ color.rgb = uncharted2Tonemap(exposureBias * color.rgb);
+ // Precalculated white_scale from
+ //vec3 whiteScale = 1.0 / uncharted2Tonemap(vec3(W));
+ vec3 whiteScale = vec3(1.036015346);
+ color.rgb *= whiteScale;
+ return vec4(pow(color.rgb, vec3(1.0 / gamma)), color.a);
+}
+#endif
+
+void get_texture_flags()
+{
+ vec4 flags = texture2D(textureFlags, vec2(0.0, 0.0));
+ if (flags.r > 0.5) {
+ normalTexturePresent = true;
+ }
+}
+
+float intensity(vec3 color)
+{
+ return (color.r + color.g + color.b) / 3.0;
+}
+
+float get_rgb_height(vec2 uv)
+{
+ return intensity(texture2D(baseTexture, uv).rgb);
+}
+
+vec4 get_normal_map(vec2 uv)
+{
+ vec4 bump = texture2D(normalTexture, uv).rgba;
+ bump.xyz = normalize(bump.xyz * 2.0 - 1.0);
+ return bump;
+}
+
+float find_intersection(vec2 dp, vec2 ds)
+{
+ float depth = 1.0;
+ float best_depth = 0.0;
+ float size = 0.0625;
+ for (int i = 0; i < 15; i++) {
+ depth -= size;
+ float h = texture2D(normalTexture, dp + ds * depth).a;
+ if (depth <= h) {
+ best_depth = depth;
+ break;
+ }
+ }
+ depth = best_depth;
+ for (int i = 0; i < 4; i++) {
+ size *= 0.5;
+ float h = texture2D(normalTexture,dp + ds * depth).a;
+ if (depth <= h) {
+ best_depth = depth;
+ depth += size;
+ } else {
+ depth -= size;
+ }
+ }
+ return best_depth;
+}
+
+float find_intersectionRGB(vec2 dp, vec2 ds)
+{
+ const float depth_step = 1.0 / 24.0;
+ float depth = 1.0;
+ for (int i = 0 ; i < 24 ; i++) {
+ float h = get_rgb_height(dp + ds * depth);
+ if (h >= depth)
+ break;
+ depth -= depth_step;
+ }
+ return depth;
+}
+
+void main(void)
+{
+ vec3 color;
+ vec4 bump;
+ vec2 uv = gl_TexCoord[0].st;
+ bool use_normalmap = false;
+ get_texture_flags();
+
+#ifdef ENABLE_PARALLAX_OCCLUSION
+ vec2 eyeRay = vec2 (tsEyeVec.x, -tsEyeVec.y);
+ const float scale = PARALLAX_OCCLUSION_SCALE / PARALLAX_OCCLUSION_ITERATIONS;
+ const float bias = PARALLAX_OCCLUSION_BIAS / PARALLAX_OCCLUSION_ITERATIONS;
+
+#if PARALLAX_OCCLUSION_MODE == 0
+ // Parallax occlusion with slope information
+ if (normalTexturePresent && area_enable_parallax > 0.0) {
+ for (int i = 0; i < PARALLAX_OCCLUSION_ITERATIONS; i++) {
+ vec4 normal = texture2D(normalTexture, uv.xy);
+ float h = normal.a * scale - bias;
+ uv += h * normal.z * eyeRay;
+ }
+#endif
+
+#if PARALLAX_OCCLUSION_MODE == 1
+ // Relief mapping
+ if (normalTexturePresent && area_enable_parallax > 0.0) {
+ vec2 ds = eyeRay * PARALLAX_OCCLUSION_SCALE;
+ float dist = find_intersection(uv, ds);
+ uv += dist * ds;
+#endif
+ } else if (GENERATE_NORMALMAPS == 1 && area_enable_parallax > 0.0) {
+ vec2 ds = eyeRay * PARALLAX_OCCLUSION_SCALE;
+ float dist = find_intersectionRGB(uv, ds);
+ uv += dist * ds;
+ }
+#endif
+
+#if USE_NORMALMAPS == 1
+ if (normalTexturePresent) {
+ bump = get_normal_map(uv);
+ use_normalmap = true;
+ }
+#endif
+
+#if GENERATE_NORMALMAPS == 1
+ if (normalTexturePresent == false) {
+ float tl = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y + SAMPLE_STEP));
+ float t = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y - SAMPLE_STEP));
+ float tr = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y + SAMPLE_STEP));
+ float r = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y));
+ float br = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y - SAMPLE_STEP));
+ float b = get_rgb_height(vec2(uv.x, uv.y - SAMPLE_STEP));
+ float bl = get_rgb_height(vec2(uv.x -SAMPLE_STEP, uv.y - SAMPLE_STEP));
+ float l = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y));
+ float dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl);
+ float dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr);
+ bump = vec4(normalize(vec3 (dX, dY, NORMALMAPS_STRENGTH)), 1.0);
+ use_normalmap = true;
+ }
+#endif
+ vec4 base = texture2D(baseTexture, uv).rgba;
+
+#ifdef ENABLE_BUMPMAPPING
+ if (use_normalmap) {
+ vec3 L = normalize(lightVec);
+ vec3 E = normalize(eyeVec);
+ float specular = pow(clamp(dot(reflect(L, bump.xyz), E), 0.0, 1.0), 1.0);
+ float diffuse = dot(-E,bump.xyz);
+ color = (diffuse + 0.1 * specular) * base.rgb;
+ } else {
+ color = base.rgb;
+ }
+#else
+ color = base.rgb;
+#endif
+
+ vec4 col = vec4(color.rgb * gl_Color.rgb, 1.0);
+
+#ifdef ENABLE_TONE_MAPPING
+ col = applyToneMapping(col);
+#endif
+
+ // Due to a bug in some (older ?) graphics stacks (possibly in the glsl compiler ?),
+ // the fog will only be rendered correctly if the last operation before the
+ // clamp() is an addition. Else, the clamp() seems to be ignored.
+ // E.g. the following won't work:
+ // float clarity = clamp(fogShadingParameter
+ // * (fogDistance - length(eyeVec)) / fogDistance), 0.0, 1.0);
+ // As additions usually come for free following a multiplication, the new formula
+ // should be more efficient as well.
+ // Note: clarity = (1 - fogginess)
+ float clarity = clamp(fogShadingParameter
+ - fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
+ col = mix(skyBgColor, col, clarity);
+ col = vec4(col.rgb, base.a);
+
+ gl_FragColor = col;
+}
diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl
new file mode 100644
index 000000000..3ac79c26d
--- /dev/null
+++ b/client/shaders/nodes_shader/opengl_vertex.glsl
@@ -0,0 +1,144 @@
+uniform mat4 mWorldViewProj;
+uniform mat4 mWorld;
+
+// Color of the light emitted by the sun.
+uniform vec3 dayLight;
+uniform vec3 eyePosition;
+uniform float animationTimer;
+
+varying vec3 vPosition;
+varying vec3 worldPosition;
+
+varying vec3 eyeVec;
+varying vec3 lightVec;
+varying vec3 tsEyeVec;
+varying vec3 tsLightVec;
+varying float area_enable_parallax;
+
+// Color of the light emitted by the light sources.
+const vec3 artificialLight = vec3(1.04, 1.04, 1.04);
+const float e = 2.718281828459;
+const float BS = 10.0;
+
+
+float smoothCurve(float x)
+{
+ return x * x * (3.0 - 2.0 * x);
+}
+
+
+float triangleWave(float x)
+{
+ return abs(fract(x + 0.5) * 2.0 - 1.0);
+}
+
+
+float smoothTriangleWave(float x)
+{
+ return smoothCurve(triangleWave(x)) * 2.0 - 1.0;
+}
+
+
+void main(void)
+{
+ gl_TexCoord[0] = gl_MultiTexCoord0;
+ //TODO: make offset depending on view angle and parallax uv displacement
+ //thats for textures that doesnt align vertically, like dirt with grass
+ //gl_TexCoord[0].y += 0.008;
+
+ //Allow parallax/relief mapping only for certain kind of nodes
+ //Variable is also used to control area of the effect
+#if (DRAW_TYPE == NDT_NORMAL || DRAW_TYPE == NDT_LIQUID || DRAW_TYPE == NDT_FLOWINGLIQUID)
+ area_enable_parallax = 1.0;
+#else
+ area_enable_parallax = 0.0;
+#endif
+
+
+float disp_x;
+float disp_z;
+#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES && ENABLE_WAVING_LEAVES) || (MATERIAL_TYPE == TILE_MATERIAL_WAVING_PLANTS && ENABLE_WAVING_PLANTS)
+ vec4 pos2 = mWorld * gl_Vertex;
+ float tOffset = (pos2.x + pos2.y) * 0.001 + pos2.z * 0.002;
+ disp_x = (smoothTriangleWave(animationTimer * 23.0 + tOffset) +
+ smoothTriangleWave(animationTimer * 11.0 + tOffset)) * 0.4;
+ disp_z = (smoothTriangleWave(animationTimer * 31.0 + tOffset) +
+ smoothTriangleWave(animationTimer * 29.0 + tOffset) +
+ smoothTriangleWave(animationTimer * 13.0 + tOffset)) * 0.5;
+#endif
+
+
+#if (MATERIAL_TYPE == TILE_MATERIAL_LIQUID_TRANSPARENT || MATERIAL_TYPE == TILE_MATERIAL_LIQUID_OPAQUE) && ENABLE_WAVING_WATER
+ vec4 pos = gl_Vertex;
+ pos.y -= 2.0;
+ float posYbuf = (pos.z / WATER_WAVE_LENGTH + animationTimer * WATER_WAVE_SPEED * WATER_WAVE_LENGTH);
+ pos.y -= sin(posYbuf) * WATER_WAVE_HEIGHT + sin(posYbuf / 7.0) * WATER_WAVE_HEIGHT;
+ gl_Position = mWorldViewProj * pos;
+#elif MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES && ENABLE_WAVING_LEAVES
+ vec4 pos = gl_Vertex;
+ pos.x += disp_x;
+ pos.y += disp_z * 0.1;
+ pos.z += disp_z;
+ gl_Position = mWorldViewProj * pos;
+#elif MATERIAL_TYPE == TILE_MATERIAL_WAVING_PLANTS && ENABLE_WAVING_PLANTS
+ vec4 pos = gl_Vertex;
+ if (gl_TexCoord[0].y < 0.05) {
+ pos.x += disp_x;
+ pos.z += disp_z;
+ }
+ gl_Position = mWorldViewProj * pos;
+#else
+ gl_Position = mWorldViewProj * gl_Vertex;
+#endif
+
+
+ vPosition = gl_Position.xyz;
+ worldPosition = (mWorld * gl_Vertex).xyz;
+
+ // Don't generate heightmaps when too far from the eye
+ float dist = distance (vec3(0.0, 0.0, 0.0), vPosition);
+ if (dist > 150.0) {
+ area_enable_parallax = 0.0;
+ }
+
+ vec3 sunPosition = vec3 (0.0, eyePosition.y * BS + 900.0, 0.0);
+
+ vec3 normal, tangent, binormal;
+ normal = normalize(gl_NormalMatrix * gl_Normal);
+ tangent = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz);
+ binormal = normalize(gl_NormalMatrix * gl_MultiTexCoord2.xyz);
+
+ vec3 v;
+
+ lightVec = sunPosition - worldPosition;
+ v.x = dot(lightVec, tangent);
+ v.y = dot(lightVec, binormal);
+ v.z = dot(lightVec, normal);
+ tsLightVec = normalize (v);
+
+ eyeVec = -(gl_ModelViewMatrix * gl_Vertex).xyz;
+ v.x = dot(eyeVec, tangent);
+ v.y = dot(eyeVec, binormal);
+ v.z = dot(eyeVec, normal);
+ tsEyeVec = normalize (v);
+
+ // Calculate color.
+ // Red, green and blue components are pre-multiplied with
+ // the brightness, so now we have to multiply these
+ // colors with the color of the incoming light.
+ // The pre-baked colors are halved to prevent overflow.
+ vec4 color;
+ // The alpha gives the ratio of sunlight in the incoming light.
+ float nightRatio = 1 - gl_Color.a;
+ color.rgb = gl_Color.rgb * (gl_Color.a * dayLight.rgb +
+ nightRatio * artificialLight.rgb) * 2;
+ color.a = 1;
+
+ // Emphase blue a bit in darker places
+ // See C++ implementation in mapblock_mesh.cpp finalColorBlend()
+ float brightness = (color.r + color.g + color.b) / 3;
+ color.b += max(0.0, 0.021 - abs(0.2 * brightness - 0.021) +
+ 0.07 * brightness);
+
+ gl_FrontColor = gl_BackColor = clamp(color, 0.0, 1.0);
+}
diff --git a/client/shaders/selection_shader/opengl_fragment.glsl b/client/shaders/selection_shader/opengl_fragment.glsl
new file mode 100644
index 000000000..c679d0e12
--- /dev/null
+++ b/client/shaders/selection_shader/opengl_fragment.glsl
@@ -0,0 +1,9 @@
+uniform sampler2D baseTexture;
+
+void main(void)
+{
+ vec2 uv = gl_TexCoord[0].st;
+ vec4 color = texture2D(baseTexture, uv);
+ color.rgb *= gl_Color.rgb;
+ gl_FragColor = color;
+}
diff --git a/client/shaders/selection_shader/opengl_vertex.glsl b/client/shaders/selection_shader/opengl_vertex.glsl
new file mode 100644
index 000000000..d0b16c8b0
--- /dev/null
+++ b/client/shaders/selection_shader/opengl_vertex.glsl
@@ -0,0 +1,9 @@
+uniform mat4 mWorldViewProj;
+
+void main(void)
+{
+ gl_TexCoord[0] = gl_MultiTexCoord0;
+ gl_Position = mWorldViewProj * gl_Vertex;
+
+ gl_FrontColor = gl_BackColor = gl_Color;
+}
diff --git a/client/shaders/wielded_shader/opengl_fragment.glsl b/client/shaders/wielded_shader/opengl_fragment.glsl
new file mode 100644
index 000000000..546aef71d
--- /dev/null
+++ b/client/shaders/wielded_shader/opengl_fragment.glsl
@@ -0,0 +1,127 @@
+uniform sampler2D baseTexture;
+uniform sampler2D normalTexture;
+uniform sampler2D textureFlags;
+
+uniform vec4 skyBgColor;
+uniform float fogDistance;
+uniform vec3 eyePosition;
+
+varying vec3 vPosition;
+varying vec3 worldPosition;
+
+varying vec3 eyeVec;
+varying vec3 lightVec;
+
+bool normalTexturePresent = false;
+bool texTileableHorizontal = false;
+bool texTileableVertical = false;
+bool texSeamless = false;
+
+const float e = 2.718281828459;
+const float BS = 10.0;
+const float fogStart = FOG_START;
+const float fogShadingParameter = 1 / ( 1 - fogStart);
+
+void get_texture_flags()
+{
+ vec4 flags = texture2D(textureFlags, vec2(0.0, 0.0));
+ if (flags.r > 0.5) {
+ normalTexturePresent = true;
+ }
+ if (flags.g > 0.5) {
+ texTileableHorizontal = true;
+ }
+ if (flags.b > 0.5) {
+ texTileableVertical = true;
+ }
+ if (texTileableHorizontal && texTileableVertical) {
+ texSeamless = true;
+ }
+}
+
+float intensity(vec3 color)
+{
+ return (color.r + color.g + color.b) / 3.0;
+}
+
+float get_rgb_height(vec2 uv)
+{
+ if (texSeamless) {
+ return intensity(texture2D(baseTexture, uv).rgb);
+ } else {
+ return intensity(texture2D(baseTexture, clamp(uv, 0.0, 0.999)).rgb);
+ }
+}
+
+vec4 get_normal_map(vec2 uv)
+{
+ vec4 bump = texture2D(normalTexture, uv).rgba;
+ bump.xyz = normalize(bump.xyz * 2.0 - 1.0);
+ return bump;
+}
+
+void main(void)
+{
+ vec3 color;
+ vec4 bump;
+ vec2 uv = gl_TexCoord[0].st;
+ bool use_normalmap = false;
+ get_texture_flags();
+
+#if USE_NORMALMAPS == 1
+ if (normalTexturePresent) {
+ bump = get_normal_map(uv);
+ use_normalmap = true;
+ }
+#endif
+
+#if GENERATE_NORMALMAPS == 1
+ if (normalTexturePresent == false) {
+ float tl = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y + SAMPLE_STEP));
+ float t = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y - SAMPLE_STEP));
+ float tr = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y + SAMPLE_STEP));
+ float r = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y));
+ float br = get_rgb_height(vec2(uv.x + SAMPLE_STEP, uv.y - SAMPLE_STEP));
+ float b = get_rgb_height(vec2(uv.x, uv.y - SAMPLE_STEP));
+ float bl = get_rgb_height(vec2(uv.x -SAMPLE_STEP, uv.y - SAMPLE_STEP));
+ float l = get_rgb_height(vec2(uv.x - SAMPLE_STEP, uv.y));
+ float dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl);
+ float dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr);
+ bump = vec4(normalize(vec3 (dX, dY, NORMALMAPS_STRENGTH)), 1.0);
+ use_normalmap = true;
+ }
+#endif
+
+ vec4 base = texture2D(baseTexture, uv).rgba;
+
+#ifdef ENABLE_BUMPMAPPING
+ if (use_normalmap) {
+ vec3 L = normalize(lightVec);
+ vec3 E = normalize(eyeVec);
+ float specular = pow(clamp(dot(reflect(L, bump.xyz), E), 0.0, 1.0), 1.0);
+ float diffuse = dot(-E,bump.xyz);
+ color = (diffuse + 0.1 * specular) * base.rgb;
+ } else {
+ color = base.rgb;
+ }
+#else
+ color = base.rgb;
+#endif
+
+ vec4 col = vec4(color.rgb, base.a);
+ col *= gl_Color;
+ // Due to a bug in some (older ?) graphics stacks (possibly in the glsl compiler ?),
+ // the fog will only be rendered correctly if the last operation before the
+ // clamp() is an addition. Else, the clamp() seems to be ignored.
+ // E.g. the following won't work:
+ // float clarity = clamp(fogShadingParameter
+ // * (fogDistance - length(eyeVec)) / fogDistance), 0.0, 1.0);
+ // As additions usually come for free following a multiplication, the new formula
+ // should be more efficient as well.
+ // Note: clarity = (1 - fogginess)
+ float clarity = clamp(fogShadingParameter
+ - fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
+ col = mix(skyBgColor, col, clarity);
+
+ gl_FragColor = vec4(col.rgb, base.a);
+}
diff --git a/client/shaders/wielded_shader/opengl_vertex.glsl b/client/shaders/wielded_shader/opengl_vertex.glsl
new file mode 100644
index 000000000..9f05b833a
--- /dev/null
+++ b/client/shaders/wielded_shader/opengl_vertex.glsl
@@ -0,0 +1,32 @@
+uniform mat4 mWorldViewProj;
+uniform mat4 mWorld;
+
+uniform vec3 eyePosition;
+uniform float animationTimer;
+
+varying vec3 vPosition;
+varying vec3 worldPosition;
+
+varying vec3 eyeVec;
+varying vec3 lightVec;
+varying vec3 tsEyeVec;
+varying vec3 tsLightVec;
+
+const float e = 2.718281828459;
+const float BS = 10.0;
+
+void main(void)
+{
+ gl_TexCoord[0] = gl_MultiTexCoord0;
+ gl_Position = mWorldViewProj * gl_Vertex;
+
+ vPosition = gl_Position.xyz;
+ worldPosition = (mWorld * gl_Vertex).xyz;
+
+ vec3 sunPosition = vec3 (0.0, eyePosition.y * BS + 900.0, 0.0);
+
+ lightVec = sunPosition - worldPosition;
+ eyeVec = -(gl_ModelViewMatrix * gl_Vertex).xyz;
+
+ gl_FrontColor = gl_BackColor = gl_Color;
+}
diff --git a/clientmods/preview/init.lua b/clientmods/preview/init.lua
new file mode 100644
index 000000000..f3992612a
--- /dev/null
+++ b/clientmods/preview/init.lua
@@ -0,0 +1,152 @@
+local modname = core.get_current_modname() or "??"
+local modstorage = core.get_mod_storage()
+
+-- This is an example function to ensure it's working properly, should be removed before merge
+core.register_on_shutdown(function()
+ print("[PREVIEW] shutdown client")
+end)
+
+core.register_on_connect(function()
+ print("[PREVIEW] Player connection completed")
+ local server_info = core.get_server_info()
+ print("Server version: " .. server_info.protocol_version)
+ print("Server ip: " .. server_info.ip)
+ print("Server address: " .. server_info.address)
+ print("Server port: " .. server_info.port)
+end)
+
+core.register_on_placenode(function(pointed_thing, node)
+ print("The local player place a node!")
+ print("pointed_thing :" .. dump(pointed_thing))
+ print("node placed :" .. dump(node))
+ return false
+end)
+
+core.register_on_item_use(function(itemstack, pointed_thing)
+ print("The local player used an item!")
+ print("pointed_thing :" .. dump(pointed_thing))
+ print("item = " .. itemstack:get_name())
+ return false
+end)
+
+-- This is an example function to ensure it's working properly, should be removed before merge
+core.register_on_receiving_chat_messages(function(message)
+ print("[PREVIEW] Received message " .. message)
+ return false
+end)
+
+-- This is an example function to ensure it's working properly, should be removed before merge
+core.register_on_sending_chat_messages(function(message)
+ print("[PREVIEW] Sending message " .. message)
+ return false
+end)
+
+-- This is an example function to ensure it's working properly, should be removed before merge
+core.register_on_hp_modification(function(hp)
+ print("[PREVIEW] HP modified " .. hp)
+end)
+
+-- This is an example function to ensure it's working properly, should be removed before merge
+core.register_on_damage_taken(function(hp)
+ print("[PREVIEW] Damage taken " .. hp)
+end)
+
+-- This is an example function to ensure it's working properly, should be removed before merge
+core.register_globalstep(function(dtime)
+ -- print("[PREVIEW] globalstep " .. dtime)
+end)
+
+-- This is an example function to ensure it's working properly, should be removed before merge
+core.register_chatcommand("dump", {
+ func = function(param)
+ return true, dump(_G)
+ end,
+})
+
+core.register_chatcommand("colorize_test", {
+ func = function(param)
+ return true, core.colorize("red", param)
+ end,
+})
+
+core.register_chatcommand("test_node", {
+ func = function(param)
+ core.display_chat_message(dump(core.get_node({x=0, y=0, z=0})))
+ core.display_chat_message(dump(core.get_node_or_nil({x=0, y=0, z=0})))
+ end,
+})
+
+local function preview_minimap()
+ local minimap = core.ui.minimap
+ if not minimap then
+ print("[PREVIEW] Minimap is disabled. Skipping.")
+ return
+ end
+ minimap:set_mode(4)
+ minimap:show()
+ minimap:set_pos({x=5, y=50, z=5})
+ minimap:set_shape(math.random(0, 1))
+
+ print("[PREVIEW] Minimap: mode => " .. dump(minimap:get_mode()) ..
+ " position => " .. dump(minimap:get_pos()) ..
+ " angle => " .. dump(minimap:get_angle()))
+end
+
+core.after(2, function()
+ print("[PREVIEW] loaded " .. modname .. " mod")
+ modstorage:set_string("current_mod", modname)
+ print(modstorage:get_string("current_mod"))
+ preview_minimap()
+end)
+
+core.after(5, function()
+ if core.ui.minimap then
+ core.ui.minimap:show()
+ end
+
+ print("[PREVIEW] Day count: " .. core.get_day_count() ..
+ " time of day " .. core.get_timeofday())
+
+ print("[PREVIEW] Node level: " .. core.get_node_level({x=0, y=20, z=0}) ..
+ " max level " .. core.get_node_max_level({x=0, y=20, z=0}))
+
+ print("[PREVIEW] Find node near: " .. dump(core.find_node_near({x=0, y=20, z=0}, 10,
+ {"group:tree", "default:dirt", "default:stone"})))
+end)
+
+core.register_on_dignode(function(pos, node)
+ print("The local player dug a node!")
+ print("pos:" .. dump(pos))
+ print("node:" .. dump(node))
+ return false
+end)
+
+core.register_on_punchnode(function(pos, node)
+ print("The local player punched a node!")
+ local itemstack = core.get_wielded_item()
+ --[[
+ -- getters
+ print(dump(itemstack:is_empty()))
+ print(dump(itemstack:get_name()))
+ print(dump(itemstack:get_count()))
+ print(dump(itemstack:get_wear()))
+ print(dump(itemstack:get_meta()))
+ print(dump(itemstack:get_metadata()
+ print(dump(itemstack:is_known()))
+ --print(dump(itemstack:get_definition()))
+ print(dump(itemstack:get_tool_capabilities()))
+ print(dump(itemstack:to_string()))
+ print(dump(itemstack:to_table()))
+ -- setters
+ print(dump(itemstack:set_name("default:dirt")))
+ print(dump(itemstack:set_count("95")))
+ print(dump(itemstack:set_wear(934)))
+ print(dump(itemstack:get_meta()))
+ print(dump(itemstack:get_metadata()))
+ --]]
+ print(dump(itemstack:to_table()))
+ print("pos:" .. dump(pos))
+ print("node:" .. dump(node))
+ return false
+end)
+
diff --git a/cmake/Modules/FindCURL.cmake b/cmake/Modules/FindCURL.cmake
new file mode 100644
index 000000000..2ec866ef9
--- /dev/null
+++ b/cmake/Modules/FindCURL.cmake
@@ -0,0 +1,19 @@
+mark_as_advanced(CURL_LIBRARY CURL_INCLUDE_DIR)
+
+find_library(CURL_LIBRARY NAMES curl)
+find_path(CURL_INCLUDE_DIR NAMES curl/curl.h)
+
+set(CURL_REQUIRED_VARS CURL_LIBRARY CURL_INCLUDE_DIR)
+
+if(WIN32)
+ find_file(CURL_DLL NAMES libcurl-4.dll
+ PATHS
+ "C:/Windows/System32"
+ DOC "Path to the cURL DLL (for installation)")
+ mark_as_advanced(CURL_DLL)
+ set(CURL_REQUIRED_VARS ${CURL_REQUIRED_VARS} CURL_DLL)
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(CURL DEFAULT_MSG ${CURL_REQUIRED_VARS})
+
diff --git a/cmake/Modules/FindGMP.cmake b/cmake/Modules/FindGMP.cmake
new file mode 100644
index 000000000..64a57cfe0
--- /dev/null
+++ b/cmake/Modules/FindGMP.cmake
@@ -0,0 +1,27 @@
+option(ENABLE_SYSTEM_GMP "Use GMP from system" TRUE)
+mark_as_advanced(GMP_LIBRARY GMP_INCLUDE_DIR)
+set(USE_SYSTEM_GMP FALSE)
+
+if(ENABLE_SYSTEM_GMP)
+ find_library(GMP_LIBRARY NAMES libgmp.so)
+ find_path(GMP_INCLUDE_DIR NAMES gmp.h)
+
+ if(GMP_LIBRARY AND GMP_INCLUDE_DIR)
+ message (STATUS "Using GMP provided by system.")
+ set(USE_SYSTEM_GMP TRUE)
+ else()
+ message (STATUS "Detecting GMP from system failed.")
+ endif()
+else()
+ message (STATUS "Detecting GMP from system disabled! (ENABLE_SYSTEM_GMP=0)")
+endif()
+
+if(NOT USE_SYSTEM_GMP)
+ message(STATUS "Using bundled mini-gmp library.")
+ set(GMP_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/gmp)
+ set(GMP_LIBRARY gmp)
+ add_subdirectory(lib/gmp)
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(GMP DEFAULT_MSG GMP_LIBRARY GMP_INCLUDE_DIR)
diff --git a/cmake/Modules/FindGettextLib.cmake b/cmake/Modules/FindGettextLib.cmake
new file mode 100644
index 000000000..cb1ce7b91
--- /dev/null
+++ b/cmake/Modules/FindGettextLib.cmake
@@ -0,0 +1,78 @@
+
+set(CUSTOM_GETTEXT_PATH "${PROJECT_SOURCE_DIR}/../../gettext"
+ CACHE FILEPATH "path to custom gettext")
+
+find_path(GETTEXT_INCLUDE_DIR
+ NAMES libintl.h
+ PATHS "${CUSTOM_GETTEXT_PATH}/include"
+ DOC "GetText include directory")
+
+find_program(GETTEXT_MSGFMT
+ NAMES msgfmt
+ PATHS "${CUSTOM_GETTEXT_PATH}/bin"
+ DOC "Path to msgfmt")
+
+set(GETTEXT_REQUIRED_VARS GETTEXT_INCLUDE_DIR GETTEXT_MSGFMT)
+
+if(APPLE)
+ find_library(GETTEXT_LIBRARY
+ NAMES libintl.a
+ PATHS "${CUSTOM_GETTEXT_PATH}/lib"
+ DOC "GetText library")
+
+ find_library(ICONV_LIBRARY
+ NAMES libiconv.dylib
+ PATHS "/usr/lib"
+ DOC "IConv library")
+ set(GETTEXT_REQUIRED_VARS ${GETTEXT_REQUIRED_VARS} GETTEXT_LIBRARY ICONV_LIBRARY)
+endif(APPLE)
+
+# Modern Linux, as well as OSX, does not require special linking because
+# GetText is part of glibc.
+# TODO: check the requirements on other BSDs and older Linux
+if(WIN32)
+ if(MSVC)
+ set(GETTEXT_LIB_NAMES
+ libintl.lib intl.lib libintl3.lib intl3.lib)
+ else()
+ set(GETTEXT_LIB_NAMES
+ libintl.dll.a intl.dll.a libintl3.dll.a intl3.dll.a)
+ endif()
+ find_library(GETTEXT_LIBRARY
+ NAMES ${GETTEXT_LIB_NAMES}
+ PATHS "${CUSTOM_GETTEXT_PATH}/lib"
+ DOC "GetText library")
+ find_file(GETTEXT_DLL
+ NAMES libintl.dll intl.dll libintl3.dll intl3.dll
+ PATHS "${CUSTOM_GETTEXT_PATH}/bin" "${CUSTOM_GETTEXT_PATH}/lib"
+ DOC "gettext *intl*.dll")
+ find_file(GETTEXT_ICONV_DLL
+ NAMES libiconv2.dll
+ PATHS "${CUSTOM_GETTEXT_PATH}/bin" "${CUSTOM_GETTEXT_PATH}/lib"
+ DOC "gettext *iconv*.lib")
+ set(GETTEXT_REQUIRED_VARS ${GETTEXT_REQUIRED_VARS} GETTEXT_DLL GETTEXT_ICONV_DLL)
+endif(WIN32)
+
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(GetText DEFAULT_MSG ${GETTEXT_REQUIRED_VARS})
+
+
+if(GETTEXT_FOUND)
+ # BSD variants require special linkage as they don't use glibc
+ if(${CMAKE_SYSTEM_NAME} MATCHES "BSD")
+ set(GETTEXT_LIBRARY "intl")
+ endif()
+
+ set(GETTEXT_PO_PATH ${CMAKE_SOURCE_DIR}/po)
+ set(GETTEXT_MO_BUILD_PATH ${CMAKE_BINARY_DIR}/locale/<locale>/LC_MESSAGES)
+ set(GETTEXT_MO_DEST_PATH ${LOCALEDIR}/<locale>/LC_MESSAGES)
+ file(GLOB GETTEXT_AVAILABLE_LOCALES RELATIVE ${GETTEXT_PO_PATH} "${GETTEXT_PO_PATH}/*")
+ list(REMOVE_ITEM GETTEXT_AVAILABLE_LOCALES minetest.pot)
+ list(REMOVE_ITEM GETTEXT_AVAILABLE_LOCALES timestamp)
+ macro(SET_MO_PATHS _buildvar _destvar _locale)
+ string(REPLACE "<locale>" ${_locale} ${_buildvar} ${GETTEXT_MO_BUILD_PATH})
+ string(REPLACE "<locale>" ${_locale} ${_destvar} ${GETTEXT_MO_DEST_PATH})
+ endmacro()
+endif()
+
diff --git a/cmake/Modules/FindIrrlicht.cmake b/cmake/Modules/FindIrrlicht.cmake
index 246e72cfb..8dda15722 100644
--- a/cmake/Modules/FindIrrlicht.cmake
+++ b/cmake/Modules/FindIrrlicht.cmake
@@ -1,64 +1,71 @@
-#FindIrrlicht.cmake
+mark_as_advanced(IRRLICHT_LIBRARY IRRLICHT_INCLUDE_DIR IRRLICHT_DLL)
set(IRRLICHT_SOURCE_DIR "" CACHE PATH "Path to irrlicht source directory (optional)")
-if( UNIX )
- # Unix
-else( UNIX )
- # Windows
-endif( UNIX )
# Find include directory
-FIND_PATH(IRRLICHT_INCLUDE_DIR NAMES irrlicht.h
- PATHS
- /usr/local/include/irrlicht
- /usr/include/irrlicht
- "${IRRLICHT_SOURCE_DIR}/include"
-)
+if(NOT IRRLICHT_SOURCE_DIR STREQUAL "")
+ set(IRRLICHT_SOURCE_DIR_INCLUDE
+ "${IRRLICHT_SOURCE_DIR}/include"
+ )
-# Find library directory
+ set(IRRLICHT_LIBRARY_NAMES libIrrlicht.a Irrlicht Irrlicht.lib)
-FIND_LIBRARY(IRRLICHT_LIBRARY NAMES libIrrlicht.a Irrlicht
- PATHS
- /usr/local/lib
- /usr/lib
- #${IRRLICHT_PLATFORM_DIR}
- "${IRRLICHT_SOURCE_DIR}/lib/Win32-visualstudio"
- "${IRRLICHT_SOURCE_DIR}/lib/Win32-gcc"
-)
+ if(WIN32)
+ if(MSVC)
+ set(IRRLICHT_SOURCE_DIR_LIBS "${IRRLICHT_SOURCE_DIR}/lib/Win32-visualstudio")
+ set(IRRLICHT_LIBRARY_NAMES Irrlicht.lib)
+ else()
+ set(IRRLICHT_SOURCE_DIR_LIBS "${IRRLICHT_SOURCE_DIR}/lib/Win32-gcc")
+ set(IRRLICHT_LIBRARY_NAMES libIrrlicht.a libIrrlicht.dll.a)
+ endif()
+ else()
+ set(IRRLICHT_SOURCE_DIR_LIBS "${IRRLICHT_SOURCE_DIR}/lib/Linux")
+ set(IRRLICHT_LIBRARY_NAMES libIrrlicht.a)
+ endif()
+
+ find_path(IRRLICHT_INCLUDE_DIR NAMES irrlicht.h
+ PATHS
+ ${IRRLICHT_SOURCE_DIR_INCLUDE}
+ NO_DEFAULT_PATH
+ )
+
+ find_library(IRRLICHT_LIBRARY NAMES ${IRRLICHT_LIBRARY_NAMES}
+ PATHS
+ ${IRRLICHT_SOURCE_DIR_LIBS}
+ NO_DEFAULT_PATH
+ )
-MESSAGE(STATUS "IRRLICHT_INCLUDE_DIR = ${IRRLICHT_INCLUDE_DIR}")
-MESSAGE(STATUS "IRRLICHT_LIBRARY = ${IRRLICHT_LIBRARY}")
+else()
+ find_path(IRRLICHT_INCLUDE_DIR NAMES irrlicht.h
+ PATHS
+ /usr/local/include/irrlicht
+ /usr/include/irrlicht
+ )
-# On windows, find the dll for installation
+ find_library(IRRLICHT_LIBRARY NAMES libIrrlicht.so libIrrlicht.a Irrlicht
+ PATHS
+ /usr/local/lib
+ /usr/lib
+ )
+endif()
+
+
+# On Windows, find the DLL for installation
if(WIN32)
if(MSVC)
- FIND_FILE(IRRLICHT_DLL NAMES Irrlicht.dll
- PATHS
- "${IRRLICHT_SOURCE_DIR}/bin/Win32-VisualStudio"
- DOC "Path of the Irrlicht dll (for installation)"
- )
+ set(IRRLICHT_COMPILER "VisualStudio")
else()
- FIND_FILE(IRRLICHT_DLL NAMES Irrlicht.dll
- PATHS
- "${IRRLICHT_SOURCE_DIR}/bin/Win32-gcc"
- DOC "Path of the Irrlicht dll (for installation)"
- )
+ set(IRRLICHT_COMPILER "gcc")
endif()
- MESSAGE(STATUS "IRRLICHT_DLL = ${IRRLICHT_DLL}")
+ find_file(IRRLICHT_DLL NAMES Irrlicht.dll
+ PATHS
+ "${IRRLICHT_SOURCE_DIR}/bin/Win32-${IRRLICHT_COMPILER}"
+ DOC "Path of the Irrlicht dll (for installation)"
+ )
endif(WIN32)
-# handle the QUIETLY and REQUIRED arguments and set IRRLICHT_FOUND to TRUE if
-# all listed variables are TRUE
-INCLUDE(FindPackageHandleStandardArgs)
-FIND_PACKAGE_HANDLE_STANDARD_ARGS(IRRLICHT DEFAULT_MSG IRRLICHT_LIBRARY IRRLICHT_INCLUDE_DIR)
-
-IF(IRRLICHT_FOUND)
- SET(IRRLICHT_LIBRARIES ${IRRLICHT_LIBRARY})
-ELSE(IRRLICHT_FOUND)
- SET(IRRLICHT_LIBRARIES)
-ENDIF(IRRLICHT_FOUND)
-
-MARK_AS_ADVANCED(IRRLICHT_LIBRARY IRRLICHT_INCLUDE_DIR IRRLICHT_DLL)
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Irrlicht DEFAULT_MSG IRRLICHT_LIBRARY IRRLICHT_INCLUDE_DIR)
diff --git a/cmake/Modules/FindJson.cmake b/cmake/Modules/FindJson.cmake
new file mode 100644
index 000000000..26339a295
--- /dev/null
+++ b/cmake/Modules/FindJson.cmake
@@ -0,0 +1,26 @@
+# Look for JSONCPP if asked to.
+# We use a bundled version by default because some distros ship versions of
+# JSONCPP that cause segfaults and other memory errors when we link with them.
+# See https://github.com/minetest/minetest/issues/1793
+
+mark_as_advanced(JSON_LIBRARY JSON_INCLUDE_DIR)
+option(ENABLE_SYSTEM_JSONCPP "Enable using a system-wide JSONCPP. May cause segfaults and other memory errors!" FALSE)
+
+if(ENABLE_SYSTEM_JSONCPP)
+ find_library(JSON_LIBRARY NAMES jsoncpp)
+ find_path(JSON_INCLUDE_DIR json/features.h PATH_SUFFIXES jsoncpp)
+
+ include(FindPackageHandleStandardArgs)
+ find_package_handle_standard_args(JSONCPP DEFAULT_MSG JSON_LIBRARY JSON_INCLUDE_DIR)
+
+ if(JSONCPP_FOUND)
+ message(STATUS "Using system JSONCPP library.")
+ endif()
+endif()
+
+if(NOT JSONCPP_FOUND)
+ message(STATUS "Using bundled JSONCPP library.")
+ set(JSON_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/jsoncpp)
+ set(JSON_LIBRARY jsoncpp)
+ add_subdirectory(lib/jsoncpp)
+endif()
diff --git a/cmake/Modules/FindLua.cmake b/cmake/Modules/FindLua.cmake
new file mode 100644
index 000000000..be5d92d8c
--- /dev/null
+++ b/cmake/Modules/FindLua.cmake
@@ -0,0 +1,28 @@
+# Look for Lua library to use
+# This selects LuaJIT by default
+
+option(ENABLE_LUAJIT "Enable LuaJIT support" TRUE)
+set(USE_LUAJIT FALSE)
+option(REQUIRE_LUAJIT "Require LuaJIT support" FALSE)
+if(REQUIRE_LUAJIT)
+ set(ENABLE_LUAJIT TRUE)
+endif()
+if(ENABLE_LUAJIT)
+ find_package(LuaJIT)
+ if(LUAJIT_FOUND)
+ set(USE_LUAJIT TRUE)
+ message (STATUS "Using LuaJIT provided by system.")
+ elseif(REQUIRE_LUAJIT)
+ message(FATAL_ERROR "LuaJIT not found whereas REQUIRE_LUAJIT=\"TRUE\" is used.\n"
+ "To continue, either install LuaJIT or do not use REQUIRE_LUAJIT=\"TRUE\".")
+ endif()
+else()
+ message (STATUS "LuaJIT detection disabled! (ENABLE_LUAJIT=0)")
+endif()
+
+if(NOT USE_LUAJIT)
+ message(STATUS "LuaJIT not found, using bundled Lua.")
+ set(LUA_LIBRARY lua)
+ set(LUA_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/lua/src)
+ add_subdirectory(lib/lua)
+endif()
diff --git a/cmake/Modules/FindLuaJIT.cmake b/cmake/Modules/FindLuaJIT.cmake
new file mode 100644
index 000000000..cd6e7bdd8
--- /dev/null
+++ b/cmake/Modules/FindLuaJIT.cmake
@@ -0,0 +1,50 @@
+# Locate LuaJIT library
+# This module defines
+# LUAJIT_FOUND, if false, do not try to link to Lua
+# LUA_INCLUDE_DIR, where to find lua.h
+# LUA_VERSION_STRING, the version of Lua found (since CMake 2.8.8)
+#
+# This module is similar to FindLua51.cmake except that it finds LuaJit instead.
+
+FIND_PATH(LUA_INCLUDE_DIR luajit.h
+ HINTS
+ $ENV{LUA_DIR}
+ PATH_SUFFIXES include/luajit-2.1 include/luajit-2.0 include/luajit-5_1-2.1 include/luajit-5_1-2.0 include
+ PATHS
+ ~/Library/Frameworks
+ /Library/Frameworks
+ /sw # Fink
+ /opt/local # DarwinPorts
+ /opt/csw # Blastwave
+ /opt
+)
+
+FIND_LIBRARY(LUA_LIBRARY
+ NAMES luajit-5.1
+ HINTS
+ $ENV{LUA_DIR}
+ PATH_SUFFIXES lib64 lib
+ PATHS
+ ~/Library/Frameworks
+ /Library/Frameworks
+ /sw
+ /opt/local
+ /opt/csw
+ /opt
+)
+
+IF(LUA_INCLUDE_DIR AND EXISTS "${LUA_INCLUDE_DIR}/luajit.h")
+ FILE(STRINGS "${LUA_INCLUDE_DIR}/luajit.h" lua_version_str REGEX "^#define[ \t]+LUA_RELEASE[ \t]+\"LuaJIT .+\"")
+
+ STRING(REGEX REPLACE "^#define[ \t]+LUA_RELEASE[ \t]+\"LuaJIT ([^\"]+)\".*" "\\1" LUA_VERSION_STRING "${lua_version_str}")
+ UNSET(lua_version_str)
+ENDIF()
+
+INCLUDE(FindPackageHandleStandardArgs)
+# handle the QUIETLY and REQUIRED arguments and set LUAJIT_FOUND to TRUE if
+# all listed variables are TRUE
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(LuaJit
+ REQUIRED_VARS LUA_LIBRARY LUA_INCLUDE_DIR
+ VERSION_VAR LUA_VERSION_STRING)
+
+MARK_AS_ADVANCED(LUA_INCLUDE_DIR LUA_LIBRARY LUA_MATH_LIBRARY)
diff --git a/cmake/Modules/FindNcursesw.cmake b/cmake/Modules/FindNcursesw.cmake
new file mode 100644
index 000000000..e572c704a
--- /dev/null
+++ b/cmake/Modules/FindNcursesw.cmake
@@ -0,0 +1,204 @@
+#.rst:
+# FindNcursesw
+# ------------
+#
+# Find the ncursesw (wide ncurses) include file and library.
+#
+# Based on FindCurses.cmake which comes with CMake.
+#
+# Checks for ncursesw first. If not found, it then executes the
+# regular old FindCurses.cmake to look for for ncurses (or curses).
+#
+#
+# Result Variables
+# ^^^^^^^^^^^^^^^^
+#
+# This module defines the following variables:
+#
+# ``CURSES_FOUND``
+# True if curses is found.
+# ``NCURSESW_FOUND``
+# True if ncursesw is found.
+# ``CURSES_INCLUDE_DIRS``
+# The include directories needed to use Curses.
+# ``CURSES_LIBRARIES``
+# The libraries needed to use Curses.
+# ``CURSES_HAVE_CURSES_H``
+# True if curses.h is available.
+# ``CURSES_HAVE_NCURSES_H``
+# True if ncurses.h is available.
+# ``CURSES_HAVE_NCURSES_NCURSES_H``
+# True if ``ncurses/ncurses.h`` is available.
+# ``CURSES_HAVE_NCURSES_CURSES_H``
+# True if ``ncurses/curses.h`` is available.
+# ``CURSES_HAVE_NCURSESW_NCURSES_H``
+# True if ``ncursesw/ncurses.h`` is available.
+# ``CURSES_HAVE_NCURSESW_CURSES_H``
+# True if ``ncursesw/curses.h`` is available.
+#
+# Set ``CURSES_NEED_NCURSES`` to ``TRUE`` before the
+# ``find_package(Ncursesw)`` call if NCurses functionality is required.
+#
+#=============================================================================
+# Copyright 2001-2014 Kitware, Inc.
+# modifications: Copyright 2015 kahrl <kahrl@gmx.net>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the names of Kitware, Inc., the Insight Software Consortium,
+# nor the names of their contributors may be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# ------------------------------------------------------------------------------
+#
+# The above copyright and license notice applies to distributions of
+# CMake in source and binary form. Some source files contain additional
+# notices of original copyright by their contributors; see each source
+# for details. Third-party software packages supplied with CMake under
+# compatible licenses provide their own copyright notices documented in
+# corresponding subdirectories.
+#
+# ------------------------------------------------------------------------------
+#
+# CMake was initially developed by Kitware with the following sponsorship:
+#
+# * National Library of Medicine at the National Institutes of Health
+# as part of the Insight Segmentation and Registration Toolkit (ITK).
+#
+# * US National Labs (Los Alamos, Livermore, Sandia) ASC Parallel
+# Visualization Initiative.
+#
+# * National Alliance for Medical Image Computing (NAMIC) is funded by the
+# National Institutes of Health through the NIH Roadmap for Medical Research,
+# Grant U54 EB005149.
+#
+# * Kitware, Inc.
+#=============================================================================
+
+include(CheckLibraryExists)
+
+find_library(CURSES_NCURSESW_LIBRARY NAMES ncursesw
+ DOC "Path to libncursesw.so or .lib or .a")
+
+set(CURSES_USE_NCURSES FALSE)
+set(CURSES_USE_NCURSESW FALSE)
+
+if(CURSES_NCURSESW_LIBRARY)
+ set(CURSES_USE_NCURSES TRUE)
+ set(CURSES_USE_NCURSESW TRUE)
+endif()
+
+if(CURSES_USE_NCURSESW)
+ get_filename_component(_cursesLibDir "${CURSES_NCURSESW_LIBRARY}" PATH)
+ get_filename_component(_cursesParentDir "${_cursesLibDir}" PATH)
+
+ find_path(CURSES_INCLUDE_PATH
+ NAMES ncursesw/ncurses.h ncursesw/curses.h ncurses.h curses.h
+ HINTS "${_cursesParentDir}/include"
+ )
+
+ # Previous versions of FindCurses provided these values.
+ if(NOT DEFINED CURSES_LIBRARY)
+ set(CURSES_LIBRARY "${CURSES_NCURSESW_LIBRARY}")
+ endif()
+
+ CHECK_LIBRARY_EXISTS("${CURSES_NCURSESW_LIBRARY}"
+ cbreak "" CURSES_NCURSESW_HAS_CBREAK)
+ if(NOT CURSES_NCURSESW_HAS_CBREAK)
+ find_library(CURSES_EXTRA_LIBRARY tinfo HINTS "${_cursesLibDir}"
+ DOC "Path to libtinfo.so or .lib or .a")
+ find_library(CURSES_EXTRA_LIBRARY tinfo )
+ endif()
+
+ # Report whether each possible header name exists in the include directory.
+ if(NOT DEFINED CURSES_HAVE_NCURSESW_NCURSES_H)
+ if(EXISTS "${CURSES_INCLUDE_PATH}/ncursesw/ncurses.h")
+ set(CURSES_HAVE_NCURSESW_NCURSES_H "${CURSES_INCLUDE_PATH}/ncursesw/ncurses.h")
+ else()
+ set(CURSES_HAVE_NCURSESW_NCURSES_H "CURSES_HAVE_NCURSESW_NCURSES_H-NOTFOUND")
+ endif()
+ endif()
+ if(NOT DEFINED CURSES_HAVE_NCURSESW_CURSES_H)
+ if(EXISTS "${CURSES_INCLUDE_PATH}/ncursesw/curses.h")
+ set(CURSES_HAVE_NCURSESW_CURSES_H "${CURSES_INCLUDE_PATH}/ncursesw/curses.h")
+ else()
+ set(CURSES_HAVE_NCURSESW_CURSES_H "CURSES_HAVE_NCURSESW_CURSES_H-NOTFOUND")
+ endif()
+ endif()
+ if(NOT DEFINED CURSES_HAVE_NCURSES_H)
+ if(EXISTS "${CURSES_INCLUDE_PATH}/ncurses.h")
+ set(CURSES_HAVE_NCURSES_H "${CURSES_INCLUDE_PATH}/ncurses.h")
+ else()
+ set(CURSES_HAVE_NCURSES_H "CURSES_HAVE_NCURSES_H-NOTFOUND")
+ endif()
+ endif()
+ if(NOT DEFINED CURSES_HAVE_CURSES_H)
+ if(EXISTS "${CURSES_INCLUDE_PATH}/curses.h")
+ set(CURSES_HAVE_CURSES_H "${CURSES_INCLUDE_PATH}/curses.h")
+ else()
+ set(CURSES_HAVE_CURSES_H "CURSES_HAVE_CURSES_H-NOTFOUND")
+ endif()
+ endif()
+
+
+ find_library(CURSES_FORM_LIBRARY form HINTS "${_cursesLibDir}"
+ DOC "Path to libform.so or .lib or .a")
+ find_library(CURSES_FORM_LIBRARY form )
+
+ # Need to provide the *_LIBRARIES
+ set(CURSES_LIBRARIES ${CURSES_LIBRARY})
+
+ if(CURSES_EXTRA_LIBRARY)
+ set(CURSES_LIBRARIES ${CURSES_LIBRARIES} ${CURSES_EXTRA_LIBRARY})
+ endif()
+
+ if(CURSES_FORM_LIBRARY)
+ set(CURSES_LIBRARIES ${CURSES_LIBRARIES} ${CURSES_FORM_LIBRARY})
+ endif()
+
+ # Provide the *_INCLUDE_DIRS result.
+ set(CURSES_INCLUDE_DIRS ${CURSES_INCLUDE_PATH})
+ set(CURSES_INCLUDE_DIR ${CURSES_INCLUDE_PATH}) # compatibility
+
+ # handle the QUIETLY and REQUIRED arguments and set CURSES_FOUND to TRUE if
+ # all listed variables are TRUE
+ include(FindPackageHandleStandardArgs)
+ FIND_PACKAGE_HANDLE_STANDARD_ARGS(Ncursesw DEFAULT_MSG
+ CURSES_LIBRARY CURSES_INCLUDE_PATH)
+ set(CURSES_FOUND ${NCURSESW_FOUND})
+
+else()
+ find_package(Curses)
+ set(NCURSESW_FOUND FALSE)
+endif()
+
+mark_as_advanced(
+ CURSES_INCLUDE_PATH
+ CURSES_CURSES_LIBRARY
+ CURSES_NCURSES_LIBRARY
+ CURSES_NCURSESW_LIBRARY
+ CURSES_EXTRA_LIBRARY
+ CURSES_FORM_LIBRARY
+ )
diff --git a/cmake/Modules/FindOpenGLES2.cmake b/cmake/Modules/FindOpenGLES2.cmake
new file mode 100644
index 000000000..a8622f3b2
--- /dev/null
+++ b/cmake/Modules/FindOpenGLES2.cmake
@@ -0,0 +1,112 @@
+#-------------------------------------------------------------------
+# This file is stolen from part of the CMake build system for OGRE (Object-oriented Graphics Rendering Engine) http://www.ogre3d.org/
+#
+# The contents of this file are placed in the public domain. Feel
+# free to make use of it in any way you like.
+#-------------------------------------------------------------------
+
+# - Try to find OpenGLES and EGL
+# Once done this will define
+#
+# OPENGLES2_FOUND - system has OpenGLES
+# OPENGLES2_INCLUDE_DIR - the GL include directory
+# OPENGLES2_LIBRARIES - Link these to use OpenGLES
+#
+# EGL_FOUND - system has EGL
+# EGL_INCLUDE_DIR - the EGL include directory
+# EGL_LIBRARIES - Link these to use EGL
+
+# Win32, Apple, and Android are not tested!
+# Linux tested and works
+
+if(WIN32)
+ if(CYGWIN)
+ find_path(OPENGLES2_INCLUDE_DIR GLES2/gl2.h)
+ find_library(OPENGLES2_LIBRARY libGLESv2)
+ else()
+ if(BORLAND)
+ set(OPENGLES2_LIBRARY import32 CACHE STRING "OpenGL ES 2.x library for Win32")
+ else()
+ # TODO
+ # set(OPENGLES_LIBRARY ${SOURCE_DIR}/Dependencies/lib/release/libGLESv2.lib CACHE STRING "OpenGL ES 2.x library for win32"
+ endif()
+ endif()
+elseif(APPLE)
+ create_search_paths(/Developer/Platforms)
+ findpkg_framework(OpenGLES2)
+ set(OPENGLES2_LIBRARY "-framework OpenGLES")
+else()
+ find_path(OPENGLES2_INCLUDE_DIR GLES2/gl2.h
+ PATHS /usr/openwin/share/include
+ /opt/graphics/OpenGL/include
+ /usr/X11R6/include
+ /usr/include
+ )
+
+ find_library(OPENGLES2_LIBRARY
+ NAMES GLESv2
+ PATHS /opt/graphics/OpenGL/lib
+ /usr/openwin/lib
+ /usr/shlib /usr/X11R6/lib
+ /usr/lib
+ )
+
+ if(NOT BUILD_ANDROID)
+ find_path(EGL_INCLUDE_DIR EGL/egl.h
+ PATHS /usr/openwin/share/include
+ /opt/graphics/OpenGL/include
+ /usr/X11R6/include
+ /usr/include
+ )
+
+ find_library(EGL_LIBRARY
+ NAMES EGL
+ PATHS /opt/graphics/OpenGL/lib
+ /usr/openwin/lib
+ /usr/shlib
+ /usr/X11R6/lib
+ /usr/lib
+ )
+
+ # On Unix OpenGL usually requires X11.
+ # It doesn't require X11 on OSX.
+
+ if(OPENGLES2_LIBRARY)
+ if(NOT X11_FOUND)
+ include(FindX11)
+ endif()
+ if(X11_FOUND)
+ set(OPENGLES2_LIBRARIES ${X11_LIBRARIES})
+ endif()
+ endif()
+ endif()
+endif()
+
+set(OPENGLES2_LIBRARIES ${OPENGLES2_LIBRARIES} ${OPENGLES2_LIBRARY})
+
+if(BUILD_ANDROID)
+ if(OPENGLES2_LIBRARY)
+ set(EGL_LIBRARIES)
+ set(OPENGLES2_FOUND TRUE)
+ endif()
+else()
+ if(OPENGLES2_LIBRARY AND EGL_LIBRARY)
+ set(OPENGLES2_LIBRARIES ${OPENGLES2_LIBRARY} ${OPENGLES2_LIBRARIES})
+ set(EGL_LIBRARIES ${EGL_LIBRARY} ${EGL_LIBRARIES})
+ set(OPENGLES2_FOUND TRUE)
+ endif()
+endif()
+
+mark_as_advanced(
+ OPENGLES2_INCLUDE_DIR
+ OPENGLES2_LIBRARY
+ EGL_INCLUDE_DIR
+ EGL_LIBRARY
+)
+
+if(OPENGLES2_FOUND)
+ message(STATUS "Found system OpenGL ES 2 library: ${OPENGLES2_LIBRARIES}")
+else()
+ set(OPENGLES2_LIBRARIES "")
+endif()
+
diff --git a/cmake/Modules/FindSQLite3.cmake b/cmake/Modules/FindSQLite3.cmake
new file mode 100644
index 000000000..b23553a80
--- /dev/null
+++ b/cmake/Modules/FindSQLite3.cmake
@@ -0,0 +1,9 @@
+mark_as_advanced(SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR)
+
+find_path(SQLITE3_INCLUDE_DIR sqlite3.h)
+
+find_library(SQLITE3_LIBRARY NAMES sqlite3)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(SQLite3 DEFAULT_MSG SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR)
+
diff --git a/cmake/Modules/FindVorbis.cmake b/cmake/Modules/FindVorbis.cmake
new file mode 100644
index 000000000..8f3813694
--- /dev/null
+++ b/cmake/Modules/FindVorbis.cmake
@@ -0,0 +1,46 @@
+# - Find vorbis
+# Find the native vorbis includes and libraries
+#
+# VORBIS_INCLUDE_DIR - where to find vorbis.h, etc.
+# OGG_INCLUDE_DIR - where to find ogg/ogg.h, etc.
+# VORBIS_LIBRARIES - List of libraries when using vorbis(file).
+# VORBIS_FOUND - True if vorbis found.
+
+if(NOT GP2XWIZ)
+ if(VORBIS_INCLUDE_DIR)
+ # Already in cache, be silent
+ set(VORBIS_FIND_QUIETLY TRUE)
+ endif(VORBIS_INCLUDE_DIR)
+ find_path(OGG_INCLUDE_DIR ogg/ogg.h)
+ find_path(VORBIS_INCLUDE_DIR vorbis/vorbisfile.h)
+ # MSVC built ogg/vorbis may be named ogg_static and vorbis_static
+ find_library(OGG_LIBRARY NAMES ogg ogg_static)
+ find_library(VORBIS_LIBRARY NAMES vorbis vorbis_static)
+ find_library(VORBISFILE_LIBRARY NAMES vorbisfile vorbisfile_static)
+ # Handle the QUIETLY and REQUIRED arguments and set VORBIS_FOUND
+ # to TRUE if all listed variables are TRUE.
+ include(FindPackageHandleStandardArgs)
+ find_package_handle_standard_args(VORBIS DEFAULT_MSG
+ OGG_INCLUDE_DIR VORBIS_INCLUDE_DIR
+ OGG_LIBRARY VORBIS_LIBRARY VORBISFILE_LIBRARY)
+else(NOT GP2XWIZ)
+ find_path(VORBIS_INCLUDE_DIR tremor/ivorbisfile.h)
+ find_library(VORBIS_LIBRARY NAMES vorbis_dec)
+ find_package_handle_standard_args(VORBIS DEFAULT_MSG
+ VORBIS_INCLUDE_DIR VORBIS_LIBRARY)
+endif(NOT GP2XWIZ)
+
+if(VORBIS_FOUND)
+ if(NOT GP2XWIZ)
+ set(VORBIS_LIBRARIES ${VORBISFILE_LIBRARY} ${VORBIS_LIBRARY}
+ ${OGG_LIBRARY})
+ else(NOT GP2XWIZ)
+ set(VORBIS_LIBRARIES ${VORBIS_LIBRARY})
+ endif(NOT GP2XWIZ)
+else(VORBIS_FOUND)
+ set(VORBIS_LIBRARIES)
+endif(VORBIS_FOUND)
+
+mark_as_advanced(OGG_INCLUDE_DIR VORBIS_INCLUDE_DIR)
+mark_as_advanced(OGG_LIBRARY VORBIS_LIBRARY VORBISFILE_LIBRARY)
+
diff --git a/cmake/Modules/GenerateVersion.cmake b/cmake/Modules/GenerateVersion.cmake
new file mode 100644
index 000000000..ad0e38263
--- /dev/null
+++ b/cmake/Modules/GenerateVersion.cmake
@@ -0,0 +1,26 @@
+# Always run during 'make'
+
+if(DEVELOPMENT_BUILD)
+ execute_process(COMMAND git rev-parse --short HEAD
+ WORKING_DIRECTORY "${GENERATE_VERSION_SOURCE_DIR}"
+ OUTPUT_VARIABLE VERSION_GITHASH OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET)
+ if(VERSION_GITHASH)
+ set(VERSION_GITHASH "${VERSION_STRING}-${VERSION_GITHASH}")
+ execute_process(COMMAND git diff-index --quiet HEAD
+ WORKING_DIRECTORY "${GENERATE_VERSION_SOURCE_DIR}"
+ RESULT_VARIABLE IS_DIRTY)
+ if(IS_DIRTY)
+ set(VERSION_GITHASH "${VERSION_GITHASH}-dirty")
+ endif()
+ message(STATUS "*** Detected Git version ${VERSION_GITHASH} ***")
+ endif()
+endif()
+if(NOT VERSION_GITHASH)
+ set(VERSION_GITHASH "${VERSION_STRING}")
+endif()
+
+configure_file(
+ ${GENERATE_VERSION_SOURCE_DIR}/cmake_config_githash.h.in
+ ${GENERATE_VERSION_BINARY_DIR}/cmake_config_githash.h)
+
diff --git a/cmake/Modules/misc.cmake b/cmake/Modules/misc.cmake
deleted file mode 100644
index 0bd2e3fce..000000000
--- a/cmake/Modules/misc.cmake
+++ /dev/null
@@ -1,21 +0,0 @@
-#
-# Random macros
-#
-
-# Not used ATM
-
-MACRO (GETDATETIME RESULT)
- IF (WIN32)
- EXECUTE_PROCESS(COMMAND "cmd" /C echo %date% %time% OUTPUT_VARIABLE ${RESULT})
- string(REGEX REPLACE "\n" "" ${RESULT} "${${RESULT}}")
- ELSEIF(UNIX)
- EXECUTE_PROCESS(COMMAND "date" "+%Y-%m-%d_%H:%M:%S" OUTPUT_VARIABLE ${RESULT})
- string(REGEX REPLACE "\n" "" ${RESULT} "${${RESULT}}")
- ELSE (WIN32)
- MESSAGE(SEND_ERROR "date not implemented")
- SET(${RESULT} "Unknown")
- ENDIF (WIN32)
-
- string(REGEX REPLACE " " "_" ${RESULT} "${${RESULT}}")
-ENDMACRO (GETDATETIME)
-
diff --git a/data/cloud.png b/data/cloud.png
deleted file mode 100644
index 24091a377..000000000
--- a/data/cloud.png
+++ /dev/null
Binary files differ
diff --git a/data/coalstone.png b/data/coalstone.png
deleted file mode 100644
index c993a02f0..000000000
--- a/data/coalstone.png
+++ /dev/null
Binary files differ
diff --git a/data/crack.png b/data/crack.png
deleted file mode 100644
index 0fef2a6fd..000000000
--- a/data/crack.png
+++ /dev/null
Binary files differ
diff --git a/data/fontlucida.png b/data/fontlucida.png
deleted file mode 100644
index c63fa02b7..000000000
--- a/data/fontlucida.png
+++ /dev/null
Binary files differ
diff --git a/data/grass.png b/data/grass.png
deleted file mode 100644
index 30ef38591..000000000
--- a/data/grass.png
+++ /dev/null
Binary files differ
diff --git a/data/grass2.png b/data/grass2.png
deleted file mode 100644
index 66b3e58b5..000000000
--- a/data/grass2.png
+++ /dev/null
Binary files differ
diff --git a/data/grass_footsteps.png b/data/grass_footsteps.png
deleted file mode 100644
index 26bd236c5..000000000
--- a/data/grass_footsteps.png
+++ /dev/null
Binary files differ
diff --git a/data/leaves.png b/data/leaves.png
deleted file mode 100644
index 086da8091..000000000
--- a/data/leaves.png
+++ /dev/null
Binary files differ
diff --git a/data/mese.png b/data/mese.png
deleted file mode 100644
index 4c876cdc5..000000000
--- a/data/mese.png
+++ /dev/null
Binary files differ
diff --git a/data/mud_with_grass.png b/data/mud_with_grass.png
deleted file mode 100644
index 60ae4eec2..000000000
--- a/data/mud_with_grass.png
+++ /dev/null
Binary files differ
diff --git a/data/pauseMenu.gui b/data/pauseMenu.gui
deleted file mode 100644
index 0cf6c7adc..000000000
--- a/data/pauseMenu.gui
+++ /dev/null
Binary files differ
diff --git a/data/player.png b/data/player.png
deleted file mode 100644
index 90adf9747..000000000
--- a/data/player.png
+++ /dev/null
Binary files differ
diff --git a/data/player_back.png b/data/player_back.png
deleted file mode 100644
index 530aa7519..000000000
--- a/data/player_back.png
+++ /dev/null
Binary files differ
diff --git a/data/rat.png b/data/rat.png
deleted file mode 100644
index d1a0e2ae2..000000000
--- a/data/rat.png
+++ /dev/null
Binary files differ
diff --git a/data/sign.png b/data/sign.png
deleted file mode 100644
index 2e0b3cbef..000000000
--- a/data/sign.png
+++ /dev/null
Binary files differ
diff --git a/data/sign_back.png b/data/sign_back.png
deleted file mode 100644
index 779e4bc2c..000000000
--- a/data/sign_back.png
+++ /dev/null
Binary files differ
diff --git a/data/skybox1.png b/data/skybox1.png
deleted file mode 100644
index 202e40eba..000000000
--- a/data/skybox1.png
+++ /dev/null
Binary files differ
diff --git a/data/skybox1_source.png b/data/skybox1_source.png
deleted file mode 100644
index 642e89ce4..000000000
--- a/data/skybox1_source.png
+++ /dev/null
Binary files differ
diff --git a/data/skybox2.png b/data/skybox2.png
deleted file mode 100644
index f09a8cbf1..000000000
--- a/data/skybox2.png
+++ /dev/null
Binary files differ
diff --git a/data/skybox3.png b/data/skybox3.png
deleted file mode 100644
index 9a37734c8..000000000
--- a/data/skybox3.png
+++ /dev/null
Binary files differ
diff --git a/data/stick.png b/data/stick.png
deleted file mode 100644
index 7a4663cc3..000000000
--- a/data/stick.png
+++ /dev/null
Binary files differ
diff --git a/data/stone.png b/data/stone.png
deleted file mode 100644
index daf2cceb8..000000000
--- a/data/stone.png
+++ /dev/null
Binary files differ
diff --git a/data/stone_with_arrow.png b/data/stone_with_arrow.png
deleted file mode 100644
index d64470675..000000000
--- a/data/stone_with_arrow.png
+++ /dev/null
Binary files differ
diff --git a/data/tf.jpg b/data/tf.jpg
deleted file mode 100644
index d08b6c95e..000000000
--- a/data/tf.jpg
+++ /dev/null
Binary files differ
diff --git a/data/tool_mesepick.png b/data/tool_mesepick.png
deleted file mode 100644
index 886f4b21c..000000000
--- a/data/tool_mesepick.png
+++ /dev/null
Binary files differ
diff --git a/data/tool_stpick.png b/data/tool_stpick.png
deleted file mode 100644
index 9ca3a5e03..000000000
--- a/data/tool_stpick.png
+++ /dev/null
Binary files differ
diff --git a/data/tool_wpick.png b/data/tool_wpick.png
deleted file mode 100644
index 359249515..000000000
--- a/data/tool_wpick.png
+++ /dev/null
Binary files differ
diff --git a/data/torch.png b/data/torch.png
deleted file mode 100644
index 7a953c222..000000000
--- a/data/torch.png
+++ /dev/null
Binary files differ
diff --git a/data/torch_on_ceiling.png b/data/torch_on_ceiling.png
deleted file mode 100644
index 6965d380e..000000000
--- a/data/torch_on_ceiling.png
+++ /dev/null
Binary files differ
diff --git a/data/torch_on_floor.png b/data/torch_on_floor.png
deleted file mode 100644
index 76d1dd5ac..000000000
--- a/data/torch_on_floor.png
+++ /dev/null
Binary files differ
diff --git a/data/tree_top.png b/data/tree_top.png
deleted file mode 100644
index 2cdd94f80..000000000
--- a/data/tree_top.png
+++ /dev/null
Binary files differ
diff --git a/data/water.png b/data/water.png
deleted file mode 100644
index 98cda2776..000000000
--- a/data/water.png
+++ /dev/null
Binary files differ
diff --git a/data/water_old.png b/data/water_old.png
deleted file mode 100644
index 37b2898fa..000000000
--- a/data/water_old.png
+++ /dev/null
Binary files differ
diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in
new file mode 100644
index 000000000..3618b852d
--- /dev/null
+++ b/doc/Doxyfile.in
@@ -0,0 +1,53 @@
+# Project properties
+PROJECT_NAME = @PROJECT_NAME_CAPITALIZED@
+PROJECT_NUMBER = @VERSION_STRING@
+PROJECT_LOGO = @CMAKE_CURRENT_SOURCE_DIR@/misc/minetest.svg
+
+# Parsing
+JAVADOC_AUTOBRIEF = YES
+EXTRACT_ALL = YES
+EXTRACT_PRIVATE = YES
+EXTRACT_STATIC = YES
+SORT_MEMBERS_CTORS_1ST = YES
+WARN_IF_UNDOCUMENTED = NO
+BUILTIN_STL_SUPPORT = YES
+PREDEFINED = "USE_SPATIAL=1" \
+ "USE_LEVELDB=1" \
+ "USE_REDIS=1" \
+ "USE_SOUND=1" \
+ "USE_CURL=1" \
+ "USE_FREETYPE=1" \
+ "USE_GETTEXT=1"
+
+# Input
+RECURSIVE = NO
+STRIP_FROM_PATH = @CMAKE_CURRENT_SOURCE_DIR@/src
+INPUT = @CMAKE_CURRENT_SOURCE_DIR@/doc/main_page.dox \
+ @CMAKE_CURRENT_SOURCE_DIR@/src/ \
+ @CMAKE_CURRENT_SOURCE_DIR@/src/client \
+ @CMAKE_CURRENT_SOURCE_DIR@/src/network \
+ @CMAKE_CURRENT_SOURCE_DIR@/src/util \
+ @CMAKE_CURRENT_SOURCE_DIR@/src/script \
+ @CMAKE_CURRENT_SOURCE_DIR@/src/script/common \
+ @CMAKE_CURRENT_SOURCE_DIR@/src/script/cpp_api \
+ @CMAKE_CURRENT_SOURCE_DIR@/src/script/lua_api \
+ @CMAKE_CURRENT_SOURCE_DIR@/src/threading
+
+# Dot graphs
+HAVE_DOT = @DOXYGEN_DOT_FOUND@
+CALL_GRAPH = YES
+CALLER_GRAPH = YES
+MAX_DOT_GRAPH_DEPTH = 3
+DOT_MULTI_TARGETS = YES
+DOT_IMAGE_FORMAT = svg
+
+# Output
+GENERATE_LATEX = NO
+REFERENCED_BY_RELATION = YES
+REFERENCES_RELATION = YES
+SEARCHENGINE = YES
+DISABLE_INDEX = YES
+GENERATE_TREEVIEW = YES
+HTML_DYNAMIC_SECTIONS = YES
+HTML_TIMESTAMP = YES
+
diff --git a/doc/README.android b/doc/README.android
new file mode 100644
index 000000000..0b17647c0
--- /dev/null
+++ b/doc/README.android
@@ -0,0 +1,130 @@
+Minetest Android port
+=====================
+Date: 2014 06 28
+
+Controls
+--------
+The Android port doesn't support everything you can do on PC due to the
+limited capabilities of common devices. What can be done is described
+below:
+
+While you're playing the game normally (that is, no menu or inventory is
+shown), the following controls are available:
+* Look around: touch screen and slide finger
+* double tap: place a node or use selected item
+* long tap: dig node
+* touch shown buttons: press button
+* Buttons:
+** left upper corner: chat
+** right lower corner: jump
+** right lower corner: crouch
+** left lower corner: walk/step...
+ left up right
+ down
+** left lower corner: display inventory
+
+When a menu or inventory is displayed:
+* double tap outside menu area: close menu
+* tap on an item stack: select that stack
+* tap on an empty slot: if you selected a stack already, that stack is placed here
+* drag and drop: touch stack and hold finger down, move the stack to another
+ slot, tap another finger while keeping first finger on screen
+ --> places a single item from dragged stack into current (first touched) slot
+
+Special settings
+----------------
+There are some settings especially useful for Android users. Minetest's config
+file can usually be found at /mnt/sdcard/Minetest.
+
+* gui_scaling: this is a user-specified scaling factor for the GUI- In case
+ main menu is too big or small on your device, try changing this
+ value.
+* inventory_image_hack: if your inventory items are messed up, try setting
+ this to true
+
+Known issues
+------------
+Not all issues are fixed by now:
+
+* Unable to exit from volume menu -- don't use the volume menu, use Android's
+ volume controls instead.
+* 512 MB RAM seems to be inadequate -- this depends on the server you join.
+ Try to play on more lightweight servers.
+
+Versioning
+----------
+Android version numbers are 4 digits instead of Minetest's 3 digits. The last
+number of Android's version represents the Android internal version code. This
+version code is strictly incremental. It's incremented for each official
+Minetest Android build.
+
+E.g. prerelease Minetest Android builds have been 0.4.9.3, while the first
+official version most likely will be 0.4.10.4
+
+Requirements
+------------
+
+In order to build, your PC has to be set up to build Minetest in the usual
+manner (see the regular Minetest documentation for how to get this done).
+In addition to what is required for Minetest in general, you will need the
+following software packages. The version number in parenthesis denotes the
+version that was tested at the time this README was drafted; newer/older
+versions may or may not work.
+
+* android SDK (x86_64 20131030)
+* android NDK (r9d)
+* wget (1.13.4)
+
+Additionally, you'll need to have an Internet connection available on the
+build system, as the Android build will download some source packages.
+
+Build
+-----
+
+Debug build:
+* Enter "build/android" subdirectory
+* Execute "make"
+* Answer the questions about where SDK and NDK are located on your filesystem
+* Wait for build to finish
+
+After the build is finished, the resulting apk can be fond in
+build/android/bin/. It will be called Minetest-debug.apk
+
+Release build:
+
+* In order to make a release build you'll have to have a keystore setup to sign
+ the resulting apk package. How this is done is not part of this README. There
+ are different tutorials on the web explaining how to do it
+ - choose one yourself.
+
+* Once your keystore is setup, enter build/android subdirectory and create a new
+ file "ant.properties" there. Add following lines to that file:
+
+ > key.store=<path to your keystore>
+ > key.alias=Minetest
+
+* Execute "make release"
+* Enter your keystore as well as your Mintest key password once asked. Be
+ careful it's shown on console in clear text!
+* The result can be found at "bin/Minetest-release.apk"
+
+Other things that may be nice to know
+------------
+* The environment for Android development tools is saved within Android build
+ build folder. If you want direct access to it do:
+
+ > make envpaths
+ > . and_env
+
+ After you've done this you'll have your path and path variables set correct
+ to use adb and all other Android development tools
+
+* You can build a single dependency by calling make and the dependency's name,
+ e.g.:
+
+ > make irrlicht
+
+* You can completely cleanup a dependency by calling make and the "clean" target,
+ e.g.:
+
+ > make clean_irrlicht
diff --git a/doc/README.txt b/doc/README.txt
deleted file mode 100644
index 93203b3db..000000000
--- a/doc/README.txt
+++ /dev/null
@@ -1,169 +0,0 @@
-Minetest-c55
----------------
-
-Copyright (c) 2010 Perttu Ahola <celeron55@gmail.com>
-
-An InfiniMiner/Minecraft inspired game.
-
-NOTE: This file is somewhat outdated most of the time.
-
-This is a development version:
-- Don't expect it to work as well as a finished game will.
-- Please report any bugs to me. That way I can fix them to the next release.
- - debug.txt is useful when the game crashes.
-
-Public servers:
- kray.dy.fi :30000 (friend's server)
- celeron.55.lt :30000 (my own server)
-
-Controls:
-- See the in-game pause menu
-
-Map directory:
-- Map is stored in a directory, which can be removed to generate a new map.
-- There is na command-line option for it: --map-dir
-- As default, it is located in:
- ../map
-- Otherwise something like this:
- Windows: C:\Documents and Settings\user\Application Data\minetest\map
- Linux: ~/.minetest/map
- OS X: ~/Library/Application Support/map
-
-Configuration file:
-- An optional configuration file can be used. See minetest.conf.example.
-- Path to file can be passed as a parameter to the executable:
- --config <path-to-file>
-- Defaults:
- - If built with -DRUN_IN_PLACE:
- ../minetest.conf
- ../../minetest.conf
- - Otherwise something like this:
- Windows: C:\Documents and Settings\user\Application Data\minetest\minetest.conf
- Linux: ~/.minetest/minetest.conf
- OS X: ~/Library/Application Support/minetest.conf
-
-Command-line options:
-- Use --help
-
-Compiling on GNU/Linux:
-
-- You need:
- * CMake
- * Irrlicht
- * Zlib
-- You can probably find these in your distro's package repository.
-- Building has been tested to work flawlessly on many systems.
-
-- Check possible options:
- $ cd whatever/minetest
- $ cmake . -LH
-
-- A system-wide install:
- $ cd whatever/minetest
- $ cmake . -DCMAKE_INSTALL_PREFIX=/usr/local
- $ make -j2
- $ sudo make install
-
- $ minetest
-
-- Install to home directory:
- $ cd whatever/minetest
- $ cmake . -DCMAKE_INSTALL_PREFIX=~/minetest_install
- $ make -j2
- $ make install
-
- $ ~/minetest_install/bin/minetest
-
-- For running in the source directory:
- $ cd whatever/minetest
- $ cmake . -DRUN_IN_PLACE
- $ make -j2
-
- $ ./bin/minetest
-
-Compiling on Windows:
-- You need CMake, Irrlicht, Zlib and Visual Studio or MinGW
-- NOTE: Probably it will not work easily and you will need to fix some stuff.
-- Steps:
- - Start up the CMake GUI
- - Select your compiler
- - Hit "Configure"
- - Set up some options and paths
- - Hit "Configure"
- - Hit "Generate"
- - VC: Open the generated .sln and build it
- - MinGW: Browse to the build directory and run 'make'
-
-License of Minetest-c55
------------------------
-
-Minetest-c55
-Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-Irrlicht
----------------
-
-This program uses the Irrlicht Engine. http://irrlicht.sourceforge.net/
-
- The Irrlicht Engine License
-
-Copyright © 2002-2005 Nikolaus Gebhardt
-
-This software is provided 'as-is', without any express or implied
-warranty. In no event will the authors be held liable for any damages
-arising from the use of this software.
-
-Permission is granted to anyone to use this software for any purpose,
-including commercial applications, and to alter it and redistribute
-it freely, subject to the following restrictions:
-
- 1. The origin of this software must not be misrepresented; you
- must not claim that you wrote the original software. If you use
- this software in a product, an acknowledgment in the product
- documentation would be appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must
- not be misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source
- distribution.
-
-
-JThread
----------------
-
-This program uses the JThread library. License for JThread follows:
-
-Copyright (c) 2000-2006 Jori Liesenborgs (jori.liesenborgs@gmail.com)
-
-Permission is hereby granted, free of charge, to any person obtaining a
-copy of this software and associated documentation files (the "Software"),
-to deal in the Software without restriction, including without limitation
-the rights to use, copy, modify, merge, publish, distribute, sublicense,
-and/or sell copies of the Software, and to permit persons to whom the
-Software is furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included
-in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-IN THE SOFTWARE.
-
-
diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md
new file mode 100644
index 000000000..8ef3bfb2e
--- /dev/null
+++ b/doc/client_lua_api.md
@@ -0,0 +1,1152 @@
+Minetest Lua Client Modding API Reference 0.4.15
+================================================
+* More information at <http://www.minetest.net/>
+* Developer Wiki: <http://dev.minetest.net/>
+
+Introduction
+------------
+
+**WARNING: The client API is currently unstable, and may break/change without warning.**
+
+Content and functionality can be added to Minetest 0.4.15-dev+ by using Lua
+scripting in run-time loaded mods.
+
+A mod is a self-contained bunch of scripts, textures and other related
+things that is loaded by and interfaces with Minetest.
+
+Transfering client-sided mods form the server to the client is planned, but not implemented yet.
+
+If you see a deficiency in the API, feel free to attempt to add the
+functionality in the engine and API. You can send such improvements as
+source code patches on GitHub (https://github.com/minetest/minetest).
+
+Programming in Lua
+------------------
+If you have any difficulty in understanding this, please read
+[Programming in Lua](http://www.lua.org/pil/).
+
+Startup
+-------
+Mods are loaded during client startup from the mod load paths by running
+the `init.lua` scripts in a shared environment.
+
+Paths
+-----
+* `RUN_IN_PLACE=1` (Windows release, local build)
+ * `$path_user`:
+ * Linux: `<build directory>`
+ * Windows: `<build directory>`
+ * `$path_share`
+ * Linux: `<build directory>`
+ * Windows: `<build directory>`
+* `RUN_IN_PLACE=0`: (Linux release)
+ * `$path_share`
+ * Linux: `/usr/share/minetest`
+ * Windows: `<install directory>/minetest-0.4.x`
+ * `$path_user`:
+ * Linux: `$HOME/.minetest`
+ * Windows: `C:/users/<user>/AppData/minetest` (maybe)
+
+Mod load path
+-------------
+Generic:
+
+* `$path_share/clientmods/`
+* `$path_user/clientmods/` (User-installed mods)
+
+In a run-in-place version (e.g. the distributed windows version):
+
+* `minetest-0.4.x/clientmods/` (User-installed mods)
+
+On an installed version on Linux:
+
+* `/usr/share/minetest/clientmods/`
+* `$HOME/.minetest/clientmods/` (User-installed mods)
+
+Modpack support
+----------------
+**NOTE: Not implemented yet.**
+
+Mods can be put in a subdirectory, if the parent directory, which otherwise
+should be a mod, contains a file named `modpack.txt`. This file shall be
+empty, except for lines starting with `#`, which are comments.
+
+Mod directory structure
+------------------------
+
+ clientmods
+ ├── modname
+ | ├── depends.txt
+ | ├── init.lua
+ └── another
+
+### modname
+The location of this directory.
+
+### depends.txt
+List of mods that have to be loaded before loading this mod.
+
+A single line contains a single modname.
+
+Optional dependencies can be defined by appending a question mark
+to a single modname. Their meaning is that if the specified mod
+is missing, that does not prevent this mod from being loaded.
+
+### init.lua
+The main Lua script. Running this script should register everything it
+wants to register. Subsequent execution depends on minetest calling the
+registered callbacks.
+
+`minetest.setting_get(name)` and `minetest.setting_getbool(name)` can be used
+to read custom or existing settings at load time, if necessary.
+
+### `sounds`
+Media files (sounds) that will be transferred to the
+client and will be available for use by the mod.
+
+Naming convention for registered textual names
+----------------------------------------------
+Registered names should generally be in this format:
+
+ "modname:<whatever>" (<whatever> can have characters a-zA-Z0-9_)
+
+This is to prevent conflicting names from corrupting maps and is
+enforced by the mod loader.
+
+### Example
+In the mod `experimental`, there is the ideal item/node/entity name `tnt`.
+So the name should be `experimental:tnt`.
+
+Enforcement can be overridden by prefixing the name with `:`. This can
+be used for overriding the registrations of some other mod.
+
+Example: Any mod can redefine `experimental:tnt` by using the name
+
+ :experimental:tnt
+
+when registering it.
+(also that mod is required to have `experimental` as a dependency)
+
+The `:` prefix can also be used for maintaining backwards compatibility.
+
+Sounds
+------
+**NOTE: max_hear_distance and connecting to objects is not implemented.**
+
+Only Ogg Vorbis files are supported.
+
+For positional playing of sounds, only single-channel (mono) files are
+supported. Otherwise OpenAL will play them non-positionally.
+
+Mods should generally prefix their sounds with `modname_`, e.g. given
+the mod name "`foomod`", a sound could be called:
+
+ foomod_foosound.ogg
+
+Sounds are referred to by their name with a dot, a single digit and the
+file extension stripped out. When a sound is played, the actual sound file
+is chosen randomly from the matching sounds.
+
+When playing the sound `foomod_foosound`, the sound is chosen randomly
+from the available ones of the following files:
+
+* `foomod_foosound.ogg`
+* `foomod_foosound.0.ogg`
+* `foomod_foosound.1.ogg`
+* (...)
+* `foomod_foosound.9.ogg`
+
+Examples of sound parameter tables:
+
+ -- Play locationless
+ {
+ gain = 1.0, -- default
+ }
+ -- Play locationless, looped
+ {
+ gain = 1.0, -- default
+ loop = true,
+ }
+ -- Play in a location
+ {
+ pos = {x = 1, y = 2, z = 3},
+ gain = 1.0, -- default
+ max_hear_distance = 32, -- default, uses an euclidean metric
+ }
+ -- Play connected to an object, looped
+ {
+ object = <an ObjectRef>,
+ gain = 1.0, -- default
+ max_hear_distance = 32, -- default, uses an euclidean metric
+ loop = true,
+ }
+
+Looped sounds must either be connected to an object or played locationless.
+
+### SimpleSoundSpec
+* e.g. `""`
+* e.g. `"default_place_node"`
+* e.g. `{}`
+* e.g. `{name = "default_place_node"}`
+* e.g. `{name = "default_place_node", gain = 1.0}`
+
+Representations of simple things
+--------------------------------
+
+### Position/vector
+
+ {x=num, y=num, z=num}
+
+For helper functions see "Vector helpers".
+
+### pointed_thing
+* `{type="nothing"}`
+* `{type="node", under=pos, above=pos}`
+* `{type="object", id=ObjectID}`
+
+Flag Specifier Format
+---------------------
+Flags using the standardized flag specifier format can be specified in either of
+two ways, by string or table.
+
+The string format is a comma-delimited set of flag names; whitespace and
+unrecognized flag fields are ignored. Specifying a flag in the string sets the
+flag, and specifying a flag prefixed by the string `"no"` explicitly
+clears the flag from whatever the default may be.
+
+In addition to the standard string flag format, the schematic flags field can
+also be a table of flag names to boolean values representing whether or not the
+flag is set. Additionally, if a field with the flag name prefixed with `"no"`
+is present, mapped to a boolean of any value, the specified flag is unset.
+
+E.g. A flag field of value
+
+ {place_center_x = true, place_center_y=false, place_center_z=true}
+
+is equivalent to
+
+ {place_center_x = true, noplace_center_y=true, place_center_z=true}
+
+which is equivalent to
+
+ "place_center_x, noplace_center_y, place_center_z"
+
+or even
+
+ "place_center_x, place_center_z"
+
+since, by default, no schematic attributes are set.
+
+Formspec
+--------
+Formspec defines a menu. It is a string, with a somewhat strange format.
+
+Spaces and newlines can be inserted between the blocks, as is used in the
+examples.
+
+### Examples
+
+#### Chest
+
+ size[8,9]
+ list[context;main;0,0;8,4;]
+ list[current_player;main;0,5;8,4;]
+
+#### Furnace
+
+ size[8,9]
+ list[context;fuel;2,3;1,1;]
+ list[context;src;2,1;1,1;]
+ list[context;dst;5,1;2,2;]
+ list[current_player;main;0,5;8,4;]
+
+#### Minecraft-like player inventory
+
+ size[8,7.5]
+ image[1,0.6;1,2;player.png]
+ list[current_player;main;0,3.5;8,4;]
+ list[current_player;craft;3,0;3,3;]
+ list[current_player;craftpreview;7,1;1,1;]
+
+### Elements
+
+#### `size[<W>,<H>,<fixed_size>]`
+* Define the size of the menu in inventory slots
+* `fixed_size`: `true`/`false` (optional)
+* deprecated: `invsize[<W>,<H>;]`
+
+#### `container[<X>,<Y>]`
+* Start of a container block, moves all physical elements in the container by (X, Y)
+* Must have matching container_end
+* Containers can be nested, in which case the offsets are added
+ (child containers are relative to parent containers)
+
+#### `container_end[]`
+* End of a container, following elements are no longer relative to this container
+
+#### `list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;]`
+* Show an inventory list
+
+#### `list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;<starting item index>]`
+* Show an inventory list
+
+#### `listring[<inventory location>;<list name>]`
+* Allows to create a ring of inventory lists
+* Shift-clicking on items in one element of the ring
+ will send them to the next inventory list inside the ring
+* The first occurrence of an element inside the ring will
+ determine the inventory where items will be sent to
+
+#### `listring[]`
+* Shorthand for doing `listring[<inventory location>;<list name>]`
+ for the last two inventory lists added by list[...]
+
+#### `listcolors[<slot_bg_normal>;<slot_bg_hover>]`
+* Sets background color of slots as `ColorString`
+* Sets background color of slots on mouse hovering
+
+#### `listcolors[<slot_bg_normal>;<slot_bg_hover>;<slot_border>]`
+* Sets background color of slots as `ColorString`
+* Sets background color of slots on mouse hovering
+* Sets color of slots border
+
+#### `listcolors[<slot_bg_normal>;<slot_bg_hover>;<slot_border>;<tooltip_bgcolor>;<tooltip_fontcolor>]`
+* Sets background color of slots as `ColorString`
+* Sets background color of slots on mouse hovering
+* Sets color of slots border
+* Sets default background color of tooltips
+* Sets default font color of tooltips
+
+#### `tooltip[<gui_element_name>;<tooltip_text>;<bgcolor>,<fontcolor>]`
+* Adds tooltip for an element
+* `<bgcolor>` tooltip background color as `ColorString` (optional)
+* `<fontcolor>` tooltip font color as `ColorString` (optional)
+
+#### `image[<X>,<Y>;<W>,<H>;<texture name>]`
+* Show an image
+* Position and size units are inventory slots
+
+#### `item_image[<X>,<Y>;<W>,<H>;<item name>]`
+* Show an inventory image of registered item/node
+* Position and size units are inventory slots
+
+#### `bgcolor[<color>;<fullscreen>]`
+* Sets background color of formspec as `ColorString`
+* If `true`, the background color is drawn fullscreen (does not effect the size of the formspec)
+
+#### `background[<X>,<Y>;<W>,<H>;<texture name>]`
+* Use a background. Inventory rectangles are not drawn then.
+* Position and size units are inventory slots
+* Example for formspec 8x4 in 16x resolution: image shall be sized
+ 8 times 16px times 4 times 16px.
+
+#### `background[<X>,<Y>;<W>,<H>;<texture name>;<auto_clip>]`
+* Use a background. Inventory rectangles are not drawn then.
+* Position and size units are inventory slots
+* Example for formspec 8x4 in 16x resolution:
+ image shall be sized 8 times 16px times 4 times 16px
+* If `true` the background is clipped to formspec size
+ (`x` and `y` are used as offset values, `w` and `h` are ignored)
+
+#### `pwdfield[<X>,<Y>;<W>,<H>;<name>;<label>]`
+* Textual password style field; will be sent to server when a button is clicked
+* When enter is pressed in field, fields.key_enter_field will be sent with the name
+ of this field.
+* `x` and `y` position the field relative to the top left of the menu
+* `w` and `h` are the size of the field
+* Fields are a set height, but will be vertically centred on `h`
+* Position and size units are inventory slots
+* `name` is the name of the field as returned in fields to `on_receive_fields`
+* `label`, if not blank, will be text printed on the top left above the field
+* See field_close_on_enter to stop enter closing the formspec
+
+#### `field[<X>,<Y>;<W>,<H>;<name>;<label>;<default>]`
+* Textual field; will be sent to server when a button is clicked
+* When enter is pressed in field, fields.key_enter_field will be sent with the name
+ of this field.
+* `x` and `y` position the field relative to the top left of the menu
+* `w` and `h` are the size of the field
+* Fields are a set height, but will be vertically centred on `h`
+* Position and size units are inventory slots
+* `name` is the name of the field as returned in fields to `on_receive_fields`
+* `label`, if not blank, will be text printed on the top left above the field
+* `default` is the default value of the field
+ * `default` may contain variable references such as `${text}'` which
+ will fill the value from the metadata value `text`
+ * **Note**: no extra text or more than a single variable is supported ATM.
+* See field_close_on_enter to stop enter closing the formspec
+
+#### `field[<name>;<label>;<default>]`
+* As above, but without position/size units
+* When enter is pressed in field, fields.key_enter_field will be sent with the name
+ of this field.
+* Special field for creating simple forms, such as sign text input
+* Must be used without a `size[]` element
+* A "Proceed" button will be added automatically
+* See field_close_on_enter to stop enter closing the formspec
+
+#### `field_close_on_enter[<name>;<close_on_enter>]`
+* <name> is the name of the field
+* if <close_on_enter> is false, pressing enter in the field will submit the form but not close it
+* defaults to true when not specified (ie: no tag for a field)
+
+#### `textarea[<X>,<Y>;<W>,<H>;<name>;<label>;<default>]`
+* Same as fields above, but with multi-line input
+
+#### `label[<X>,<Y>;<label>]`
+* `x` and `y` work as per field
+* `label` is the text on the label
+* Position and size units are inventory slots
+
+#### `vertlabel[<X>,<Y>;<label>]`
+* Textual label drawn vertically
+* `x` and `y` work as per field
+* `label` is the text on the label
+* Position and size units are inventory slots
+
+#### `button[<X>,<Y>;<W>,<H>;<name>;<label>]`
+* Clickable button. When clicked, fields will be sent.
+* `x`, `y` and `name` work as per field
+* `w` and `h` are the size of the button
+* `label` is the text on the button
+* Position and size units are inventory slots
+
+#### `image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]`
+* `x`, `y`, `w`, `h`, and `name` work as per button
+* `texture name` is the filename of an image
+* Position and size units are inventory slots
+
+#### `image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>;<noclip>;<drawborder>;<pressed texture name>]`
+* `x`, `y`, `w`, `h`, and `name` work as per button
+* `texture name` is the filename of an image
+* Position and size units are inventory slots
+* `noclip=true` means the image button doesn't need to be within specified formsize
+* `drawborder`: draw button border or not
+* `pressed texture name` is the filename of an image on pressed state
+
+#### `item_image_button[<X>,<Y>;<W>,<H>;<item name>;<name>;<label>]`
+* `x`, `y`, `w`, `h`, `name` and `label` work as per button
+* `item name` is the registered name of an item/node,
+ tooltip will be made out of its description
+ to override it use tooltip element
+* Position and size units are inventory slots
+
+#### `button_exit[<X>,<Y>;<W>,<H>;<name>;<label>]`
+* When clicked, fields will be sent and the form will quit.
+
+#### `image_button_exit[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]`
+* When clicked, fields will be sent and the form will quit.
+
+#### `textlist[<X>,<Y>;<W>,<H>;<name>;<listelem 1>,<listelem 2>,...,<listelem n>]`
+* Scrollable item list showing arbitrary text elements
+* `x` and `y` position the itemlist relative to the top left of the menu
+* `w` and `h` are the size of the itemlist
+* `name` fieldname sent to server on doubleclick value is current selected element
+* `listelements` can be prepended by #color in hexadecimal format RRGGBB (only),
+ * if you want a listelement to start with "#" write "##".
+
+#### `textlist[<X>,<Y>;<W>,<H>;<name>;<listelem 1>,<listelem 2>,...,<listelem n>;<selected idx>;<transparent>]`
+* Scrollable itemlist showing arbitrary text elements
+* `x` and `y` position the item list relative to the top left of the menu
+* `w` and `h` are the size of the item list
+* `name` fieldname sent to server on doubleclick value is current selected element
+* `listelements` can be prepended by #RRGGBB (only) in hexadecimal format
+ * if you want a listelement to start with "#" write "##"
+* Index to be selected within textlist
+* `true`/`false`: draw transparent background
+* See also `minetest.explode_textlist_event` (main menu: `engine.explode_textlist_event`)
+
+#### `tabheader[<X>,<Y>;<name>;<caption 1>,<caption 2>,...,<caption n>;<current_tab>;<transparent>;<draw_border>]`
+* Show a tab**header** at specific position (ignores formsize)
+* `x` and `y` position the itemlist relative to the top left of the menu
+* `name` fieldname data is transferred to Lua
+* `caption 1`...: name shown on top of tab
+* `current_tab`: index of selected tab 1...
+* `transparent` (optional): show transparent
+* `draw_border` (optional): draw border
+
+#### `box[<X>,<Y>;<W>,<H>;<color>]`
+* Simple colored semitransparent box
+* `x` and `y` position the box relative to the top left of the menu
+* `w` and `h` are the size of box
+* `color` is color specified as a `ColorString`
+
+#### `dropdown[<X>,<Y>;<W>;<name>;<item 1>,<item 2>, ...,<item n>;<selected idx>]`
+* Show a dropdown field
+* **Important note**: There are two different operation modes:
+ 1. handle directly on change (only changed dropdown is submitted)
+ 2. read the value on pressing a button (all dropdown values are available)
+* `x` and `y` position of dropdown
+* Width of dropdown
+* Fieldname data is transferred to Lua
+* Items to be shown in dropdown
+* Index of currently selected dropdown item
+
+#### `checkbox[<X>,<Y>;<name>;<label>;<selected>]`
+* Show a checkbox
+* `x` and `y`: position of checkbox
+* `name` fieldname data is transferred to Lua
+* `label` to be shown left of checkbox
+* `selected` (optional): `true`/`false`
+
+#### `scrollbar[<X>,<Y>;<W>,<H>;<orientation>;<name>;<value>]`
+* Show a scrollbar
+* There are two ways to use it:
+ 1. handle the changed event (only changed scrollbar is available)
+ 2. read the value on pressing a button (all scrollbars are available)
+* `x` and `y`: position of trackbar
+* `w` and `h`: width and height
+* `orientation`: `vertical`/`horizontal`
+* Fieldname data is transferred to Lua
+* Value this trackbar is set to (`0`-`1000`)
+* See also `minetest.explode_scrollbar_event` (main menu: `engine.explode_scrollbar_event`)
+
+#### `table[<X>,<Y>;<W>,<H>;<name>;<cell 1>,<cell 2>,...,<cell n>;<selected idx>]`
+* Show scrollable table using options defined by the previous `tableoptions[]`
+* Displays cells as defined by the previous `tablecolumns[]`
+* `x` and `y`: position the itemlist relative to the top left of the menu
+* `w` and `h` are the size of the itemlist
+* `name`: fieldname sent to server on row select or doubleclick
+* `cell 1`...`cell n`: cell contents given in row-major order
+* `selected idx`: index of row to be selected within table (first row = `1`)
+* See also `minetest.explode_table_event` (main menu: `engine.explode_table_event`)
+
+#### `tableoptions[<opt 1>;<opt 2>;...]`
+* Sets options for `table[]`
+* `color=#RRGGBB`
+ * default text color (`ColorString`), defaults to `#FFFFFF`
+* `background=#RRGGBB`
+ * table background color (`ColorString`), defaults to `#000000`
+* `border=<true/false>`
+ * should the table be drawn with a border? (default: `true`)
+* `highlight=#RRGGBB`
+ * highlight background color (`ColorString`), defaults to `#466432`
+* `highlight_text=#RRGGBB`
+ * highlight text color (`ColorString`), defaults to `#FFFFFF`
+* `opendepth=<value>`
+ * all subtrees up to `depth < value` are open (default value = `0`)
+ * only useful when there is a column of type "tree"
+
+#### `tablecolumns[<type 1>,<opt 1a>,<opt 1b>,...;<type 2>,<opt 2a>,<opt 2b>;...]`
+* Sets columns for `table[]`
+* Types: `text`, `image`, `color`, `indent`, `tree`
+ * `text`: show cell contents as text
+ * `image`: cell contents are an image index, use column options to define images
+ * `color`: cell contents are a ColorString and define color of following cell
+ * `indent`: cell contents are a number and define indentation of following cell
+ * `tree`: same as indent, but user can open and close subtrees (treeview-like)
+* Column options:
+ * `align=<value>`
+ * for `text` and `image`: content alignment within cells.
+ Available values: `left` (default), `center`, `right`, `inline`
+ * `width=<value>`
+ * for `text` and `image`: minimum width in em (default: `0`)
+ * for `indent` and `tree`: indent width in em (default: `1.5`)
+ * `padding=<value>`: padding left of the column, in em (default `0.5`).
+ Exception: defaults to 0 for indent columns
+ * `tooltip=<value>`: tooltip text (default: empty)
+ * `image` column options:
+ * `0=<value>` sets image for image index 0
+ * `1=<value>` sets image for image index 1
+ * `2=<value>` sets image for image index 2
+ * and so on; defined indices need not be contiguous empty or
+ non-numeric cells are treated as `0`.
+ * `color` column options:
+ * `span=<value>`: number of following columns to affect (default: infinite)
+
+**Note**: do _not_ use a element name starting with `key_`; those names are reserved to
+pass key press events to formspec!
+
+Spatial Vectors
+---------------
+* `vector.new(a[, b, c])`: returns a vector:
+ * A copy of `a` if `a` is a vector.
+ * `{x = a, y = b, z = c}`, if all `a, b, c` are defined
+* `vector.direction(p1, p2)`: returns a vector
+* `vector.distance(p1, p2)`: returns a number
+* `vector.length(v)`: returns a number
+* `vector.normalize(v)`: returns a vector
+* `vector.floor(v)`: returns a vector, each dimension rounded down
+* `vector.round(v)`: returns a vector, each dimension rounded to nearest int
+* `vector.apply(v, func)`: returns a vector
+* `vector.equals(v1, v2)`: returns a boolean
+
+For the following functions `x` can be either a vector or a number:
+
+* `vector.add(v, x)`: returns a vector
+* `vector.subtract(v, x)`: returns a vector
+* `vector.multiply(v, x)`: returns a scaled vector or Schur product
+* `vector.divide(v, x)`: returns a scaled vector or Schur quotient
+
+Helper functions
+----------------
+* `dump2(obj, name="_", dumped={})`
+ * Return object serialized as a string, handles reference loops
+* `dump(obj, dumped={})`
+ * Return object serialized as a string
+* `math.hypot(x, y)`
+ * Get the hypotenuse of a triangle with legs x and y.
+ Useful for distance calculation.
+* `math.sign(x, tolerance)`
+ * Get the sign of a number.
+ Optional: Also returns `0` when the absolute value is within the tolerance (default: `0`)
+* `string.split(str, separator=",", include_empty=false, max_splits=-1, sep_is_pattern=false)`
+ * If `max_splits` is negative, do not limit splits.
+ * `sep_is_pattern` specifies if separator is a plain string or a pattern (regex).
+ * e.g. `string:split("a,b", ",") == {"a","b"}`
+* `string:trim()`
+ * e.g. `string.trim("\n \t\tfoo bar\t ") == "foo bar"`
+* `minetest.wrap_text(str, limit)`: returns a string
+ * Adds new lines to the string to keep it within the specified character limit
+ * limit: Maximal amount of characters in one line
+* `minetest.pos_to_string({x=X,y=Y,z=Z}, decimal_places))`: returns string `"(X,Y,Z)"`
+ * Convert position to a printable string
+ Optional: 'decimal_places' will round the x, y and z of the pos to the given decimal place.
+* `minetest.string_to_pos(string)`: returns a position
+ * Same but in reverse. Returns `nil` if the string can't be parsed to a position.
+* `minetest.string_to_area("(X1, Y1, Z1) (X2, Y2, Z2)")`: returns two positions
+ * Converts a string representing an area box into two positions
+* `minetest.is_yes(arg)`
+ * returns whether `arg` can be interpreted as yes
+* `table.copy(table)`: returns a table
+ * returns a deep copy of `table`
+
+Minetest namespace reference
+------------------------------
+
+### Utilities
+
+* `minetest.get_current_modname()`: returns the currently loading mod's name, when we are loading a mod
+* `minetest.get_version()`: returns a table containing components of the
+ engine version. Components:
+ * `project`: Name of the project, eg, "Minetest"
+ * `string`: Simple version, eg, "1.2.3-dev"
+ * `hash`: Full git version (only set if available), eg, "1.2.3-dev-01234567-dirty"
+ Use this for informational purposes only. The information in the returned
+ table does not represent the capabilities of the engine, nor is it
+ reliable or verifyable. Compatible forks will have a different name and
+ version entirely. To check for the presence of engine features, test
+ whether the functions exported by the wanted features exist. For example:
+ `if minetest.nodeupdate then ... end`.
+* `minetest.sha1(data, [raw])`: returns the sha1 hash of data
+ * `data`: string of data to hash
+ * `raw`: return raw bytes instead of hex digits, default: false
+
+### Logging
+* `minetest.debug(...)`
+ * Equivalent to `minetest.log(table.concat({...}, "\t"))`
+* `minetest.log([level,] text)`
+ * `level` is one of `"none"`, `"error"`, `"warning"`, `"action"`,
+ `"info"`, or `"verbose"`. Default is `"none"`.
+
+### Global callback registration functions
+Call these functions only at load time!
+
+* `minetest.register_globalstep(func(dtime))`
+ * Called every client environment step, usually interval of 0.1s
+* `minetest.register_on_shutdown(func())`
+ * Called before client shutdown
+ * **Warning**: If the client terminates abnormally (i.e. crashes), the registered
+ callbacks **will likely not be run**. Data should be saved at
+ semi-frequent intervals as well as on server shutdown.
+* `minetest.register_on_connect(func())`
+ * Called at the end of client connection (when player is loaded onto map)
+* `minetest.register_on_receiving_chat_message(func(name, message))`
+ * Called always when a client receive a message
+ * Return `true` to mark the message as handled, which means that it will not be shown to chat
+* `minetest.register_on_sending_chat_message(func(name, message))`
+ * Called always when a client send a message from chat
+ * Return `true` to mark the message as handled, which means that it will not be sent to server
+* `minetest.register_chatcommand(cmd, chatcommand definition)`
+ * Adds definition to minetest.registered_chatcommands
+* `minetest.unregister_chatcommand(name)`
+ * Unregisters a chatcommands registered with register_chatcommand.
+* `minetest.register_on_death(func())`
+ * Called when the local player dies
+* `minetest.register_on_hp_modification(func(hp))`
+ * Called when server modified player's HP
+* `minetest.register_on_damage_taken(func(hp))`
+ * Called when the local player take damages
+* `minetest.register_on_formspec_input(func(formname, fields))`
+ * Called when a button is pressed in the local player's inventory form
+ * Newest functions are called first
+ * If function returns `true`, remaining functions are not called
+* `minetest.register_on_dignode(func(pos, node))`
+ * Called when the local player digs a node
+ * Newest functions are called first
+ * If any function returns true, the node isn't dug
+* `minetest.register_on_punchnode(func(pos, node))`
+ * Called when the local player punches a node
+ * Newest functions are called first
+ * If any function returns true, the punch is ignored
+* `minetest.register_on_placenode(function(pointed_thing, node))`
+ * Called when a node has been placed
+* `minetest.register_on_item_use(func(item, pointed_thing))`
+ * Called when the local player uses an item.
+ * Newest functions are called first.
+ * If any function returns true, the item use is not sent to server.
+### Sounds
+* `minetest.sound_play(spec, parameters)`: returns a handle
+ * `spec` is a `SimpleSoundSpec`
+ * `parameters` is a sound parameter table
+* `minetest.sound_stop(handle)`
+
+### Timing
+* `minetest.after(time, func, ...)`
+ * Call the function `func` after `time` seconds, may be fractional
+ * Optional: Variable number of arguments that are passed to `func`
+* `minetest.get_us_time()`
+ * Returns time with microsecond precision. May not return wall time.
+* `minetest.get_day_count()`
+ * Returns number days elapsed since world was created, accounting for time changes.
+* `minetest.get_timeofday()`
+ * Returns the time of day: `0` for midnight, `0.5` for midday
+
+### Map
+* `minetest.get_node(pos)`
+ * Returns the node at the given position as table in the format
+ `{name="node_name", param1=0, param2=0}`, returns `{name="ignore", param1=0, param2=0}`
+ for unloaded areas.
+* `minetest.get_node_or_nil(pos)`
+ * Same as `get_node` but returns `nil` for unloaded areas.
+* `minetest.find_node_near(pos, radius, nodenames, [search_center])`: returns pos or `nil`
+ * `radius`: using a maximum metric
+ * `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"`
+ * `search_center` is an optional boolean (default: `false`)
+ If true `pos` is also checked for the nodes
+* `minetest.get_meta(pos)`
+ * Get a `NodeMetaRef` at that position
+* `minetest.get_node_level(pos)`
+ * get level of leveled node (water, snow)
+* `minetest.get_node_max_level(pos)`
+ * get max available level for leveled node
+
+### Player
+* `minetest.get_wielded_item()`
+ * Returns the itemstack the local player is holding
+* `minetest.send_chat_message(message)`
+ * Act as if `message` was typed by the player into the terminal.
+* `minetest.run_server_chatcommand(cmd, param)`
+ * Alias for `minetest.send_chat_message("/" .. cmd .. " " .. param)`
+* `minetest.clear_out_chat_queue()`
+ * Clears the out chat queue
+* `minetest.localplayer`
+ * Reference to the LocalPlayer object. See [`LocalPlayer`](#localplayer) class reference for methods.
+
+### Client Environment
+* `minetest.get_player_names()`
+ * Returns list of player names on server
+* `minetest.disconnect()`
+ * Disconnect from the server and exit to main menu.
+ * Returns `false` if the client is already disconnecting otherwise returns `true`.
+* `minetest.take_screenshot()`
+ * Take a screenshot.
+* `minetest.get_server_info()`
+ * Returns [server info](#server-info).
+* `minetest.send_respawn()`
+ * Sends a respawn request to the server.
+
+### Storage API
+* `minetest.get_mod_storage()`:
+ * returns reference to mod private `StorageRef`
+ * must be called during mod load time
+
+### Misc.
+* `minetest.parse_json(string[, nullvalue])`: returns something
+ * Convert a string containing JSON data into the Lua equivalent
+ * `nullvalue`: returned in place of the JSON null; defaults to `nil`
+ * On success returns a table, a string, a number, a boolean or `nullvalue`
+ * On failure outputs an error message and returns `nil`
+ * Example: `parse_json("[10, {\"a\":false}]")`, returns `{10, {a = false}}`
+* `minetest.write_json(data[, styled])`: returns a string or `nil` and an error message
+ * Convert a Lua table into a JSON string
+ * styled: Outputs in a human-readable format if this is set, defaults to false
+ * Unserializable things like functions and userdata are saved as null.
+ * **Warning**: JSON is more strict than the Lua table format.
+ 1. You can only use strings and positive integers of at least one as keys.
+ 2. You can not mix string and integer keys.
+ This is due to the fact that JSON has two distinct array and object values.
+ * Example: `write_json({10, {a = false}})`, returns `"[10, {\"a\": false}]"`
+* `minetest.serialize(table)`: returns a string
+ * Convert a table containing tables, strings, numbers, booleans and `nil`s
+ into string form readable by `minetest.deserialize`
+ * Example: `serialize({foo='bar'})`, returns `'return { ["foo"] = "bar" }'`
+* `minetest.deserialize(string)`: returns a table
+ * Convert a string returned by `minetest.deserialize` into a table
+ * `string` is loaded in an empty sandbox environment.
+ * Will load functions, but they cannot access the global environment.
+ * Example: `deserialize('return { ["foo"] = "bar" }')`, returns `{foo='bar'}`
+ * Example: `deserialize('print("foo")')`, returns `nil` (function call fails)
+ * `error:[string "print("foo")"]:1: attempt to call global 'print' (a nil value)`
+* `minetest.compress(data, method, ...)`: returns `compressed_data`
+ * Compress a string of data.
+ * `method` is a string identifying the compression method to be used.
+ * Supported compression methods:
+ * Deflate (zlib): `"deflate"`
+ * `...` indicates method-specific arguments. Currently defined arguments are:
+ * Deflate: `level` - Compression level, `0`-`9` or `nil`.
+* `minetest.decompress(compressed_data, method, ...)`: returns data
+ * Decompress a string of data (using ZLib).
+ * See documentation on `minetest.compress()` for supported compression methods.
+ * currently supported.
+ * `...` indicates method-specific arguments. Currently, no methods use this.
+* `minetest.encode_base64(string)`: returns string encoded in base64
+ * Encodes a string in base64.
+* `minetest.decode_base64(string)`: returns string
+ * Decodes a string encoded in base64.
+* `minetest.gettext(string)` : returns string
+ * look up the translation of a string in the gettext message catalog
+* `fgettext_ne(string, ...)`
+ * call minetest.gettext(string), replace "$1"..."$9" with the given
+ extra arguments and return the result
+* `fgettext(string, ...)` : returns string
+ * same as fgettext_ne(), but calls minetest.formspec_escape before returning result
+* `minetest.pointed_thing_to_face_pos(placer, pointed_thing)`: returns a position
+ * returns the exact position on the surface of a pointed node
+* `minetest.global_exists(name)`
+ * Checks if a global variable has been set, without triggering a warning.
+
+### UI
+* `minetest.ui.minimap`
+ * Reference to the minimap object. See [`Minimap`](#minimap) class reference for methods.
+ * If client disabled minimap (using enable_minimap setting) this reference will be nil.
+* `minetest.camera`
+ * Reference to the camera object. See [`Camera`](#camera) class reference for methods.
+* `minetest.show_formspec(formname, formspec)` : returns true on success
+ * Shows a formspec to the player
+* `minetest.display_chat_message(message)` returns true on success
+ * Shows a chat message to the current player.
+
+Class reference
+---------------
+
+### Minimap
+An interface to manipulate minimap on client UI
+
+* `show()`: shows the minimap (if not disabled by server)
+* `hide()`: hides the minimap
+* `set_pos(pos)`: sets the minimap position on screen
+* `get_pos()`: returns the minimap current position
+* `set_angle(deg)`: sets the minimap angle in degrees
+* `get_angle()`: returns the current minimap angle in degrees
+* `set_mode(mode)`: sets the minimap mode (0 to 6)
+* `get_mode()`: returns the current minimap mode
+* `set_shape(shape)`: Sets the minimap shape. (0 = square, 1 = round)
+* `get_shape()`: Gets the minimap shape. (0 = square, 1 = round)
+
+### Camera
+An interface to get or set information about the camera and cameranode.
+Please do not try to access the reference until the camera is initialized, otherwise the reference will be nil.
+
+#### Methods
+* `set_camera_mode(mode)`
+ * Pass `0` for first-person, `1` for third person, and `2` for third person front
+* `get_camera_mode()`
+ * Returns with same syntax as above
+* `get_fov()`
+ * Returns:
+
+```lua
+ {
+ x = number,
+ y = number,
+ max = number,
+ actual = number
+ }
+```
+
+* `get_pos()`
+ * Returns position of camera with view bobbing
+* `get_offset()`
+ * Returns eye offset vector
+* `get_look_dir()`
+ * Returns eye direction unit vector
+* `get_look_vertical()`
+ * Returns pitch in radians
+* `get_look_horizontal()`
+ * Returns yaw in radians
+* `get_aspect_ratio()`
+ * Returns aspect ratio of screen
+
+### LocalPlayer
+An interface to retrieve information about the player. The player is
+not accessible until the client is fully done loading and therefore
+not at module init time.
+
+To get the localplayer handle correctly, use `on_connect()` as follows:
+
+```lua
+local localplayer
+minetest.register_on_connect(function()
+ localplayer = minetest.localplayer
+end)
+```
+
+Methods:
+
+* `get_pos()`
+ * returns current player current position
+* `get_velocity()`
+ * returns player speed vector
+* `get_hp()`
+ * returns player HP
+* `get_name()`
+ * returns player name
+* `is_attached()`
+ * returns true if player is attached
+* `is_touching_ground()`
+ * returns true if player touching ground
+* `is_in_liquid()`
+ * returns true if player is in a liquid (This oscillates so that the player jumps a bit above the surface)
+* `is_in_liquid_stable()`
+ * returns true if player is in a stable liquid (This is more stable and defines the maximum speed of the player)
+* `get_liquid_viscosity()`
+ * returns liquid viscosity (Gets the viscosity of liquid to calculate friction)
+* `is_climbing()`
+ * returns true if player is climbing
+* `swimming_vertical()`
+ * returns true if player is swimming in vertical
+* `get_physics_override()`
+ * returns:
+
+```lua
+ {
+ speed = float,
+ jump = float,
+ gravity = float,
+ sneak = boolean,
+ sneak_glitch = boolean
+ }
+```
+
+* `get_override_pos()`
+ * returns override position
+* `get_last_pos()`
+ * returns last player position before the current client step
+* `get_last_velocity()`
+ * returns last player speed
+* `get_breath()`
+ * returns the player's breath
+* `get_movement_acceleration()`
+ * returns acceleration of the player in different environments:
+
+```lua
+ {
+ fast = float,
+ air = float,
+ default = float,
+ }
+```
+
+* `get_movement_speed()`
+ * returns player's speed in different environments:
+
+```lua
+ {
+ walk = float,
+ jump = float,
+ crouch = float,
+ fast = float,
+ climb = float,
+ }
+```
+
+* `get_movement()`
+ * returns player's movement in different environments:
+
+```lua
+ {
+ liquid_fluidity = float,
+ liquid_sink = float,
+ liquid_fluidity_smooth = float,
+ gravity = float,
+ }
+```
+
+* `get_last_look_horizontal()`:
+ * returns last look horizontal angle
+* `get_last_look_vertical()`:
+ * returns last look vertical angle
+* `get_key_pressed()`:
+ * returns last key typed by the player
+
+### Settings
+An interface to read config files in the format of `minetest.conf`.
+
+It can be created via `Settings(filename)`.
+
+#### Methods
+* `get(key)`: returns a value
+* `get_bool(key)`: returns a boolean
+* `set(key, value)`
+* `remove(key)`: returns a boolean (`true` for success)
+* `get_names()`: returns `{key1,...}`
+* `write()`: returns a boolean (`true` for success)
+ * write changes to file
+* `to_table()`: returns `{[key1]=value1,...}`
+
+### NodeMetaRef
+Node metadata: reference extra data and functionality stored in a node.
+Can be obtained via `minetest.get_meta(pos)`.
+
+#### Methods
+* `get_string(name)`
+* `get_int(name)`
+* `get_float(name)`
+* `to_table()`: returns `nil` or a table with keys:
+ * `fields`: key-value storage
+ * `inventory`: `{list1 = {}, ...}}`
+
+-----------------
+### Definitions
+* `minetest.get_node_def(nodename)`
+ * Returns [node definition](#node-definition) table of `nodename`
+* `minetest.get_item_def(itemstring)`
+ * Returns item definition table of `itemstring`
+
+#### Node Definition
+
+```lua
+ {
+ has_on_construct = bool, -- Whether the node has the on_construct callback defined
+ has_on_destruct = bool, -- Whether the node has the on_destruct callback defined
+ has_after_destruct = bool, -- Whether the node has the after_destruct callback defined
+ name = string, -- The name of the node e.g. "air", "default:dirt"
+ groups = table, -- The groups of the node
+ paramtype = string, -- Paramtype of the node
+ paramtype2 = string, -- ParamType2 of the node
+ drawtype = string, -- Drawtype of the node
+ mesh = <string>, -- Mesh name if existant
+ minimap_color = <Color>, -- Color of node on minimap *May not exist*
+ visual_scale = number, -- Visual scale of node
+ alpha = number, -- Alpha of the node. Only used for liquids
+ color = <Color>, -- Color of node *May not exist*
+ palette_name = <string>, -- Filename of palette *May not exist*
+ palette = <{ -- List of colors
+ Color,
+ Color
+ }>,
+ waving = number, -- 0 of not waving, 1 if waving
+ connect_sides = number, -- Used for connected nodes
+ connects_to = { -- List of nodes to connect to
+ "node1",
+ "node2"
+ },
+ post_effect_color = Color, -- Color overlayed on the screen when the player is in the node
+ leveled = number, -- Max level for node
+ sunlight_propogates = bool, -- Whether light passes through the block
+ light_source = number, -- Light emitted by the block
+ is_ground_content = bool, -- Whether caves should cut through the node
+ walkable = bool, -- Whether the player collides with the node
+ pointable = bool, -- Whether the player can select the node
+ diggable = bool, -- Whether the player can dig the node
+ climbable = bool, -- Whether the player can climb up the node
+ buildable_to = bool, -- Whether the player can replace the node by placing a node on it
+ rightclickable = bool, -- Whether the player can place nodes pointing at this node
+ damage_per_second = number, -- HP of damage per second when the player is in the node
+ liquid_type = <string>, -- A string containing "none", "flowing", or "source" *May not exist*
+ liquid_alternative_flowing = <string>, -- Alternative node for liquid *May not exist*
+ liquid_alternative_source = <string>, -- Alternative node for liquid *May not exist*
+ liquid_viscosity = <number>, -- How fast the liquid flows *May not exist*
+ liquid_renewable = <boolean>, -- Whether the liquid makes an infinite source *May not exist*
+ liquid_range = <number>, -- How far the liquid flows *May not exist*
+ drowning = bool, -- Whether the player will drown in the node
+ floodable = bool, -- Whether nodes will be replaced by liquids (flooded)
+ node_box = table, -- Nodebox to draw the node with
+ collision_box = table, -- Nodebox to set the collision area
+ selection_box = table, -- Nodebox to set the area selected by the player
+ sounds = { -- Table of sounds that the block makes
+ sound_footstep = SimpleSoundSpec,
+ sound_dig = SimpleSoundSpec,
+ sound_dug = SimpleSoundSpec
+ },
+ legacy_facedir_simple = bool, -- Whether to use old facedir
+ legacy_wallmounted = bool -- Whether to use old wallmounted
+ }
+```
+
+#### Item Definition
+
+```lua
+ {
+ name = string, -- Name of the item e.g. "default:stone"
+ description = string, -- Description of the item e.g. "Stone"
+ type = string, -- Item type: "none", "node", "craftitem", "tool"
+ inventory_image = string, -- Image in the inventory
+ wield_image = string, -- Image in wieldmesh
+ palette_image = string, -- Image for palette
+ color = Color, -- Color for item
+ wield_scale = Vector, -- Wieldmesh scale
+ stack_max = number, -- Number of items stackable together
+ usable = bool, -- Has on_use callback defined
+ liquids_pointable = bool, -- Whether you can point at liquids with the item
+ tool_capabilities = <table>, -- If the item is a tool, tool capabiltites of the item
+ groups = table, -- Groups of the item
+ sound_place = SimpleSoundSpec, -- Sound played when placed
+ sound_place_failed = SimpleSoundSpec, -- Sound played when placement failed
+ node_placement_prediction = string -- Node placed in client until server catches up
+ }
+```
+-----------------
+
+### Chat command definition (`register_chatcommand`)
+
+ {
+ params = "<name> <privilege>", -- Short parameter description
+ description = "Remove privilege from player", -- Full description
+ func = function(param), -- Called when command is run.
+ -- Returns boolean success and text output.
+ }
+### Server info
+```lua
+{
+ address = "minetest.example.org", -- The domain name/IP address of a remote server or "" for a local server.
+ ip = "203.0.113.156", -- The IP address of the server.
+ port = 30000, -- The port the client is connected to.
+ protocol_version = 30 -- Will not be accurate at start up as the client might not be connected to the server yet, in that case it will be 0.
+}
+```
+
+Escape sequences
+----------------
+Most text can contain escape sequences, that can for example color the text.
+There are a few exceptions: tab headers, dropdowns and vertical labels can't.
+The following functions provide escape sequences:
+* `minetest.get_color_escape_sequence(color)`:
+ * `color` is a [ColorString](#colorstring)
+ * The escape sequence sets the text color to `color`
+* `minetest.colorize(color, message)`:
+ * Equivalent to:
+ `minetest.get_color_escape_sequence(color) ..
+ message ..
+ minetest.get_color_escape_sequence("#ffffff")`
+* `minetest.get_background_escape_sequence(color)`
+ * `color` is a [ColorString](#colorstring)
+ * The escape sequence sets the background of the whole text element to
+ `color`. Only defined for item descriptions and tooltips.
+* `minetest.strip_foreground_colors(str)`
+ * Removes foreground colors added by `get_color_escape_sequence`.
+* `minetest.strip_background_colors(str)`
+ * Removes background colors added by `get_background_escape_sequence`.
+* `minetest.strip_colors(str)`
+ * Removes all color escape sequences.
+
+`ColorString`
+-------------
+`#RGB` defines a color in hexadecimal format.
+
+`#RGBA` defines a color in hexadecimal format and alpha channel.
+
+`#RRGGBB` defines a color in hexadecimal format.
+
+`#RRGGBBAA` defines a color in hexadecimal format and alpha channel.
+
+Named colors are also supported and are equivalent to
+[CSS Color Module Level 4](http://dev.w3.org/csswg/css-color/#named-colors).
+To specify the value of the alpha channel, append `#AA` to the end of the color name
+(e.g. `colorname#08`). For named colors the hexadecimal string representing the alpha
+value must (always) be two hexadecimal digits.
+
+`Color`
+-------------
+`{a = alpha, r = red, g = green, b = blue}` defines an ARGB8 color.
diff --git a/doc/fst_api.txt b/doc/fst_api.txt
new file mode 100644
index 000000000..c8bcb1e91
--- /dev/null
+++ b/doc/fst_api.txt
@@ -0,0 +1,171 @@
+Formspec toolkit api 0.0.3
+==========================
+
+Formspec toolkit is a set of functions to create basic ui elements.
+
+
+File: fst/ui.lua
+----------------
+
+ui.lua adds base ui interface to add additional components to.
+
+ui.add(component) -> returns name of added component
+^ add component to ui
+^ component: component to add
+
+ui.delete(component) -> true/false if a component was deleted or not
+^ remove a component from ui
+^ component: component to delete
+
+ui.set_default(name)
+^ set component to show if not a single component is set visible
+^ name: name of component to set as default
+
+ui.find_by_name(name) --> returns component or nil
+^ find a component within ui
+^ name: name of component to look for
+
+File: fst/tabview.lua
+---------------------
+
+tabview_create(name, size, tabheaderpos) --> returns tabview component
+^ create a new tabview component
+^ name: name of tabview (has to be unique per ui)
+^ size: size of tabview
+ {
+ x,
+ y
+ }
+^ tabheaderpos: upper left position of tabheader (relative to upper left fs corner)
+ {
+ x,
+ y
+ }
+
+Class reference tabview:
+
+methods:
+- add_tab(tab)
+ ^ add a tab to this tabview
+ ^ tab:
+ {
+ name = "tabname", -- name of tab to create
+ caption = "tab caption", -- text to show for tab header
+ cbf_button_handler = function(tabview, fields, tabname, tabdata), -- callback for button events
+ --TODO cbf_events = function(tabview, event, tabname), -- callback for events
+ cbf_formspec = function(tabview, name, tabdata), -- get formspec
+ tabsize =
+ {
+ x, -- x width
+ y -- y height
+ }, -- special size for this tab (only relevant if no parent for tabview set)
+ on_change = function(type,old_tab,new_tab) -- called on tab chang, type is "ENTER" or "LEAVE"
+ }
+- set_autosave_tab(value)
+ ^ tell tabview to automatically save current tabname as "tabview_name"_LAST
+ ^ value: true/false
+- set_tab(name)
+ ^ set's tab to tab named "name", returns true/false on success
+ ^ name: name of tab to set
+- set_global_event_handler(handler)
+ ^ set a handler to be called prior calling tab specific event handler
+ ^ handler: function(tabview,event) --> returns true to finish event processing false to continue
+- set_global_button_handler(handler)
+ ^ set a handler to be called prior calling tab specific button handler
+ ^ handler: function(tabview,fields) --> returns true to finish button processing false to continue
+- set_parent(parent)
+ ^ set parent to attach tabview to. TV's with parent are hidden if their parent
+ is hidden and they don't set their specified size.
+ ^ parent: component to attach to
+- show()
+ ^ show tabview
+- hide()
+ ^ hide tabview
+- delete()
+ ^ delete tabview
+- set_fixed_size(state)
+ ^ true/false set to fixed size, variable size
+
+File: fst/dialog.lua
+---------------------
+Only one dialog can be shown at a time. If a dialog is closed it's parent is
+gonna be activated and shown again.
+
+dialog_create(name, cbf_formspec, cbf_button_handler, cbf_events)
+^ create a dialog component
+^ name: name of component (unique per ui)
+^ cbf_formspec: function to be called to get formspec
+ function(dialogdata)
+^ cbf_button_handler: function to handle buttons
+ function(dialog, fields)
+^ cbf_events: function to handle events
+ function(dialog, event)
+
+Class reference dialog:
+
+methods:
+- set_parent(parent)
+ ^ set parent to attach a dialog to
+ ^ parent: component to attach to
+- show()
+ ^ show dialog
+- hide()
+ ^ hide dialog
+- delete()
+ ^ delete dialog from ui
+
+members:
+- data
+ ^ variable data attached to this dialog
+- parent
+ ^ parent component to return to on exit
+
+File: fst/buttonbar.lua
+-----------------------
+
+buttonbar_create(name, cbf_buttonhandler, pos, orientation, size)
+^ create a buttonbar
+^ name: name of component (unique per ui)
+^ cbf_buttonhandler: function to be called on button pressed
+ function(buttonbar,buttonname,buttondata)
+^ pos: position relative to upper left of current shown formspec
+ {
+ x,
+ y
+ }
+^ orientation: "vertical" or "horizontal"
+^ size: size of bar
+ {
+ width,
+ height
+ }
+
+Class reference buttonbar:
+
+methods:
+- add_button(btn_id, caption, button_image)
+- set_parent(parent)
+ ^ set parent to attach a buttonbar to
+ ^ parent: component to attach to
+- show()
+ ^ show buttonbar
+- hide()
+ ^ hide buttonbar
+- delete()
+ ^ delete buttonbar from ui
+
+Developer doc:
+==============
+Skeleton for any component:
+{
+ name = "some id", -- unique id
+ type = "toplevel", -- type of component
+ -- toplevel: component can be show without additional components
+ -- addon: component is an addon to be shown along toplevel component
+ hide = function(this) end, -- called to hide the component
+ show = function(this) end, -- called to show the component
+ delete = function(this) end, -- called to delete component from ui
+ handle_buttons = function(this,fields) -- called upon button press
+ handle_events = function(this,event) -- called upon event reception
+ get_formspec = function(this) -- has to return formspec to be displayed
+}
diff --git a/doc/gpl-2.0.txt b/doc/gpl-2.0.txt
deleted file mode 100644
index d159169d1..000000000
--- a/doc/gpl-2.0.txt
+++ /dev/null
@@ -1,339 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users. This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it. (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.) You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
- To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have. You must make sure that they, too, receive or can get the
-source code. And you must show them these terms so they know their
-rights.
-
- We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
- Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software. If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
- Finally, any free program is threatened constantly by software
-patents. We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary. To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- GNU GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License. The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language. (Hereinafter, translation is included without limitation in
-the term "modification".) Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
- 1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
- 2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) You must cause the modified files to carry prominent notices
- stating that you changed the files and the date of any change.
-
- b) You must cause any work that you distribute or publish, that in
- whole or in part contains or is derived from the Program or any
- part thereof, to be licensed as a whole at no charge to all third
- parties under the terms of this License.
-
- c) If the modified program normally reads commands interactively
- when run, you must cause it, when started running for such
- interactive use in the most ordinary way, to print or display an
- announcement including an appropriate copyright notice and a
- notice that there is no warranty (or else, saying that you provide
- a warranty) and that users may redistribute the program under
- these conditions, and telling the user how to view a copy of this
- License. (Exception: if the Program itself is interactive but
- does not normally print such an announcement, your work based on
- the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
- a) Accompany it with the complete corresponding machine-readable
- source code, which must be distributed under the terms of Sections
- 1 and 2 above on a medium customarily used for software interchange; or,
-
- b) Accompany it with a written offer, valid for at least three
- years, to give any third party, for a charge no more than your
- cost of physically performing source distribution, a complete
- machine-readable copy of the corresponding source code, to be
- distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
-
- c) Accompany it with the information you received as to the offer
- to distribute corresponding source code. (This alternative is
- allowed only for noncommercial distribution and only if you
- received the program in object code or executable form with such
- an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable. However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License. Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
- 5. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Program or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
- 6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
- 7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all. For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded. In such case, this License incorporates
-the limitation as if written in the body of this License.
-
- 9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation. If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
- 10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission. For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this. Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
- NO WARRANTY
-
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
- <one line to give the program's name and a brief idea of what it does.>
- Copyright (C) <year> <name of author>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU 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.
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
- Gnomovision version 69, Copyright (C) year name of author
- Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- <signature of Ty Coon>, 1 April 1989
- Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs. If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.
diff --git a/doc/lgpl-2.1.txt b/doc/lgpl-2.1.txt
new file mode 100644
index 000000000..4362b4915
--- /dev/null
+++ b/doc/lgpl-2.1.txt
@@ -0,0 +1,502 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/doc/lua_api.txt b/doc/lua_api.txt
new file mode 100644
index 000000000..ca6f1585f
--- /dev/null
+++ b/doc/lua_api.txt
@@ -0,0 +1,4838 @@
+Minetest Lua Modding API Reference 0.4.17
+=========================================
+* More information at <http://www.minetest.net/>
+* Developer Wiki: <http://dev.minetest.net/>
+
+Introduction
+------------
+Content and functionality can be added to Minetest 0.4 by using Lua
+scripting in run-time loaded mods.
+
+A mod is a self-contained bunch of scripts, textures and other related
+things that is loaded by and interfaces with Minetest.
+
+Mods are contained and ran solely on the server side. Definitions and media
+files are automatically transferred to the client.
+
+If you see a deficiency in the API, feel free to attempt to add the
+functionality in the engine and API. You can send such improvements as
+source code patches to <celeron55@gmail.com>.
+
+Programming in Lua
+------------------
+If you have any difficulty in understanding this, please read
+[Programming in Lua](http://www.lua.org/pil/).
+
+Startup
+-------
+Mods are loaded during server startup from the mod load paths by running
+the `init.lua` scripts in a shared environment.
+
+Paths
+-----
+* `RUN_IN_PLACE=1` (Windows release, local build)
+ * `$path_user`:
+ * Linux: `<build directory>`
+ * Windows: `<build directory>`
+ * `$path_share`
+ * Linux: `<build directory>`
+ * Windows: `<build directory>`
+* `RUN_IN_PLACE=0`: (Linux release)
+ * `$path_share`
+ * Linux: `/usr/share/minetest`
+ * Windows: `<install directory>/minetest-0.4.x`
+ * `$path_user`:
+ * Linux: `$HOME/.minetest`
+ * Windows: `C:/users/<user>/AppData/minetest` (maybe)
+
+Games
+-----
+Games are looked up from:
+
+* `$path_share/games/gameid/`
+* `$path_user/games/gameid/`
+
+where `gameid` is unique to each game.
+
+The game directory contains the file `game.conf`, which contains these fields:
+
+ name = <Human-readable full name of the game>
+
+e.g.
+
+ name = Minetest
+
+The game directory can contain the file minetest.conf, which will be used
+to set default settings when running the particular game.
+It can also contain a settingtypes.txt in the same format as the one in builtin.
+This settingtypes.txt will be parsed by the menu and the settings will be displayed
+in the "Games" category in the settings tab.
+
+### Menu images
+
+Games can provide custom main menu images. They are put inside a `menu` directory
+inside the game directory.
+
+The images are named `$identifier.png`, where `$identifier` is
+one of `overlay,background,footer,header`.
+If you want to specify multiple images for one identifier, add additional images named
+like `$identifier.$n.png`, with an ascending number $n starting with 1, and a random
+image will be chosen from the provided ones.
+
+
+Mod load path
+-------------
+Generic:
+
+* `$path_share/games/gameid/mods/`
+* `$path_share/mods/`
+* `$path_user/games/gameid/mods/`
+* `$path_user/mods/` (User-installed mods)
+* `$worldpath/worldmods/`
+
+In a run-in-place version (e.g. the distributed windows version):
+
+* `minetest-0.4.x/games/gameid/mods/`
+* `minetest-0.4.x/mods/` (User-installed mods)
+* `minetest-0.4.x/worlds/worldname/worldmods/`
+
+On an installed version on Linux:
+
+* `/usr/share/minetest/games/gameid/mods/`
+* `$HOME/.minetest/mods/` (User-installed mods)
+* `$HOME/.minetest/worlds/worldname/worldmods`
+
+Mod load path for world-specific games
+--------------------------------------
+It is possible to include a game in a world; in this case, no mods or
+games are loaded or checked from anywhere else.
+
+This is useful for e.g. adventure worlds.
+
+This happens if the following directory exists:
+
+ $world/game/
+
+Mods should be then be placed in:
+
+ $world/game/mods/
+
+Modpack support
+----------------
+Mods can be put in a subdirectory, if the parent directory, which otherwise
+should be a mod, contains a file named `modpack.txt`. This file shall be
+empty, except for lines starting with `#`, which are comments.
+
+Mod directory structure
+------------------------
+
+ mods
+ |-- modname
+ | |-- depends.txt
+ | |-- screenshot.png
+ | |-- description.txt
+ | |-- settingtypes.txt
+ | |-- init.lua
+ | |-- models
+ | |-- textures
+ | | |-- modname_stuff.png
+ | | `-- modname_something_else.png
+ | |-- sounds
+ | |-- media
+ | `-- <custom data>
+ `-- another
+
+
+### modname
+The location of this directory can be fetched by using
+`minetest.get_modpath(modname)`.
+
+### `depends.txt`
+List of mods that have to be loaded before loading this mod.
+
+A single line contains a single modname.
+
+Optional dependencies can be defined by appending a question mark
+to a single modname. Their meaning is that if the specified mod
+is missing, that does not prevent this mod from being loaded.
+
+### `screenshot.png`
+A screenshot shown in the mod manager within the main menu. It should
+have an aspect ratio of 3:2 and a minimum size of 300×200 pixels.
+
+### `description.txt`
+A File containing description to be shown within mainmenu.
+
+### `settingtypes.txt`
+A file in the same format as the one in builtin. It will be parsed by the
+settings menu and the settings will be displayed in the "Mods" category.
+
+### `init.lua`
+The main Lua script. Running this script should register everything it
+wants to register. Subsequent execution depends on minetest calling the
+registered callbacks.
+
+`minetest.settings` can be used to read custom or existing settings at load
+time, if necessary. (See `Settings`)
+
+### `models`
+Models for entities or meshnodes.
+
+### `textures`, `sounds`, `media`
+Media files (textures, sounds, whatever) that will be transferred to the
+client and will be available for use by the mod.
+
+Naming convention for registered textual names
+----------------------------------------------
+Registered names should generally be in this format:
+
+ `modname:<whatever>`
+
+`<whatever>` can have these characters:
+
+ a-zA-Z0-9_
+
+This is to prevent conflicting names from corrupting maps and is
+enforced by the mod loader.
+
+### Example
+In the mod `experimental`, there is the ideal item/node/entity name `tnt`.
+So the name should be `experimental:tnt`.
+
+Enforcement can be overridden by prefixing the name with `:`. This can
+be used for overriding the registrations of some other mod.
+
+Example: Any mod can redefine `experimental:tnt` by using the name
+
+ :experimental:tnt
+
+when registering it.
+(also that mod is required to have `experimental` as a dependency)
+
+The `:` prefix can also be used for maintaining backwards compatibility.
+
+Aliases
+-------
+Aliases can be added by using `minetest.register_alias(name, convert_to)` or
+`minetest.register_alias_force(name, convert_to)`.
+
+This will make Minetest to convert things called name to things called
+`convert_to`.
+
+The only difference between `minetest.register_alias` and
+`minetest.register_alias_force` is that if an item called `name` exists,
+`minetest.register_alias` will do nothing while
+`minetest.register_alias_force` will unregister it.
+
+This can be used for maintaining backwards compatibility.
+
+This can be also used for setting quick access names for things, e.g. if
+you have an item called `epiclylongmodname:stuff`, you could do
+
+ minetest.register_alias("stuff", "epiclylongmodname:stuff")
+
+and be able to use `/giveme stuff`.
+
+Mapgen aliases
+--------------
+In a game, a certain number of these must be set to tell core mapgens which
+of the game's nodes are to be used by the core mapgens. For example:
+
+ minetest.register_alias("mapgen_stone", "default:stone")
+
+### Aliases needed for all mapgens except Mapgen v6
+
+Base terrain:
+
+"mapgen_stone"
+"mapgen_water_source"
+"mapgen_river_water_source"
+
+Caves:
+
+"mapgen_lava_source"
+
+Dungeons:
+
+Only needed for registered biomes where 'node_stone' is stone:
+"mapgen_cobble"
+"mapgen_stair_cobble"
+"mapgen_mossycobble"
+Only needed for registered biomes where 'node_stone' is desert stone:
+"mapgen_desert_stone"
+"mapgen_stair_desert_stone"
+Only needed for registered biomes where 'node_stone' is sandstone:
+"mapgen_sandstone"
+"mapgen_sandstonebrick"
+"mapgen_stair_sandstone_block"
+
+### Aliases needed for Mapgen v6
+
+Terrain and biomes:
+
+"mapgen_stone"
+"mapgen_water_source"
+"mapgen_lava_source"
+"mapgen_dirt"
+"mapgen_dirt_with_grass"
+"mapgen_sand"
+"mapgen_gravel"
+"mapgen_desert_stone"
+"mapgen_desert_sand"
+"mapgen_dirt_with_snow"
+"mapgen_snowblock"
+"mapgen_snow"
+"mapgen_ice"
+
+Flora:
+
+"mapgen_tree"
+"mapgen_leaves"
+"mapgen_apple"
+"mapgen_jungletree"
+"mapgen_jungleleaves"
+"mapgen_junglegrass"
+"mapgen_pine_tree"
+"mapgen_pine_needles"
+
+Dungeons:
+
+"mapgen_cobble"
+"mapgen_stair_cobble"
+"mapgen_mossycobble"
+"mapgen_stair_desert_stone"
+
+Textures
+--------
+Mods should generally prefix their textures with `modname_`, e.g. given
+the mod name `foomod`, a texture could be called:
+
+ foomod_foothing.png
+
+Textures are referred to by their complete name, or alternatively by
+stripping out the file extension:
+
+* e.g. `foomod_foothing.png`
+* e.g. `foomod_foothing`
+
+Texture modifiers
+-----------------
+There are various texture modifiers that can be used
+to generate textures on-the-fly.
+
+### Texture overlaying
+Textures can be overlaid by putting a `^` between them.
+
+Example:
+
+ default_dirt.png^default_grass_side.png
+
+`default_grass_side.png` is overlayed over `default_dirt.png`.
+The texture with the lower resolution will be automatically upscaled to
+the higher resolution texture.
+
+### Texture grouping
+Textures can be grouped together by enclosing them in `(` and `)`.
+
+Example: `cobble.png^(thing1.png^thing2.png)`
+
+A texture for `thing1.png^thing2.png` is created and the resulting
+texture is overlaid on top of `cobble.png`.
+
+### Escaping
+Modifiers that accept texture names (e.g. `[combine`) accept escaping to allow
+passing complex texture names as arguments. Escaping is done with backslash and
+is required for `^` and `:`.
+
+Example: `cobble.png^[lowpart:50:color.png\^[mask\:trans.png`
+
+The lower 50 percent of `color.png^[mask:trans.png` are overlaid
+on top of `cobble.png`.
+
+### Advanced texture modifiers
+
+#### `[crack:<n>:<p>`
+* `<n>` = animation frame count
+* `<p>` = current animation frame
+
+Draw a step of the crack animation on the texture.
+
+Example:
+
+ default_cobble.png^[crack:10:1
+
+#### `[combine:<w>x<h>:<x1>,<y1>=<file1>:<x2>,<y2>=<file2>:...`
+* `<w>` = width
+* `<h>` = height
+* `<x>` = x position
+* `<y>` = y position
+* `<file>` = texture to combine
+
+Creates a texture of size `<w>` times `<h>` and blits the listed files to their
+specified coordinates.
+
+Example:
+
+ [combine:16x32:0,0=default_cobble.png:0,16=default_wood.png
+
+#### `[resize:<w>x<h>`
+Resizes the texture to the given dimensions.
+
+Example:
+
+ default_sandstone.png^[resize:16x16
+
+#### `[opacity:<r>`
+Makes the base image transparent according to the given ratio.
+
+`r` must be between 0 and 255.
+0 means totally transparent. 255 means totally opaque.
+
+Example:
+
+ default_sandstone.png^[opacity:127
+
+#### `[invert:<mode>`
+Inverts the given channels of the base image.
+Mode may contain the characters "r", "g", "b", "a".
+Only the channels that are mentioned in the mode string will be inverted.
+
+Example:
+
+ default_apple.png^[invert:rgb
+
+#### `[brighten`
+Brightens the texture.
+
+Example:
+
+ tnt_tnt_side.png^[brighten
+
+#### `[noalpha`
+Makes the texture completely opaque.
+
+Example:
+
+ default_leaves.png^[noalpha
+
+#### `[makealpha:<r>,<g>,<b>`
+Convert one color to transparency.
+
+Example:
+
+ default_cobble.png^[makealpha:128,128,128
+
+#### `[transform<t>`
+* `<t>` = transformation(s) to apply
+
+Rotates and/or flips the image.
+
+`<t>` can be a number (between 0 and 7) or a transform name.
+Rotations are counter-clockwise.
+
+ 0 I identity
+ 1 R90 rotate by 90 degrees
+ 2 R180 rotate by 180 degrees
+ 3 R270 rotate by 270 degrees
+ 4 FX flip X
+ 5 FXR90 flip X then rotate by 90 degrees
+ 6 FY flip Y
+ 7 FYR90 flip Y then rotate by 90 degrees
+
+Example:
+
+ default_stone.png^[transformFXR90
+
+#### `[inventorycube{<top>{<left>{<right>`
+Escaping does not apply here and `^` is replaced by `&` in texture names instead.
+
+Create an inventory cube texture using the side textures.
+
+Example:
+
+ [inventorycube{grass.png{dirt.png&grass_side.png{dirt.png&grass_side.png
+
+Creates an inventorycube with `grass.png`, `dirt.png^grass_side.png` and
+`dirt.png^grass_side.png` textures
+
+#### `[lowpart:<percent>:<file>`
+Blit the lower `<percent>`% part of `<file>` on the texture.
+
+Example:
+
+ base.png^[lowpart:25:overlay.png
+
+#### `[verticalframe:<t>:<n>`
+* `<t>` = animation frame count
+* `<n>` = current animation frame
+
+Crops the texture to a frame of a vertical animation.
+
+Example:
+
+ default_torch_animated.png^[verticalframe:16:8
+
+#### `[mask:<file>`
+Apply a mask to the base image.
+
+The mask is applied using binary AND.
+
+#### `[sheet:<w>x<h>:<x>,<y>`
+Retrieves a tile at position x,y from the base image
+which it assumes to be a tilesheet with dimensions w,h.
+
+
+#### `[colorize:<color>:<ratio>`
+Colorize the textures with the given color.
+`<color>` is specified as a `ColorString`.
+`<ratio>` is an int ranging from 0 to 255 or the word "`alpha`". If
+it is an int, then it specifies how far to interpolate between the
+colors where 0 is only the texture color and 255 is only `<color>`. If
+omitted, the alpha of `<color>` will be used as the ratio. If it is
+the word "`alpha`", then each texture pixel will contain the RGB of
+`<color>` and the alpha of `<color>` multiplied by the alpha of the
+texture pixel.
+
+#### `[multiply:<color>`
+Multiplies texture colors with the given color.
+`<color>` is specified as a `ColorString`.
+Result is more like what you'd expect if you put a color on top of another
+color. Meaning white surfaces get a lot of your new color while black parts don't
+change very much.
+
+Hardware coloring
+-----------------
+The goal of hardware coloring is to simplify the creation of
+colorful nodes. If your textures use the same pattern, and they only
+differ in their color (like colored wool blocks), you can use hardware
+coloring instead of creating and managing many texture files.
+All of these methods use color multiplication (so a white-black texture
+with red coloring will result in red-black color).
+
+### Static coloring
+This method is useful if you wish to create nodes/items with
+the same texture, in different colors, each in a new node/item definition.
+
+#### Global color
+When you register an item or node, set its `color` field (which accepts a
+`ColorSpec`) to the desired color.
+
+An `ItemStack`s static color can be overwritten by the `color` metadata
+field. If you set that field to a `ColorString`, that color will be used.
+
+#### Tile color
+Each tile may have an individual static color, which overwrites every
+other coloring methods. To disable the coloring of a face,
+set its color to white (because multiplying with white does nothing).
+You can set the `color` property of the tiles in the node's definition
+if the tile is in table format.
+
+### Palettes
+For nodes and items which can have many colors, a palette is more
+suitable. A palette is a texture, which can contain up to 256 pixels.
+Each pixel is one possible color for the node/item.
+You can register one node/item, which can have up to 256 colors.
+
+#### Palette indexing
+When using palettes, you always provide a pixel index for the given
+node or `ItemStack`. The palette is read from left to right and from
+top to bottom. If the palette has less than 256 pixels, then it is
+stretched to contain exactly 256 pixels (after arranging the pixels
+to one line). The indexing starts from 0.
+
+Examples:
+* 16x16 palette, index = 0: the top left corner
+* 16x16 palette, index = 4: the fifth pixel in the first row
+* 16x16 palette, index = 16: the pixel below the top left corner
+* 16x16 palette, index = 255: the bottom right corner
+* 2 (width)x4 (height) palette, index=31: the top left corner.
+ The palette has 8 pixels, so each pixel is stretched to 32 pixels,
+ to ensure the total 256 pixels.
+* 2x4 palette, index=32: the top right corner
+* 2x4 palette, index=63: the top right corner
+* 2x4 palette, index=64: the pixel below the top left corner
+
+#### Using palettes with items
+When registering an item, set the item definition's `palette` field to
+a texture. You can also use texture modifiers.
+
+The `ItemStack`'s color depends on the `palette_index` field of the
+stack's metadata. `palette_index` is an integer, which specifies the
+index of the pixel to use.
+
+#### Linking palettes with nodes
+When registering a node, set the item definition's `palette` field to
+a texture. You can also use texture modifiers.
+The node's color depends on its `param2`, so you also must set an
+appropriate `drawtype`:
+* `drawtype = "color"` for nodes which use their full `param2` for
+ palette indexing. These nodes can have 256 different colors.
+ The palette should contain 256 pixels.
+* `drawtype = "colorwallmounted"` for nodes which use the first
+ five bits (most significant) of `param2` for palette indexing.
+ The remaining three bits are describing rotation, as in `wallmounted`
+ draw type. Division by 8 yields the palette index (without stretching the
+ palette). These nodes can have 32 different colors, and the palette
+ should contain 32 pixels.
+ Examples:
+ * `param2 = 17` is 2 * 8 + 1, so the rotation is 1 and the third (= 2 + 1)
+ pixel will be picked from the palette.
+ * `param2 = 35` is 4 * 8 + 3, so the rotation is 3 and the fifth (= 4 + 1)
+ pixel will be picked from the palette.
+* `drawtype = "colorfacedir"` for nodes which use the first
+ three bits of `param2` for palette indexing. The remaining
+ five bits are describing rotation, as in `facedir` draw type.
+ Division by 32 yields the palette index (without stretching the
+ palette). These nodes can have 8 different colors, and the
+ palette should contain 8 pixels.
+ Examples:
+ * `param2 = 17` is 0 * 32 + 17, so the rotation is 17 and the
+ first (= 0 + 1) pixel will be picked from the palette.
+ * `param2 = 35` is 1 * 32 + 3, so the rotation is 3 and the
+ second (= 1 + 1) pixel will be picked from the palette.
+
+To colorize a node on the map, set its `param2` value (according
+to the node's draw type).
+
+### Conversion between nodes in the inventory and the on the map
+Static coloring is the same for both cases, there is no need
+for conversion.
+
+If the `ItemStack`'s metadata contains the `color` field, it will be
+lost on placement, because nodes on the map can only use palettes.
+
+If the `ItemStack`'s metadata contains the `palette_index` field, it is
+automatically transferred between node and item forms by the engine,
+when a player digs or places a colored node.
+You can disable this feature by setting the `drop` field of the node
+to itself (without metadata).
+To transfer the color to a special drop, you need a drop table.
+Example:
+
+ minetest.register_node("mod:stone", {
+ description = "Stone",
+ tiles = {"default_stone.png"},
+ paramtype2 = "color",
+ palette = "palette.png",
+ drop = {
+ items = {
+ -- assume that mod:cobblestone also has the same palette
+ {items = {"mod:cobblestone"}, inherit_color = true },
+ }
+ }
+ })
+
+### Colored items in craft recipes
+Craft recipes only support item strings, but fortunately item strings
+can also contain metadata. Example craft recipe registration:
+
+ local stack = ItemStack("wool:block")
+ dyed:get_meta():set_int("palette_index", 3) -- add index
+ minetest.register_craft({
+ output = dyed:to_string(), -- convert to string
+ type = "shapeless",
+ recipe = {
+ "wool:block",
+ "dye:red",
+ },
+ })
+
+Metadata field filtering in the `recipe` field are not supported yet,
+so the craft output is independent of the color of the ingredients.
+
+Soft texture overlay
+--------------------
+Sometimes hardware coloring is not enough, because it affects the
+whole tile. Soft texture overlays were added to Minetest to allow
+the dynamic coloring of only specific parts of the node's texture.
+For example a grass block may have colored grass, while keeping the
+dirt brown.
+
+These overlays are 'soft', because unlike texture modifiers, the layers
+are not merged in the memory, but they are simply drawn on top of each
+other. This allows different hardware coloring, but also means that
+tiles with overlays are drawn slower. Using too much overlays might
+cause FPS loss.
+
+To define an overlay, simply set the `overlay_tiles` field of the node
+definition. These tiles are defined in the same way as plain tiles:
+they can have a texture name, color etc.
+To skip one face, set that overlay tile to an empty string.
+
+Example (colored grass block):
+
+ minetest.register_node("default:dirt_with_grass", {
+ description = "Dirt with Grass",
+ -- Regular tiles, as usual
+ -- The dirt tile disables palette coloring
+ tiles = {{name = "default_grass.png"},
+ {name = "default_dirt.png", color = "white"}},
+ -- Overlay tiles: define them in the same style
+ -- The top and bottom tile does not have overlay
+ overlay_tiles = {"", "",
+ {name = "default_grass_side.png", tileable_vertical = false}},
+ -- Global color, used in inventory
+ color = "green",
+ -- Palette in the world
+ paramtype2 = "color",
+ palette = "default_foilage.png",
+ })
+
+Sounds
+------
+Only Ogg Vorbis files are supported.
+
+For positional playing of sounds, only single-channel (mono) files are
+supported. Otherwise OpenAL will play them non-positionally.
+
+Mods should generally prefix their sounds with `modname_`, e.g. given
+the mod name "`foomod`", a sound could be called:
+
+ foomod_foosound.ogg
+
+Sounds are referred to by their name with a dot, a single digit and the
+file extension stripped out. When a sound is played, the actual sound file
+is chosen randomly from the matching sounds.
+
+When playing the sound `foomod_foosound`, the sound is chosen randomly
+from the available ones of the following files:
+
+* `foomod_foosound.ogg`
+* `foomod_foosound.0.ogg`
+* `foomod_foosound.1.ogg`
+* (...)
+* `foomod_foosound.9.ogg`
+
+Examples of sound parameter tables:
+
+ -- Play locationless on all clients
+ {
+ gain = 1.0, -- default
+ fade = 0.0, -- default, change to a value > 0 to fade the sound in
+ }
+ -- Play locationless to one player
+ {
+ to_player = name,
+ gain = 1.0, -- default
+ fade = 0.0, -- default, change to a value > 0 to fade the sound in
+ }
+ -- Play locationless to one player, looped
+ {
+ to_player = name,
+ gain = 1.0, -- default
+ loop = true,
+ }
+ -- Play in a location
+ {
+ pos = {x = 1, y = 2, z = 3},
+ gain = 1.0, -- default
+ max_hear_distance = 32, -- default, uses an euclidean metric
+ }
+ -- Play connected to an object, looped
+ {
+ object = <an ObjectRef>,
+ gain = 1.0, -- default
+ max_hear_distance = 32, -- default, uses an euclidean metric
+ loop = true,
+ }
+
+Looped sounds must either be connected to an object or played locationless to
+one player using `to_player = name,`
+
+### `SimpleSoundSpec`
+* e.g. `""`
+* e.g. `"default_place_node"`
+* e.g. `{}`
+* e.g. `{name = "default_place_node"}`
+* e.g. `{name = "default_place_node", gain = 1.0}`
+
+Registered definitions of stuff
+-------------------------------
+Anything added using certain `minetest.register_*` functions get added to
+the global `minetest.registered_*` tables.
+
+* `minetest.register_entity(name, prototype table)`
+ * added to `minetest.registered_entities[name]`
+
+* `minetest.register_node(name, node definition)`
+ * added to `minetest.registered_items[name]`
+ * added to `minetest.registered_nodes[name]`
+
+* `minetest.register_tool(name, item definition)`
+ * added to `minetest.registered_items[name]`
+
+* `minetest.register_craftitem(name, item definition)`
+ * added to `minetest.registered_items[name]`
+
+* `minetest.unregister_item(name)`
+ * Unregisters the item name from engine, and deletes the entry with key
+ * `name` from `minetest.registered_items` and from the associated item
+ * table according to its nature: `minetest.registered_nodes[]` etc
+
+* `minetest.register_biome(biome definition)`
+ * returns an integer uniquely identifying the registered biome
+ * added to `minetest.registered_biome` with the key of `biome.name`
+ * if `biome.name` is nil, the key is the returned ID
+
+* `minetest.register_ore(ore definition)`
+ * returns an integer uniquely identifying the registered ore
+ * added to `minetest.registered_ores` with the key of `ore.name`
+ * if `ore.name` is nil, the key is the returned ID
+
+* `minetest.register_decoration(decoration definition)`
+ * returns an integer uniquely identifying the registered decoration
+ * added to `minetest.registered_decorations` with the key of `decoration.name`
+ * if `decoration.name` is nil, the key is the returned ID
+
+* `minetest.register_schematic(schematic definition)`
+ * returns an integer uniquely identifying the registered schematic
+ * added to `minetest.registered_schematic` with the key of `schematic.name`
+ * if `schematic.name` is nil, the key is the returned ID
+ * if the schematic is loaded from a file, schematic.name is set to the filename
+ * if the function is called when loading the mod, and schematic.name is a relative
+ path, then the current mod path will be prepended to the schematic filename
+
+* `minetest.clear_registered_biomes()`
+ * clears all biomes currently registered
+
+* `minetest.clear_registered_ores()`
+ * clears all ores currently registered
+
+* `minetest.clear_registered_decorations()`
+ * clears all decorations currently registered
+
+* `minetest.clear_registered_schematics()`
+ * clears all schematics currently registered
+
+Note that in some cases you will stumble upon things that are not contained
+in these tables (e.g. when a mod has been removed). Always check for
+existence before trying to access the fields.
+
+Example: If you want to check the drawtype of a node, you could do:
+
+ local function get_nodedef_field(nodename, fieldname)
+ if not minetest.registered_nodes[nodename] then
+ return nil
+ end
+ return minetest.registered_nodes[nodename][fieldname]
+ end
+ local drawtype = get_nodedef_field(nodename, "drawtype")
+
+Example: `minetest.get_item_group(name, group)` has been implemented as:
+
+ function minetest.get_item_group(name, group)
+ if not minetest.registered_items[name] or not
+ minetest.registered_items[name].groups[group] then
+ return 0
+ end
+ return minetest.registered_items[name].groups[group]
+ end
+
+Nodes
+-----
+Nodes are the bulk data of the world: cubes and other things that take the
+space of a cube. Huge amounts of them are handled efficiently, but they
+are quite static.
+
+The definition of a node is stored and can be accessed by name in
+
+ minetest.registered_nodes[node.name]
+
+See "Registered definitions of stuff".
+
+Nodes are passed by value between Lua and the engine.
+They are represented by a table:
+
+ {name="name", param1=num, param2=num}
+
+`param1` and `param2` are 8-bit integers ranging from 0 to 255. The engine uses
+them for certain automated functions. If you don't use these functions, you can
+use them to store arbitrary values.
+
+The functions of `param1` and `param2` are determined by certain fields in the
+node definition:
+
+`param1` is reserved for the engine when `paramtype != "none"`:
+
+ paramtype = "light"
+ ^ The value stores light with and without sun in its upper and lower 4 bits
+ respectively. Allows light to propagate from or through the node with
+ light value falling by 1 per node. This is essential for a light source
+ node to spread its light.
+
+`param2` is reserved for the engine when any of these are used:
+
+ liquidtype == "flowing"
+ ^ The level and some flags of the liquid is stored in param2
+ drawtype == "flowingliquid"
+ ^ The drawn liquid level is read from param2
+ drawtype == "torchlike"
+ drawtype == "signlike"
+ paramtype2 == "wallmounted"
+ ^ The rotation of the node is stored in param2. You can make this value
+ by using minetest.dir_to_wallmounted().
+ paramtype2 == "facedir"
+ ^ The rotation of the node is stored in param2. Furnaces and chests are
+ rotated this way. Can be made by using minetest.dir_to_facedir().
+ Values range 0 - 23
+ facedir / 4 = axis direction:
+ 0 = y+ 1 = z+ 2 = z- 3 = x+ 4 = x- 5 = y-
+ facedir modulo 4 = rotation around that axis
+ paramtype2 == "leveled"
+ ^ Only valid for "nodebox" with type = "leveled".
+ The level of the top face of the nodebox is stored in param2.
+ The other faces are defined by 'fixed = {}' like 'type = "fixed"' nodeboxes.
+ The nodebox height is param2 / 64 nodes.
+ The maximum accepted value of param2 is 127.
+ paramtype2 == "degrotate"
+ ^ The rotation of this node is stored in param2. Plants are rotated this way.
+ Values range 0 - 179. The value stored in param2 is multiplied by two to
+ get the actual rotation of the node.
+ paramtype2 == "meshoptions"
+ ^ Only valid for "plantlike". The value of param2 becomes a bitfield which can
+ be used to change how the client draws plantlike nodes. Bits 0, 1 and 2 form
+ a mesh selector. Currently the following meshes are choosable:
+ 0 = a "x" shaped plant (ordinary plant)
+ 1 = a "+" shaped plant (just rotated 45 degrees)
+ 2 = a "*" shaped plant with 3 faces instead of 2
+ 3 = a "#" shaped plant with 4 faces instead of 2
+ 4 = a "#" shaped plant with 4 faces that lean outwards
+ 5-7 are unused and reserved for future meshes.
+ Bits 3 through 7 are optional flags that can be combined and give these
+ effects:
+ bit 3 (0x08) - Makes the plant slightly vary placement horizontally
+ bit 4 (0x10) - Makes the plant mesh 1.4x larger
+ bit 5 (0x20) - Moves each face randomly a small bit down (1/8 max)
+ bits 6-7 are reserved for future use.
+ paramtype2 == "color"
+ ^ `param2` tells which color is picked from the palette.
+ The palette should have 256 pixels.
+ paramtype2 == "colorfacedir"
+ ^ Same as `facedir`, but with colors.
+ The first three bits of `param2` tells which color
+ is picked from the palette.
+ The palette should have 8 pixels.
+ paramtype2 == "colorwallmounted"
+ ^ Same as `wallmounted`, but with colors.
+ The first five bits of `param2` tells which color
+ is picked from the palette.
+ The palette should have 32 pixels.
+ paramtype2 == "glasslikeliquidlevel"
+ ^ Only valid for "glasslike_framed" or "glasslike_framed_optional" drawtypes.
+ param2 defines 64 levels of internal liquid.
+ Liquid texture is defined using `special_tiles = {"modname_tilename.png"},`
+
+Nodes can also contain extra data. See "Node Metadata".
+
+Node drawtypes
+---------------
+There are a bunch of different looking node types.
+
+Look for examples in `games/minimal` or `games/minetest_game`.
+
+* `normal`
+* `airlike`
+* `liquid`
+* `flowingliquid`
+* `glasslike`
+* `glasslike_framed`
+* `glasslike_framed_optional`
+* `allfaces`
+* `allfaces_optional`
+* `torchlike`
+* `signlike`
+* `plantlike`
+* `firelike`
+* `fencelike`
+* `raillike`
+* `nodebox` -- See below. (**Experimental!**)
+* `mesh` -- use models for nodes
+
+`*_optional` drawtypes need less rendering time if deactivated (always client side).
+
+Node boxes
+-----------
+Node selection boxes are defined using "node boxes"
+
+The `nodebox` node drawtype allows defining visual of nodes consisting of
+arbitrary number of boxes. It allows defining stuff like stairs. Only the
+`fixed` and `leveled` box type is supported for these.
+
+Please note that this is still experimental, and may be incompatibly
+changed in the future.
+
+A nodebox is defined as any of:
+
+ {
+ -- A normal cube; the default in most things
+ type = "regular"
+ }
+ {
+ -- A fixed box (facedir param2 is used, if applicable)
+ type = "fixed",
+ fixed = box OR {box1, box2, ...}
+ }
+ {
+ -- A box like the selection box for torches
+ -- (wallmounted param2 is used, if applicable)
+ type = "wallmounted",
+ wall_top = box,
+ wall_bottom = box,
+ wall_side = box
+ }
+ {
+ -- A node that has optional boxes depending on neighbouring nodes'
+ -- presence and type. See also `connects_to`.
+ type = "connected",
+ fixed = box OR {box1, box2, ...}
+ connect_top = box OR {box1, box2, ...}
+ connect_bottom = box OR {box1, box2, ...}
+ connect_front = box OR {box1, box2, ...}
+ connect_left = box OR {box1, box2, ...}
+ connect_back = box OR {box1, box2, ...}
+ connect_right = box OR {box1, box2, ...}
+ }
+
+A `box` is defined as:
+
+ {x1, y1, z1, x2, y2, z2}
+
+A box of a regular node would look like:
+
+ {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
+
+`type = "leveled"` is same as `type = "fixed"`, but `y2` will be automatically
+set to level from `param2`.
+
+
+Meshes
+------
+If drawtype `mesh` is used, tiles should hold model materials textures.
+Only static meshes are implemented.
+For supported model formats see Irrlicht engine documentation.
+
+
+Noise Parameters
+----------------
+Noise Parameters, or commonly called "`NoiseParams`", define the properties of
+perlin noise.
+
+### `offset`
+Offset that the noise is translated by (i.e. added) after calculation.
+
+### `scale`
+Factor that the noise is scaled by (i.e. multiplied) after calculation.
+
+### `spread`
+Vector containing values by which each coordinate is divided by before calculation.
+Higher spread values result in larger noise features.
+
+A value of `{x=250, y=250, z=250}` is common.
+
+### `seed`
+Random seed for the noise. Add the world seed to a seed offset for world-unique noise.
+In the case of `minetest.get_perlin()`, this value has the world seed automatically added.
+
+### `octaves`
+Number of times the noise gradient is accumulated into the noise.
+
+Increase this number to increase the amount of detail in the resulting noise.
+
+A value of `6` is common.
+
+### `persistence`
+Factor by which the effect of the noise gradient function changes with each successive octave.
+
+Values less than `1` make the details of successive octaves' noise diminish, while values
+greater than `1` make successive octaves stronger.
+
+A value of `0.6` is common.
+
+### `lacunarity`
+Factor by which the noise feature sizes change with each successive octave.
+
+A value of `2.0` is common.
+
+### `flags`
+Leave this field unset for no special handling.
+
+Currently supported are `defaults`, `eased` and `absvalue`.
+
+#### `defaults`
+Specify this if you would like to keep auto-selection of eased/not-eased while specifying
+some other flags.
+
+#### `eased`
+Maps noise gradient values onto a quintic S-curve before performing interpolation.
+This results in smooth, rolling noise. Disable this (`noeased`) for sharp-looking noise.
+If no flags are specified (or defaults is), 2D noise is eased and 3D noise is not eased.
+
+#### `absvalue`
+Accumulates the absolute value of each noise gradient result.
+
+Noise parameters format example for 2D or 3D perlin noise or perlin noise maps:
+
+ np_terrain = {
+ offset = 0,
+ scale = 1,
+ spread = {x=500, y=500, z=500},
+ seed = 571347,
+ octaves = 5,
+ persist = 0.63,
+ lacunarity = 2.0,
+ flags = "defaults, absvalue"
+ }
+ ^ A single noise parameter table can be used to get 2D or 3D noise,
+ when getting 2D noise spread.z is ignored.
+
+
+Ore types
+---------
+These tell in what manner the ore is generated.
+
+All default ores are of the uniformly-distributed scatter type.
+
+### `scatter`
+Randomly chooses a location and generates a cluster of ore.
+
+If `noise_params` is specified, the ore will be placed if the 3D perlin noise at
+that point is greater than the `noise_threshold`, giving the ability to create
+a non-equal distribution of ore.
+
+### `sheet`
+Creates a sheet of ore in a blob shape according to the 2D perlin noise
+described by `noise_params` and `noise_threshold`. This is essentially an
+improved version of the so-called "stratus" ore seen in some unofficial mods.
+
+This sheet consists of vertical columns of uniform randomly distributed height,
+varying between the inclusive range `column_height_min` and `column_height_max`.
+If `column_height_min` is not specified, this parameter defaults to 1.
+If `column_height_max` is not specified, this parameter defaults to `clust_size`
+for reverse compatibility. New code should prefer `column_height_max`.
+
+The `column_midpoint_factor` parameter controls the position of the column at which
+ore eminates from. If 1, columns grow upward. If 0, columns grow downward. If 0.5,
+columns grow equally starting from each direction. `column_midpoint_factor` is a
+decimal number ranging in value from 0 to 1. If this parameter is not specified,
+the default is 0.5.
+
+The ore parameters `clust_scarcity` and `clust_num_ores` are ignored for this ore type.
+
+### `puff`
+Creates a sheet of ore in a cloud-like puff shape.
+
+As with the `sheet` ore type, the size and shape of puffs are described by
+`noise_params` and `noise_threshold` and are placed at random vertical positions
+within the currently generated chunk.
+
+The vertical top and bottom displacement of each puff are determined by the noise
+parameters `np_puff_top` and `np_puff_bottom`, respectively.
+
+
+### `blob`
+Creates a deformed sphere of ore according to 3d perlin noise described by
+`noise_params`. The maximum size of the blob is `clust_size`, and
+`clust_scarcity` has the same meaning as with the `scatter` type.
+
+### `vein`
+Creates veins of ore varying in density by according to the intersection of two
+instances of 3d perlin noise with diffferent seeds, both described by
+`noise_params`. `random_factor` varies the influence random chance has on
+placement of an ore inside the vein, which is `1` by default. Note that
+modifying this parameter may require adjusting `noise_threshold`.
+The parameters `clust_scarcity`, `clust_num_ores`, and `clust_size` are ignored
+by this ore type. This ore type is difficult to control since it is sensitive
+to small changes. The following is a decent set of parameters to work from:
+
+ noise_params = {
+ offset = 0,
+ scale = 3,
+ spread = {x=200, y=200, z=200},
+ seed = 5390,
+ octaves = 4,
+ persist = 0.5,
+ flags = "eased",
+ },
+ noise_threshold = 1.6
+
+**WARNING**: Use this ore type *very* sparingly since it is ~200x more
+computationally expensive than any other ore.
+
+Ore attributes
+--------------
+See section "Flag Specifier Format".
+
+Currently supported flags:
+`absheight`, `puff_cliffs`, `puff_additive_composition`.
+
+### `absheight`
+Also produce this same ore between the height range of `-y_max` and `-y_min`.
+
+Useful for having ore in sky realms without having to duplicate ore entries.
+
+### `puff_cliffs`
+If set, puff ore generation will not taper down large differences in displacement
+when approaching the edge of a puff. This flag has no effect for ore types other
+than `puff`.
+
+### `puff_additive_composition`
+By default, when noise described by `np_puff_top` or `np_puff_bottom` results in a
+negative displacement, the sub-column at that point is not generated. With this
+attribute set, puff ore generation will instead generate the absolute difference in
+noise displacement values. This flag has no effect for ore types other than `puff`.
+
+Decoration types
+----------------
+The varying types of decorations that can be placed.
+
+### `simple`
+Creates a 1 times `H` times 1 column of a specified node (or a random node from
+a list, if a decoration list is specified). Can specify a certain node it must
+spawn next to, such as water or lava, for example. Can also generate a
+decoration of random height between a specified lower and upper bound.
+This type of decoration is intended for placement of grass, flowers, cacti,
+papyri, waterlilies and so on.
+
+### `schematic`
+Copies a box of `MapNodes` from a specified schematic file (or raw description).
+Can specify a probability of a node randomly appearing when placed.
+This decoration type is intended to be used for multi-node sized discrete
+structures, such as trees, cave spikes, rocks, and so on.
+
+
+Schematic specifier
+--------------------
+A schematic specifier identifies a schematic by either a filename to a
+Minetest Schematic file (`.mts`) or through raw data supplied through Lua,
+in the form of a table. This table specifies the following fields:
+
+* The `size` field is a 3D vector containing the dimensions of the provided schematic. (required)
+* The `yslice_prob` field is a table of {ypos, prob} which sets the `ypos`th vertical slice
+ of the schematic to have a `prob / 256 * 100` chance of occuring. (default: 255)
+* The `data` field is a flat table of MapNode tables making up the schematic,
+ in the order of `[z [y [x]]]`. (required)
+ Each MapNode table contains:
+ * `name`: the name of the map node to place (required)
+ * `prob` (alias `param1`): the probability of this node being placed (default: 255)
+ * `param2`: the raw param2 value of the node being placed onto the map (default: 0)
+ * `force_place`: boolean representing if the node should forcibly overwrite any
+ previous contents (default: false)
+
+About probability values:
+
+* A probability value of `0` or `1` means that node will never appear (0% chance).
+* A probability value of `254` or `255` means the node will always appear (100% chance).
+* If the probability value `p` is greater than `1`, then there is a
+ `(p / 256 * 100)` percent chance that node will appear when the schematic is
+ placed on the map.
+
+
+Schematic attributes
+--------------------
+See section "Flag Specifier Format".
+
+Currently supported flags: `place_center_x`, `place_center_y`, `place_center_z`,
+ `force_placement`.
+
+* `place_center_x`: Placement of this decoration is centered along the X axis.
+* `place_center_y`: Placement of this decoration is centered along the Y axis.
+* `place_center_z`: Placement of this decoration is centered along the Z axis.
+* `force_placement`: Schematic nodes other than "ignore" will replace existing nodes.
+
+
+HUD element types
+-----------------
+The position field is used for all element types.
+
+To account for differing resolutions, the position coordinates are the percentage
+of the screen, ranging in value from `0` to `1`.
+
+The name field is not yet used, but should contain a description of what the
+HUD element represents. The direction field is the direction in which something
+is drawn.
+
+`0` draws from left to right, `1` draws from right to left, `2` draws from
+top to bottom, and `3` draws from bottom to top.
+
+The `alignment` field specifies how the item will be aligned. It ranges from `-1` to `1`,
+with `0` being the center, `-1` is moved to the left/up, and `1` is to the right/down.
+Fractional values can be used.
+
+The `offset` field specifies a pixel offset from the position. Contrary to position,
+the offset is not scaled to screen size. This allows for some precisely-positioned
+items in the HUD.
+
+**Note**: `offset` _will_ adapt to screen DPI as well as user defined scaling factor!
+
+Below are the specific uses for fields in each type; fields not listed for that type are ignored.
+
+**Note**: Future revisions to the HUD API may be incompatible; the HUD API is still
+in the experimental stages.
+
+### `image`
+Displays an image on the HUD.
+
+* `scale`: The scale of the image, with 1 being the original texture size.
+ Only the X coordinate scale is used (positive values).
+ Negative values represent that percentage of the screen it
+ should take; e.g. `x=-100` means 100% (width).
+* `text`: The name of the texture that is displayed.
+* `alignment`: The alignment of the image.
+* `offset`: offset in pixels from position.
+
+### `text`
+Displays text on the HUD.
+
+* `scale`: Defines the bounding rectangle of the text.
+ A value such as `{x=100, y=100}` should work.
+* `text`: The text to be displayed in the HUD element.
+* `number`: An integer containing the RGB value of the color used to draw the text.
+ Specify `0xFFFFFF` for white text, `0xFF0000` for red, and so on.
+* `alignment`: The alignment of the text.
+* `offset`: offset in pixels from position.
+
+### `statbar`
+Displays a horizontal bar made up of half-images.
+
+* `text`: The name of the texture that is used.
+* `number`: The number of half-textures that are displayed.
+ If odd, will end with a vertically center-split texture.
+* `direction`
+* `offset`: offset in pixels from position.
+* `size`: If used, will force full-image size to this value (override texture pack image size)
+
+### `inventory`
+* `text`: The name of the inventory list to be displayed.
+* `number`: Number of items in the inventory to be displayed.
+* `item`: Position of item that is selected.
+* `direction`
+* `offset`: offset in pixels from position.
+
+### `waypoint`
+Displays distance to selected world position.
+
+* `name`: The name of the waypoint.
+* `text`: Distance suffix. Can be blank.
+* `number:` An integer containing the RGB value of the color used to draw the text.
+* `world_pos`: World position of the waypoint.
+
+Representations of simple things
+--------------------------------
+
+### Position/vector
+
+ {x=num, y=num, z=num}
+
+For helper functions see "Vector helpers".
+
+### `pointed_thing`
+* `{type="nothing"}`
+* `{type="node", under=pos, above=pos}`
+* `{type="object", ref=ObjectRef}`
+
+Flag Specifier Format
+---------------------
+Flags using the standardized flag specifier format can be specified in either of
+two ways, by string or table.
+
+The string format is a comma-delimited set of flag names; whitespace and
+unrecognized flag fields are ignored. Specifying a flag in the string sets the
+flag, and specifying a flag prefixed by the string `"no"` explicitly
+clears the flag from whatever the default may be.
+
+In addition to the standard string flag format, the schematic flags field can
+also be a table of flag names to boolean values representing whether or not the
+flag is set. Additionally, if a field with the flag name prefixed with `"no"`
+is present, mapped to a boolean of any value, the specified flag is unset.
+
+E.g. A flag field of value
+
+ {place_center_x = true, place_center_y=false, place_center_z=true}
+
+is equivalent to
+
+ {place_center_x = true, noplace_center_y=true, place_center_z=true}
+
+which is equivalent to
+
+ "place_center_x, noplace_center_y, place_center_z"
+
+or even
+
+ "place_center_x, place_center_z"
+
+since, by default, no schematic attributes are set.
+
+Items
+-----
+
+### Item types
+There are three kinds of items: nodes, tools and craftitems.
+
+* Node (`register_node`): A node from the world.
+* Tool (`register_tool`): A tool/weapon that can dig and damage
+ things according to `tool_capabilities`.
+* Craftitem (`register_craftitem`): A miscellaneous item.
+
+### Amount and wear
+All item stacks have an amount between 0 to 65535. It is 1 by
+default. Tool item stacks can not have an amount greater than 1.
+
+Tools use a wear (=damage) value ranging from 0 to 65535. The
+value 0 is the default and used is for unworn tools. The values
+1 to 65535 are used for worn tools, where a higher value stands for
+a higher wear. Non-tools always have a wear value of 0.
+
+### Item formats
+Items and item stacks can exist in three formats: Serializes, table format
+and `ItemStack`.
+
+#### Serialized
+This is called "stackstring" or "itemstring". It is a simple string with
+1-3 components: the full item identifier, an optional amount and an optional
+wear value. Syntax:
+
+ <identifier> [<amount>[ <wear>]]
+
+Examples:
+
+* `'default:apple'`: 1 apple
+* `'default:dirt 5'`: 5 dirt
+* `'default:pick_stone'`: a new stone pickaxe
+* `'default:pick_wood 1 21323'`: a wooden pickaxe, ca. 1/3 worn out
+
+#### Table format
+Examples:
+
+5 dirt nodes:
+
+ {name="default:dirt", count=5, wear=0, metadata=""}
+
+A wooden pick about 1/3 worn out:
+
+ {name="default:pick_wood", count=1, wear=21323, metadata=""}
+
+An apple:
+
+ {name="default:apple", count=1, wear=0, metadata=""}
+
+#### `ItemStack`
+A native C++ format with many helper methods. Useful for converting
+between formats. See the Class reference section for details.
+
+When an item must be passed to a function, it can usually be in any of
+these formats.
+
+
+Groups
+------
+In a number of places, there is a group table. Groups define the
+properties of a thing (item, node, armor of entity, capabilities of
+tool) in such a way that the engine and other mods can can interact with
+the thing without actually knowing what the thing is.
+
+### Usage
+Groups are stored in a table, having the group names with keys and the
+group ratings as values. For example:
+
+ groups = {crumbly=3, soil=1}
+ -- ^ Default dirt
+
+ groups = {crumbly=2, soil=1, level=2, outerspace=1}
+ -- ^ A more special dirt-kind of thing
+
+Groups always have a rating associated with them. If there is no
+useful meaning for a rating for an enabled group, it shall be `1`.
+
+When not defined, the rating of a group defaults to `0`. Thus when you
+read groups, you must interpret `nil` and `0` as the same value, `0`.
+
+You can read the rating of a group for an item or a node by using
+
+ minetest.get_item_group(itemname, groupname)
+
+### Groups of items
+Groups of items can define what kind of an item it is (e.g. wool).
+
+### Groups of nodes
+In addition to the general item things, groups are used to define whether
+a node is destroyable and how long it takes to destroy by a tool.
+
+### Groups of entities
+For entities, groups are, as of now, used only for calculating damage.
+The rating is the percentage of damage caused by tools with this damage group.
+See "Entity damage mechanism".
+
+ object.get_armor_groups() --> a group-rating table (e.g. {fleshy=100})
+ object.set_armor_groups({fleshy=30, cracky=80})
+
+### Groups of tools
+Groups in tools define which groups of nodes and entities they are
+effective towards.
+
+### Groups in crafting recipes
+An example: Make meat soup from any meat, any water and any bowl:
+
+ {
+ output = 'food:meat_soup_raw',
+ recipe = {
+ {'group:meat'},
+ {'group:water'},
+ {'group:bowl'},
+ },
+ -- preserve = {'group:bowl'}, -- Not implemented yet (TODO)
+ }
+
+Another example: Make red wool from white wool and red dye:
+
+ {
+ type = 'shapeless',
+ output = 'wool:red',
+ recipe = {'wool:white', 'group:dye,basecolor_red'},
+ }
+
+### Special groups
+* `immortal`: Disables the group damage system for an entity
+* `punch_operable`: For entities; disables the regular damage mechanism for
+ players punching it by hand or a non-tool item, so that it can do something
+ else than take damage.
+* `level`: Can be used to give an additional sense of progression in the game.
+ * A larger level will cause e.g. a weapon of a lower level make much less
+ damage, and get worn out much faster, or not be able to get drops
+ from destroyed nodes.
+ * `0` is something that is directly accessible at the start of gameplay
+ * There is no upper limit
+* `dig_immediate`: (player can always pick up node without reducing tool wear)
+ * `2`: the node always gets the digging time 0.5 seconds (rail, sign)
+ * `3`: the node always gets the digging time 0 seconds (torch)
+* `disable_jump`: Player (and possibly other things) cannot jump from node
+* `fall_damage_add_percent`: damage speed = `speed * (1 + value/100)`
+* `bouncy`: value is bounce speed in percent
+* `falling_node`: if there is no walkable block under the node it will fall
+* `attached_node`: if the node under it is not a walkable block the node will be
+ dropped as an item. If the node is wallmounted the wallmounted direction is
+ checked.
+* `soil`: saplings will grow on nodes in this group
+* `connect_to_raillike`: makes nodes of raillike drawtype with same group value
+ connect to each other
+
+### Known damage and digging time defining groups
+* `crumbly`: dirt, sand
+* `cracky`: tough but crackable stuff like stone.
+* `snappy`: something that can be cut using fine tools; e.g. leaves, small
+ plants, wire, sheets of metal
+* `choppy`: something that can be cut using force; e.g. trees, wooden planks
+* `fleshy`: Living things like animals and the player. This could imply
+ some blood effects when hitting.
+* `explody`: Especially prone to explosions
+* `oddly_breakable_by_hand`:
+ Can be added to nodes that shouldn't logically be breakable by the
+ hand but are. Somewhat similar to `dig_immediate`, but times are more
+ like `{[1]=3.50,[2]=2.00,[3]=0.70}` and this does not override the
+ speed of a tool if the tool can dig at a faster speed than this
+ suggests for the hand.
+
+### Examples of custom groups
+Item groups are often used for defining, well, _groups of items_.
+
+* `meat`: any meat-kind of a thing (rating might define the size or healing
+ ability or be irrelevant -- it is not defined as of yet)
+* `eatable`: anything that can be eaten. Rating might define HP gain in half
+ hearts.
+* `flammable`: can be set on fire. Rating might define the intensity of the
+ fire, affecting e.g. the speed of the spreading of an open fire.
+* `wool`: any wool (any origin, any color)
+* `metal`: any metal
+* `weapon`: any weapon
+* `heavy`: anything considerably heavy
+
+### Digging time calculation specifics
+Groups such as `crumbly`, `cracky` and `snappy` are used for this
+purpose. Rating is `1`, `2` or `3`. A higher rating for such a group implies
+faster digging time.
+
+The `level` group is used to limit the toughness of nodes a tool can dig
+and to scale the digging times / damage to a greater extent.
+
+**Please do understand this**, otherwise you cannot use the system to it's
+full potential.
+
+Tools define their properties by a list of parameters for groups. They
+cannot dig other groups; thus it is important to use a standard bunch of
+groups to enable interaction with tools.
+
+#### Tools definition
+Tools define:
+
+* Full punch interval
+* Maximum drop level
+* For an arbitrary list of groups:
+ * Uses (until the tool breaks)
+ * Maximum level (usually `0`, `1`, `2` or `3`)
+ * Digging times
+ * Damage groups
+
+#### Full punch interval
+When used as a weapon, the tool will do full damage if this time is spent
+between punches. If e.g. half the time is spent, the tool will do half
+damage.
+
+#### Maximum drop level
+Suggests the maximum level of node, when dug with the tool, that will drop
+it's useful item. (e.g. iron ore to drop a lump of iron).
+
+This is not automated; it is the responsibility of the node definition
+to implement this.
+
+#### Uses
+Determines how many uses the tool has when it is used for digging a node,
+of this group, of the maximum level. For lower leveled nodes, the use count
+is multiplied by `3^leveldiff`.
+
+* `uses=10, leveldiff=0`: actual uses: 10
+* `uses=10, leveldiff=1`: actual uses: 30
+* `uses=10, leveldiff=2`: actual uses: 90
+
+#### Maximum level
+Tells what is the maximum level of a node of this group that the tool will
+be able to dig.
+
+#### Digging times
+List of digging times for different ratings of the group, for nodes of the
+maximum level.
+
+For example, as a Lua table, `times={2=2.00, 3=0.70}`. This would
+result in the tool to be able to dig nodes that have a rating of `2` or `3`
+for this group, and unable to dig the rating `1`, which is the toughest.
+Unless there is a matching group that enables digging otherwise.
+
+If the result digging time is 0, a delay of 0.15 seconds is added between
+digging nodes; If the player releases LMB after digging, this delay is set to 0,
+i.e. players can more quickly click the nodes away instead of holding LMB.
+
+#### Damage groups
+List of damage for groups of entities. See "Entity damage mechanism".
+
+#### Example definition of the capabilities of a tool
+
+ tool_capabilities = {
+ full_punch_interval=1.5,
+ max_drop_level=1,
+ groupcaps={
+ crumbly={maxlevel=2, uses=20, times={[1]=1.60, [2]=1.20, [3]=0.80}}
+ }
+ damage_groups = {fleshy=2},
+ }
+
+This makes the tool be able to dig nodes that fulfil both of these:
+
+* Have the `crumbly` group
+* Have a `level` group less or equal to `2`
+
+Table of resulting digging times:
+
+ crumbly 0 1 2 3 4 <- level
+ -> 0 - - - - -
+ 1 0.80 1.60 1.60 - -
+ 2 0.60 1.20 1.20 - -
+ 3 0.40 0.80 0.80 - -
+
+ level diff: 2 1 0 -1 -2
+
+Table of resulting tool uses:
+
+ -> 0 - - - - -
+ 1 180 60 20 - -
+ 2 180 60 20 - -
+ 3 180 60 20 - -
+
+**Notes**:
+
+* At `crumbly==0`, the node is not diggable.
+* At `crumbly==3`, the level difference digging time divider kicks in and makes
+ easy nodes to be quickly breakable.
+* At `level > 2`, the node is not diggable, because it's `level > maxlevel`
+
+Entity damage mechanism
+-----------------------
+Damage calculation:
+
+ damage = 0
+ foreach group in cap.damage_groups:
+ damage += cap.damage_groups[group] * limit(actual_interval /
+ cap.full_punch_interval, 0.0, 1.0)
+ * (object.armor_groups[group] / 100.0)
+ -- Where object.armor_groups[group] is 0 for inexistent values
+ return damage
+
+Client predicts damage based on damage groups. Because of this, it is able to
+give an immediate response when an entity is damaged or dies; the response is
+pre-defined somehow (e.g. by defining a sprite animation) (not implemented;
+TODO).
+Currently a smoke puff will appear when an entity dies.
+
+The group `immortal` completely disables normal damage.
+
+Entities can define a special armor group, which is `punch_operable`. This
+group disables the regular damage mechanism for players punching it by hand or
+a non-tool item, so that it can do something else than take damage.
+
+On the Lua side, every punch calls:
+
+ entity:on_punch(puncher, time_from_last_punch, tool_capabilities, direction, damage)
+
+This should never be called directly, because damage is usually not handled by
+the entity itself.
+
+* `puncher` is the object performing the punch. Can be `nil`. Should never be
+ accessed unless absolutely required, to encourage interoperability.
+* `time_from_last_punch` is time from last punch (by `puncher`) or `nil`.
+* `tool_capabilities` can be `nil`.
+* `direction` is a unit vector, pointing from the source of the punch to
+ the punched object.
+* `damage` damage that will be done to entity
+Return value of this function will determin if damage is done by this function
+(retval true) or shall be done by engine (retval false)
+
+To punch an entity/object in Lua, call:
+
+ object:punch(puncher, time_from_last_punch, tool_capabilities, direction)
+
+* Return value is tool wear.
+* Parameters are equal to the above callback.
+* If `direction` equals `nil` and `puncher` does not equal `nil`,
+ `direction` will be automatically filled in based on the location of `puncher`.
+
+Node Metadata
+-------------
+The instance of a node in the world normally only contains the three values
+mentioned in "Nodes". However, it is possible to insert extra data into a
+node. It is called "node metadata"; See `NodeMetaRef`.
+
+Node metadata contains two things:
+
+* A key-value store
+* An inventory
+
+Some of the values in the key-value store are handled specially:
+
+* `formspec`: Defines a right-click inventory menu. See "Formspec".
+* `infotext`: Text shown on the screen when the node is pointed at
+
+Example stuff:
+
+ local meta = minetest.get_meta(pos)
+ meta:set_string("formspec",
+ "size[8,9]"..
+ "list[context;main;0,0;8,4;]"..
+ "list[current_player;main;0,5;8,4;]")
+ meta:set_string("infotext", "Chest");
+ local inv = meta:get_inventory()
+ inv:set_size("main", 8*4)
+ print(dump(meta:to_table()))
+ meta:from_table({
+ inventory = {
+ main = {[1] = "default:dirt", [2] = "", [3] = "", [4] = "",
+ [5] = "", [6] = "", [7] = "", [8] = "", [9] = "",
+ [10] = "", [11] = "", [12] = "", [13] = "",
+ [14] = "default:cobble", [15] = "", [16] = "", [17] = "",
+ [18] = "", [19] = "", [20] = "default:cobble", [21] = "",
+ [22] = "", [23] = "", [24] = "", [25] = "", [26] = "",
+ [27] = "", [28] = "", [29] = "", [30] = "", [31] = "",
+ [32] = ""}
+ },
+ fields = {
+ formspec = "size[8,9]list[context;main;0,0;8,4;]list[current_player;main;0,5;8,4;]",
+ infotext = "Chest"
+ }
+ })
+
+Item Metadata
+-------------
+Item stacks can store metadata too. See `ItemStackMetaRef`.
+
+Item metadata only contains a key-value store.
+
+Some of the values in the key-value store are handled specially:
+
+* `description`: Set the item stack's description. Defaults to `idef.description`
+* `color`: A `ColorString`, which sets the stack's color.
+* `palette_index`: If the item has a palette, this is used to get the
+ current color from the palette.
+
+Example stuff:
+
+ local meta = stack:get_meta()
+ meta:set_string("key", "value")
+ print(dump(meta:to_table()))
+
+Formspec
+--------
+Formspec defines a menu. Currently not much else than inventories are
+supported. It is a string, with a somewhat strange format.
+
+Spaces and newlines can be inserted between the blocks, as is used in the
+examples.
+
+### Examples
+
+#### Chest
+
+ size[8,9]
+ list[context;main;0,0;8,4;]
+ list[current_player;main;0,5;8,4;]
+
+#### Furnace
+
+ size[8,9]
+ list[context;fuel;2,3;1,1;]
+ list[context;src;2,1;1,1;]
+ list[context;dst;5,1;2,2;]
+ list[current_player;main;0,5;8,4;]
+
+#### Minecraft-like player inventory
+
+ size[8,7.5]
+ image[1,0.6;1,2;player.png]
+ list[current_player;main;0,3.5;8,4;]
+ list[current_player;craft;3,0;3,3;]
+ list[current_player;craftpreview;7,1;1,1;]
+
+### Elements
+
+#### `size[<W>,<H>,<fixed_size>]`
+* Define the size of the menu in inventory slots
+* `fixed_size`: `true`/`false` (optional)
+* deprecated: `invsize[<W>,<H>;]`
+
+#### `position[<X>,<Y>]`
+* Define the position of the formspec
+* A value between 0.0 and 1.0 represents a position inside the screen
+* The default value is the center of the screen (0.5, 0.5)
+
+#### `anchor[<X>,<Y>]`
+* Define the anchor of the formspec
+* A value between 0.0 and 1.0 represents an anchor inside the formspec
+* The default value is the center of the formspec (0.5, 0.5)
+
+#### `container[<X>,<Y>]`
+* Start of a container block, moves all physical elements in the container by (X, Y)
+* Must have matching `container_end`
+* Containers can be nested, in which case the offsets are added
+ (child containers are relative to parent containers)
+
+#### `container_end[]`
+* End of a container, following elements are no longer relative to this container
+
+#### `list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;]`
+* Show an inventory list
+
+#### `list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;<starting item index>]`
+* Show an inventory list
+
+#### `listring[<inventory location>;<list name>]`
+* Allows to create a ring of inventory lists
+* Shift-clicking on items in one element of the ring
+ will send them to the next inventory list inside the ring
+* The first occurrence of an element inside the ring will
+ determine the inventory where items will be sent to
+
+#### `listring[]`
+* Shorthand for doing `listring[<inventory location>;<list name>]`
+ for the last two inventory lists added by list[...]
+
+#### `listcolors[<slot_bg_normal>;<slot_bg_hover>]`
+* Sets background color of slots as `ColorString`
+* Sets background color of slots on mouse hovering
+
+#### `listcolors[<slot_bg_normal>;<slot_bg_hover>;<slot_border>]`
+* Sets background color of slots as `ColorString`
+* Sets background color of slots on mouse hovering
+* Sets color of slots border
+
+#### `listcolors[<slot_bg_normal>;<slot_bg_hover>;<slot_border>;<tooltip_bgcolor>;<tooltip_fontcolor>]`
+* Sets background color of slots as `ColorString`
+* Sets background color of slots on mouse hovering
+* Sets color of slots border
+* Sets default background color of tooltips
+* Sets default font color of tooltips
+
+#### `tooltip[<gui_element_name>;<tooltip_text>;<bgcolor>;<fontcolor>]`
+* Adds tooltip for an element
+* `<bgcolor>` tooltip background color as `ColorString` (optional)
+* `<fontcolor>` tooltip font color as `ColorString` (optional)
+
+#### `image[<X>,<Y>;<W>,<H>;<texture name>]`
+* Show an image
+* Position and size units are inventory slots
+
+#### `item_image[<X>,<Y>;<W>,<H>;<item name>]`
+* Show an inventory image of registered item/node
+* Position and size units are inventory slots
+
+#### `bgcolor[<color>;<fullscreen>]`
+* Sets background color of formspec as `ColorString`
+* If `true`, the background color is drawn fullscreen (does not effect the size of the formspec)
+
+#### `background[<X>,<Y>;<W>,<H>;<texture name>]`
+* Use a background. Inventory rectangles are not drawn then.
+* Position and size units are inventory slots
+* Example for formspec 8x4 in 16x resolution: image shall be sized
+ 8 times 16px times 4 times 16px.
+
+#### `background[<X>,<Y>;<W>,<H>;<texture name>;<auto_clip>]`
+* Use a background. Inventory rectangles are not drawn then.
+* Position and size units are inventory slots
+* Example for formspec 8x4 in 16x resolution:
+ image shall be sized 8 times 16px times 4 times 16px
+* If `true` the background is clipped to formspec size
+ (`x` and `y` are used as offset values, `w` and `h` are ignored)
+
+#### `pwdfield[<X>,<Y>;<W>,<H>;<name>;<label>]`
+* Textual password style field; will be sent to server when a button is clicked
+* When enter is pressed in field, fields.key_enter_field will be sent with the name
+ of this field.
+* `x` and `y` position the field relative to the top left of the menu
+* `w` and `h` are the size of the field
+* Fields are a set height, but will be vertically centred on `h`
+* Position and size units are inventory slots
+* `name` is the name of the field as returned in fields to `on_receive_fields`
+* `label`, if not blank, will be text printed on the top left above the field
+* See field_close_on_enter to stop enter closing the formspec
+
+#### `field[<X>,<Y>;<W>,<H>;<name>;<label>;<default>]`
+* Textual field; will be sent to server when a button is clicked
+* When enter is pressed in field, `fields.key_enter_field` will be sent with the name
+ of this field.
+* `x` and `y` position the field relative to the top left of the menu
+* `w` and `h` are the size of the field
+* Fields are a set height, but will be vertically centred on `h`
+* Position and size units are inventory slots
+* `name` is the name of the field as returned in fields to `on_receive_fields`
+* `label`, if not blank, will be text printed on the top left above the field
+* `default` is the default value of the field
+ * `default` may contain variable references such as `${text}'` which
+ will fill the value from the metadata value `text`
+ * **Note**: no extra text or more than a single variable is supported ATM.
+* See `field_close_on_enter` to stop enter closing the formspec
+
+#### `field[<name>;<label>;<default>]`
+* As above, but without position/size units
+* When enter is pressed in field, `fields.key_enter_field` will be sent with the name
+ of this field.
+* Special field for creating simple forms, such as sign text input
+* Must be used without a `size[]` element
+* A "Proceed" button will be added automatically
+* See `field_close_on_enter` to stop enter closing the formspec
+
+#### `field_close_on_enter[<name>;<close_on_enter>]`
+* <name> is the name of the field
+* if <close_on_enter> is false, pressing enter in the field will submit the form but not close it
+* defaults to true when not specified (ie: no tag for a field)
+
+#### `textarea[<X>,<Y>;<W>,<H>;<name>;<label>;<default>]`
+* Same as fields above, but with multi-line input
+
+#### `label[<X>,<Y>;<label>]`
+* `x` and `y` work as per field
+* `label` is the text on the label
+* Position and size units are inventory slots
+
+#### `vertlabel[<X>,<Y>;<label>]`
+* Textual label drawn vertically
+* `x` and `y` work as per field
+* `label` is the text on the label
+* Position and size units are inventory slots
+
+#### `button[<X>,<Y>;<W>,<H>;<name>;<label>]`
+* Clickable button. When clicked, fields will be sent.
+* `x`, `y` and `name` work as per field
+* `w` and `h` are the size of the button
+* `label` is the text on the button
+* Position and size units are inventory slots
+
+#### `image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]`
+* `x`, `y`, `w`, `h`, and `name` work as per button
+* `texture name` is the filename of an image
+* Position and size units are inventory slots
+
+#### `image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>;<noclip>;<drawborder>;<pressed texture name>]`
+* `x`, `y`, `w`, `h`, and `name` work as per button
+* `texture name` is the filename of an image
+* Position and size units are inventory slots
+* `noclip=true` means the image button doesn't need to be within specified formsize
+* `drawborder`: draw button border or not
+* `pressed texture name` is the filename of an image on pressed state
+
+#### `item_image_button[<X>,<Y>;<W>,<H>;<item name>;<name>;<label>]`
+* `x`, `y`, `w`, `h`, `name` and `label` work as per button
+* `item name` is the registered name of an item/node,
+ tooltip will be made out of its description
+ to override it use tooltip element
+* Position and size units are inventory slots
+
+#### `button_exit[<X>,<Y>;<W>,<H>;<name>;<label>]`
+* When clicked, fields will be sent and the form will quit.
+
+#### `image_button_exit[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]`
+* When clicked, fields will be sent and the form will quit.
+
+#### `textlist[<X>,<Y>;<W>,<H>;<name>;<listelem 1>,<listelem 2>,...,<listelem n>]`
+* Scrollable item list showing arbitrary text elements
+* `x` and `y` position the itemlist relative to the top left of the menu
+* `w` and `h` are the size of the itemlist
+* `name` fieldname sent to server on doubleclick value is current selected element
+* `listelements` can be prepended by #color in hexadecimal format RRGGBB (only),
+ * if you want a listelement to start with "#" write "##".
+
+#### `textlist[<X>,<Y>;<W>,<H>;<name>;<listelem 1>,<listelem 2>,...,<listelem n>;<selected idx>;<transparent>]`
+* Scrollable itemlist showing arbitrary text elements
+* `x` and `y` position the item list relative to the top left of the menu
+* `w` and `h` are the size of the item list
+* `name` fieldname sent to server on doubleclick value is current selected element
+* `listelements` can be prepended by #RRGGBB (only) in hexadecimal format
+ * if you want a listelement to start with "#" write "##"
+* Index to be selected within textlist
+* `true`/`false`: draw transparent background
+* See also `minetest.explode_textlist_event` (main menu: `engine.explode_textlist_event`)
+
+#### `tabheader[<X>,<Y>;<name>;<caption 1>,<caption 2>,...,<caption n>;<current_tab>;<transparent>;<draw_border>]`
+* Show a tab**header** at specific position (ignores formsize)
+* `x` and `y` position the itemlist relative to the top left of the menu
+* `name` fieldname data is transferred to Lua
+* `caption 1`...: name shown on top of tab
+* `current_tab`: index of selected tab 1...
+* `transparent` (optional): show transparent
+* `draw_border` (optional): draw border
+
+#### `box[<X>,<Y>;<W>,<H>;<color>]`
+* Simple colored semitransparent box
+* `x` and `y` position the box relative to the top left of the menu
+* `w` and `h` are the size of box
+* `color` is color specified as a `ColorString`
+
+#### `dropdown[<X>,<Y>;<W>;<name>;<item 1>,<item 2>, ...,<item n>;<selected idx>]`
+* Show a dropdown field
+* **Important note**: There are two different operation modes:
+ 1. handle directly on change (only changed dropdown is submitted)
+ 2. read the value on pressing a button (all dropdown values are available)
+* `x` and `y` position of dropdown
+* Width of dropdown
+* Fieldname data is transferred to Lua
+* Items to be shown in dropdown
+* Index of currently selected dropdown item
+
+#### `checkbox[<X>,<Y>;<name>;<label>;<selected>]`
+* Show a checkbox
+* `x` and `y`: position of checkbox
+* `name` fieldname data is transferred to Lua
+* `label` to be shown left of checkbox
+* `selected` (optional): `true`/`false`
+
+#### `scrollbar[<X>,<Y>;<W>,<H>;<orientation>;<name>;<value>]`
+* Show a scrollbar
+* There are two ways to use it:
+ 1. handle the changed event (only changed scrollbar is available)
+ 2. read the value on pressing a button (all scrollbars are available)
+* `x` and `y`: position of trackbar
+* `w` and `h`: width and height
+* `orientation`: `vertical`/`horizontal`
+* Fieldname data is transferred to Lua
+* Value this trackbar is set to (`0`-`1000`)
+* See also `minetest.explode_scrollbar_event` (main menu: `engine.explode_scrollbar_event`)
+
+#### `table[<X>,<Y>;<W>,<H>;<name>;<cell 1>,<cell 2>,...,<cell n>;<selected idx>]`
+* Show scrollable table using options defined by the previous `tableoptions[]`
+* Displays cells as defined by the previous `tablecolumns[]`
+* `x` and `y`: position the itemlist relative to the top left of the menu
+* `w` and `h` are the size of the itemlist
+* `name`: fieldname sent to server on row select or doubleclick
+* `cell 1`...`cell n`: cell contents given in row-major order
+* `selected idx`: index of row to be selected within table (first row = `1`)
+* See also `minetest.explode_table_event` (main menu: `engine.explode_table_event`)
+
+#### `tableoptions[<opt 1>;<opt 2>;...]`
+* Sets options for `table[]`
+* `color=#RRGGBB`
+ * default text color (`ColorString`), defaults to `#FFFFFF`
+* `background=#RRGGBB`
+ * table background color (`ColorString`), defaults to `#000000`
+* `border=<true/false>`
+ * should the table be drawn with a border? (default: `true`)
+* `highlight=#RRGGBB`
+ * highlight background color (`ColorString`), defaults to `#466432`
+* `highlight_text=#RRGGBB`
+ * highlight text color (`ColorString`), defaults to `#FFFFFF`
+* `opendepth=<value>`
+ * all subtrees up to `depth < value` are open (default value = `0`)
+ * only useful when there is a column of type "tree"
+
+#### `tablecolumns[<type 1>,<opt 1a>,<opt 1b>,...;<type 2>,<opt 2a>,<opt 2b>;...]`
+* Sets columns for `table[]`
+* Types: `text`, `image`, `color`, `indent`, `tree`
+ * `text`: show cell contents as text
+ * `image`: cell contents are an image index, use column options to define images
+ * `color`: cell contents are a ColorString and define color of following cell
+ * `indent`: cell contents are a number and define indentation of following cell
+ * `tree`: same as indent, but user can open and close subtrees (treeview-like)
+* Column options:
+ * `align=<value>`
+ * for `text` and `image`: content alignment within cells.
+ Available values: `left` (default), `center`, `right`, `inline`
+ * `width=<value>`
+ * for `text` and `image`: minimum width in em (default: `0`)
+ * for `indent` and `tree`: indent width in em (default: `1.5`)
+ * `padding=<value>`: padding left of the column, in em (default `0.5`).
+ Exception: defaults to 0 for indent columns
+ * `tooltip=<value>`: tooltip text (default: empty)
+ * `image` column options:
+ * `0=<value>` sets image for image index 0
+ * `1=<value>` sets image for image index 1
+ * `2=<value>` sets image for image index 2
+ * and so on; defined indices need not be contiguous empty or
+ non-numeric cells are treated as `0`.
+ * `color` column options:
+ * `span=<value>`: number of following columns to affect (default: infinite)
+
+**Note**: do _not_ use a element name starting with `key_`; those names are reserved to
+pass key press events to formspec!
+
+Inventory locations
+-------------------
+* `"context"`: Selected node metadata (deprecated: `"current_name"`)
+* `"current_player"`: Player to whom the menu is shown
+* `"player:<name>"`: Any player
+* `"nodemeta:<X>,<Y>,<Z>"`: Any node metadata
+* `"detached:<name>"`: A detached inventory
+
+Player Inventory lists
+----------------------
+* `main`: list containing the default inventory
+* `craft`: list containing the craft input
+* `craftpreview`: list containing the craft output
+* `hand`: list containing an override for the empty hand
+
+`ColorString`
+-------------
+`#RGB` defines a color in hexadecimal format.
+
+`#RGBA` defines a color in hexadecimal format and alpha channel.
+
+`#RRGGBB` defines a color in hexadecimal format.
+
+`#RRGGBBAA` defines a color in hexadecimal format and alpha channel.
+
+Named colors are also supported and are equivalent to
+[CSS Color Module Level 4](http://dev.w3.org/csswg/css-color/#named-colors).
+To specify the value of the alpha channel, append `#AA` to the end of the color name
+(e.g. `colorname#08`). For named colors the hexadecimal string representing the alpha
+value must (always) be two hexadecimal digits.
+
+`ColorSpec`
+-----------
+A ColorSpec specifies a 32-bit color. It can be written in either:
+table form, each element ranging from 0..255 (a, if absent, defaults to 255):
+ `colorspec = {a=255, r=0, g=255, b=0}`
+numerical form, the raw integer value of an ARGB8 quad:
+ `colorspec = 0xFF00FF00`
+or string form, a ColorString (defined above):
+ `colorspec = "green"`
+
+Escape sequences
+----------------
+Most text can contain escape sequences, that can for example color the text.
+There are a few exceptions: tab headers, dropdowns and vertical labels can't.
+The following functions provide escape sequences:
+
+* `minetest.get_color_escape_sequence(color)`:
+ * `color` is a ColorString
+ * The escape sequence sets the text color to `color`
+* `minetest.colorize(color, message)`:
+ * Equivalent to:
+ `minetest.get_color_escape_sequence(color) ..
+ message ..
+ minetest.get_color_escape_sequence("#ffffff")`
+* `minetest.get_background_escape_sequence(color)`
+ * `color` is a ColorString
+ * The escape sequence sets the background of the whole text element to
+ `color`. Only defined for item descriptions and tooltips.
+* `minetest.strip_foreground_colors(str)`
+ * Removes foreground colors added by `get_color_escape_sequence`.
+* `minetest.strip_background_colors(str)`
+ * Removes background colors added by `get_background_escape_sequence`.
+* `minetest.strip_colors(str)`
+ * Removes all color escape sequences.
+
+Spatial Vectors
+---------------
+* `vector.new(a[, b, c])`: returns a vector:
+ * A copy of `a` if `a` is a vector.
+ * `{x = a, y = b, z = c}`, if all `a, b, c` are defined
+* `vector.direction(p1, p2)`: returns a vector
+* `vector.distance(p1, p2)`: returns a number
+* `vector.length(v)`: returns a number
+* `vector.normalize(v)`: returns a vector
+* `vector.floor(v)`: returns a vector, each dimension rounded down
+* `vector.round(v)`: returns a vector, each dimension rounded to nearest int
+* `vector.apply(v, func)`: returns a vector
+* `vector.equals(v1, v2)`: returns a boolean
+* `vector.sort(v1, v2)`: returns minp, maxp vectors of the cuboid defined by v1 and v2
+
+For the following functions `x` can be either a vector or a number:
+
+* `vector.add(v, x)`: returns a vector
+* `vector.subtract(v, x)`: returns a vector
+* `vector.multiply(v, x)`: returns a scaled vector or Schur product
+* `vector.divide(v, x)`: returns a scaled vector or Schur quotient
+
+Helper functions
+----------------
+* `dump2(obj, name="_", dumped={})`
+ * Return object serialized as a string, handles reference loops
+* `dump(obj, dumped={})`
+ * Return object serialized as a string
+* `math.hypot(x, y)`
+ * Get the hypotenuse of a triangle with legs x and y.
+ Useful for distance calculation.
+* `math.sign(x, tolerance)`
+ * Get the sign of a number.
+ Optional: Also returns `0` when the absolute value is within the tolerance (default: `0`)
+* `string.split(str, separator=",", include_empty=false, max_splits=-1, sep_is_pattern=false)`
+ * If `max_splits` is negative, do not limit splits.
+ * `sep_is_pattern` specifies if separator is a plain string or a pattern (regex).
+ * e.g. `string:split("a,b", ",") == {"a","b"}`
+* `string:trim()`
+ * e.g. `string.trim("\n \t\tfoo bar\t ") == "foo bar"`
+* `minetest.wrap_text(str, limit, [as_table])`: returns a string or table
+ * Adds newlines to the string to keep it within the specified character limit
+ Note that returned lines may be longer than the limit since it only splits at word borders.
+ * limit: Maximal amount of characters in one line
+ * as_table: optional, if true return table of lines instead of string
+* `minetest.pos_to_string({x=X,y=Y,z=Z}, decimal_places))`: returns string `"(X,Y,Z)"`
+ * Convert position to a printable string
+ Optional: 'decimal_places' will round the x, y and z of the pos to the given decimal place.
+* `minetest.string_to_pos(string)`: returns a position
+ * Same but in reverse. Returns `nil` if the string can't be parsed to a position.
+* `minetest.string_to_area("(X1, Y1, Z1) (X2, Y2, Z2)")`: returns two positions
+ * Converts a string representing an area box into two positions
+* `minetest.formspec_escape(string)`: returns a string
+ * escapes the characters "[", "]", "\", "," and ";", which can not be used in formspecs
+* `minetest.is_yes(arg)`
+ * returns true if passed 'y', 'yes', 'true' or a number that isn't zero.
+* `minetest.get_us_time()`
+ * returns time with microsecond precision. May not return wall time.
+* `table.copy(table)`: returns a table
+ * returns a deep copy of `table`
+* `minetest.pointed_thing_to_face_pos(placer, pointed_thing)`: returns a position
+ * returns the exact position on the surface of a pointed node
+
+`minetest` namespace reference
+------------------------------
+
+### Utilities
+
+* `minetest.get_current_modname()`: returns the currently loading mod's name, when we are loading a mod
+* `minetest.get_modpath(modname)`: returns e.g. `"/home/user/.minetest/usermods/modname"`
+ * Useful for loading additional `.lua` modules or static data from mod
+* `minetest.get_modnames()`: returns a list of installed mods
+ * Return a list of installed mods, sorted alphabetically
+* `minetest.get_worldpath()`: returns e.g. `"/home/user/.minetest/world"`
+ * Useful for storing custom data
+* `minetest.is_singleplayer()`
+* `minetest.features`: Table containing API feature flags
+
+ {
+ glasslike_framed = true,
+ nodebox_as_selectionbox = true,
+ chat_send_player_param3 = true,
+ get_all_craft_recipes_works = true,
+ use_texture_alpha = true,
+ -- ^ The transparency channel of textures can be used optionally
+ no_legacy_abms = true,
+ -- ^ Tree and grass ABMs are no longer done from C++
+ texture_names_parens = true,
+ -- ^ Texture grouping is possible using parentheses
+ area_store_custom_ids = true,
+ -- ^ Unique Area ID for AreaStore:insert_area
+ add_entity_with_staticdata = true,
+ -- ^ add_entity supports passing initial staticdata to on_activate
+ no_chat_message_prediction = true,
+ -- ^ Chat messages are no longer predicted
+ }
+* `minetest.has_feature(arg)`: returns `boolean, missing_features`
+ * `arg`: string or table in format `{foo=true, bar=true}`
+ * `missing_features`: `{foo=true, bar=true}`
+* `minetest.get_player_information(player_name)`:
+ * Returns a table containing information about a player. Example return value:
+
+ {
+ address = "127.0.0.1", -- IP address of client
+ ip_version = 4, -- IPv4 / IPv6
+ min_rtt = 0.01, -- minimum round trip time
+ max_rtt = 0.2, -- maximum round trip time
+ avg_rtt = 0.02, -- average round trip time
+ min_jitter = 0.01, -- minimum packet time jitter
+ max_jitter = 0.5, -- maximum packet time jitter
+ avg_jitter = 0.03, -- average packet time jitter
+ connection_uptime = 200, -- seconds since client connected
+ protocol_version = 32, -- protocol version used by client
+ -- following information is available on debug build only!!!
+ -- DO NOT USE IN MODS
+ --ser_vers = 26, -- serialization version used by client
+ --major = 0, -- major version number
+ --minor = 4, -- minor version number
+ --patch = 10, -- patch version number
+ --vers_string = "0.4.9-git", -- full version string
+ --state = "Active" -- current client state
+ }
+* `minetest.mkdir(path)`: returns success.
+ * Creates a directory specified by `path`, creating parent directories
+ if they don't exist.
+* `minetest.get_dir_list(path, [is_dir])`: returns list of entry names
+ * is_dir is one of:
+ * nil: return all entries,
+ * true: return only subdirectory names, or
+ * false: return only file names.
+* `minetest.safe_file_write(path, content)`: returns boolean indicating success
+ * Replaces contents of file at path with new contents in a safe (atomic) way.
+ Use this instead of below code when writing e.g. database files:
+ `local f = io.open(path, "wb"); f:write(content); f:close()`
+* `minetest.get_version()`: returns a table containing components of the
+ engine version. Components:
+ * `project`: Name of the project, eg, "Minetest"
+ * `string`: Simple version, eg, "1.2.3-dev"
+ * `hash`: Full git version (only set if available), eg, "1.2.3-dev-01234567-dirty"
+ Use this for informational purposes only. The information in the returned
+ table does not represent the capabilities of the engine, nor is it
+ reliable or verifyable. Compatible forks will have a different name and
+ version entirely. To check for the presence of engine features, test
+ whether the functions exported by the wanted features exist. For example:
+ `if minetest.nodeupdate then ... end`.
+* `minetest.sha1(data, [raw])`: returns the sha1 hash of data
+ * `data`: string of data to hash
+ * `raw`: return raw bytes instead of hex digits, default: false
+
+### Logging
+* `minetest.debug(...)`
+ * Equivalent to `minetest.log(table.concat({...}, "\t"))`
+* `minetest.log([level,] text)`
+ * `level` is one of `"none"`, `"error"`, `"warning"`, `"action"`,
+ `"info"`, or `"verbose"`. Default is `"none"`.
+
+### Registration functions
+Call these functions only at load time!
+
+* `minetest.register_entity(name, prototype table)`
+* `minetest.register_abm(abm definition)`
+* `minetest.register_lbm(lbm definition)`
+* `minetest.register_node(name, node definition)`
+* `minetest.register_tool(name, item definition)`
+* `minetest.register_craftitem(name, item definition)`
+* `minetest.unregister_item(name)`
+* `minetest.register_alias(name, convert_to)`
+ * Also use this to set the 'mapgen aliases' needed in a game for the core
+ * mapgens. See 'Mapgen aliases' section above.
+* `minetest.register_alias_force(name, convert_to)`
+* `minetest.register_craft(recipe)`
+ * Check recipe table syntax for different types below.
+* `minetest.clear_craft(recipe)`
+ * Will erase existing craft based either on output item or on input recipe.
+ * Specify either output or input only. If you specify both, input will be ignored. For input use the same recipe table
+ syntax as for `minetest.register_craft(recipe)`. For output specify only the item, without a quantity.
+ * If no erase candidate could be found, Lua exception will be thrown.
+ * **Warning**! The type field ("shaped","cooking" or any other) will be ignored if the recipe
+ contains output. Erasing is then done independently from the crafting method.
+* `minetest.register_ore(ore definition)`
+* `minetest.register_biome(biome definition)`
+* `minetest.register_decoration(decoration definition)`
+* `minetest.override_item(name, redefinition)`
+ * Overrides fields of an item registered with register_node/tool/craftitem.
+ * Note: Item must already be defined, (opt)depend on the mod defining it.
+ * Example: `minetest.override_item("default:mese", {light_source=LIGHT_MAX})`
+* `minetest.clear_registered_ores()`
+* `minetest.clear_registered_biomes()`
+* `minetest.clear_registered_decorations()`
+
+### Global callback registration functions
+Call these functions only at load time!
+
+* `minetest.register_globalstep(func(dtime))`
+ * Called every server step, usually interval of 0.1s
+* `minetest.register_on_shutdown(func())`
+ * Called before server shutdown
+ * **Warning**: If the server terminates abnormally (i.e. crashes), the registered
+ callbacks **will likely not be run**. Data should be saved at
+ semi-frequent intervals as well as on server shutdown.
+* `minetest.register_on_placenode(func(pos, newnode, placer, oldnode, itemstack, pointed_thing))`
+ * Called when a node has been placed
+ * If return `true` no item is taken from `itemstack`
+ * `placer` may be any valid ObjectRef or nil.
+ * **Not recommended**; use `on_construct` or `after_place_node` in node definition
+ whenever possible
+* `minetest.register_on_dignode(func(pos, oldnode, digger))`
+ * Called when a node has been dug.
+ * **Not recommended**; Use `on_destruct` or `after_dig_node` in node definition
+ whenever possible
+* `minetest.register_on_punchnode(func(pos, node, puncher, pointed_thing))`
+ * Called when a node is punched
+* `minetest.register_on_generated(func(minp, maxp, blockseed))`
+ * Called after generating a piece of world. Modifying nodes inside the area
+ is a bit faster than usually.
+* `minetest.register_on_newplayer(func(ObjectRef))`
+ * Called after a new player has been created
+* `minetest.register_on_dieplayer(func(ObjectRef))`
+ * Called when a player dies
+* `minetest.register_on_punchplayer(func(player, hitter, time_from_last_punch, tool_capabilities, dir, damage))`
+ * Called when a player is punched
+ * `player` - ObjectRef - Player that was punched
+ * `hitter` - ObjectRef - Player that hit
+ * `time_from_last_punch`: Meant for disallowing spamming of clicks (can be nil)
+ * `tool_capabilities`: capability table of used tool (can be nil)
+ * `dir`: unit vector of direction of punch. Always defined. Points from
+ the puncher to the punched.
+ * `damage` - number that represents the damage calculated by the engine
+ * should return `true` to prevent the default damage mechanism
+* `minetest.register_on_player_hpchange(func(player, hp_change), modifier)`
+ * Called when the player gets damaged or healed
+ * `player`: ObjectRef of the player
+ * `hp_change`: the amount of change. Negative when it is damage.
+ * `modifier`: when true, the function should return the actual `hp_change`.
+ Note: modifiers only get a temporary hp_change that can be modified by later modifiers.
+ modifiers can return true as a second argument to stop the execution of further functions.
+ Non-modifiers receive the final hp change calculated by the modifiers.
+* `minetest.register_on_respawnplayer(func(ObjectRef))`
+ * Called when player is to be respawned
+ * Called _before_ repositioning of player occurs
+ * return true in func to disable regular player placement
+* `minetest.register_on_prejoinplayer(func(name, ip))`
+ * Called before a player joins the game
+ * If it returns a string, the player is disconnected with that string as reason
+* `minetest.register_on_joinplayer(func(ObjectRef))`
+ * Called when a player joins the game
+* `minetest.register_on_leaveplayer(func(ObjectRef, timed_out))`
+ * Called when a player leaves the game
+ * `timed_out`: True for timeout, false for other reasons.
+* `minetest.register_on_cheat(func(ObjectRef, cheat))`
+ * Called when a player cheats
+ * `cheat`: `{type=<cheat_type>}`, where `<cheat_type>` is one of:
+ * `moved_too_fast`
+ * `interacted_too_far`
+ * `interacted_while_dead`
+ * `finished_unknown_dig`
+ * `dug_unbreakable`
+ * `dug_too_fast`
+* `minetest.register_on_chat_message(func(name, message))`
+ * Called always when a player says something
+ * Return `true` to mark the message as handled, which means that it will not be sent to other players
+* `minetest.register_on_player_receive_fields(func(player, formname, fields))`
+ * Called when a button is pressed in player's inventory form
+ * Newest functions are called first
+ * If function returns `true`, remaining functions are not called
+* `minetest.register_on_craft(func(itemstack, player, old_craft_grid, craft_inv))`
+ * Called when `player` crafts something
+ * `itemstack` is the output
+ * `old_craft_grid` contains the recipe (Note: the one in the inventory is cleared)
+ * `craft_inv` is the inventory with the crafting grid
+ * Return either an `ItemStack`, to replace the output, or `nil`, to not modify it
+* `minetest.register_craft_predict(func(itemstack, player, old_craft_grid, craft_inv))`
+ * The same as before, except that it is called before the player crafts, to make
+ craft prediction, and it should not change anything.
+* `minetest.register_on_protection_violation(func(pos, name))`
+ * Called by `builtin` and mods when a player violates protection at a position
+ (eg, digs a node or punches a protected entity).
+ * The registered functions can be called using `minetest.record_protection_violation`
+ * The provided function should check that the position is protected by the mod
+ calling this function before it prints a message, if it does, to allow for
+ multiple protection mods.
+* `minetest.register_on_item_eat(func(hp_change, replace_with_item, itemstack, user, pointed_thing))`
+ * Called when an item is eaten, by `minetest.item_eat`
+ * Return `true` or `itemstack` to cancel the default item eat response (i.e.: hp increase)
+
+### Other registration functions
+* `minetest.register_chatcommand(cmd, chatcommand definition)`
+ * Adds definition to `minetest.registered_chatcommands`
+* `minetest.override_chatcommand(name, redefinition)`
+ * Overrides fields of a chatcommand registered with `register_chatcommand`.
+* `minetest.unregister_chatcommand(name)`
+ * Unregisters a chatcommands registered with `register_chatcommand`.
+* `minetest.register_privilege(name, definition)`
+ * `definition`: `"description text"`
+ * `definition`: `{ description = "description text", give_to_singleplayer = boolean}`
+ the default of `give_to_singleplayer` is true
+ * To allow players with `basic_privs` to grant, see `basic_privs` minetest.conf setting.
+* `minetest.register_authentication_handler(authentication handler definition)`
+ * Registers an auth handler that overrides the builtin one
+ * This function can be called by a single mod once only.
+
+### Setting-related
+* `minetest.settings`: Settings object containing all of the settings from the
+ main config file (`minetest.conf`).
+* `minetest.setting_get_pos(name)`: Loads a setting from the main settings and
+ parses it as a position (in the format `(1,2,3)`). Returns a position or nil.
+
+### Authentication
+* `minetest.string_to_privs(str)`: returns `{priv1=true,...}`
+* `minetest.privs_to_string(privs)`: returns `"priv1,priv2,..."`
+ * Convert between two privilege representations
+* `minetest.get_player_privs(name) -> {priv1=true,...}`
+* `minetest.check_player_privs(player_or_name, ...)`: returns `bool, missing_privs`
+ * A quickhand for checking privileges.
+ * `player_or_name`: Either a Player object or the name of a player.
+ * `...` is either a list of strings, e.g. `"priva", "privb"` or
+ a table, e.g. `{ priva = true, privb = true }`.
+
+* `minetest.check_password_entry(name, entry, password)`
+ * Returns true if the "password entry" for a player with name matches given
+ password, false otherwise.
+ * The "password entry" is the password representation generated by the engine
+ as returned as part of a `get_auth()` call on the auth handler.
+ * Only use this function for making it possible to log in via password from
+ external protocols such as IRC, other uses are frowned upon.
+* `minetest.get_password_hash(name, raw_password)`
+ * Convert a name-password pair to a password hash that Minetest can use.
+ * The returned value alone is not a good basis for password checks based
+ on comparing the password hash in the database with the password hash
+ from the function, with an externally provided password, as the hash
+ in the db might use the new SRP verifier format.
+ * For this purpose, use `minetest.check_password_entry` instead.
+* `minetest.get_player_ip(name)`: returns an IP address string for the player `name`
+ * The player needs to be online for this to be successful.
+
+* `minetest.get_auth_handler()`: Return the currently active auth handler
+ * See the `Authentication handler definition`
+ * Use this to e.g. get the authentication data for a player:
+ `local auth_data = minetest.get_auth_handler().get_auth(playername)`
+* `minetest.notify_authentication_modified(name)`
+ * Must be called by the authentication handler for privilege changes.
+ * `name`: string; if omitted, all auth data should be considered modified
+* `minetest.set_player_password(name, password_hash)`: Set password hash of player `name`
+* `minetest.set_player_privs(name, {priv1=true,...})`: Set privileges of player `name`
+* `minetest.auth_reload()`
+ * See `reload()` in authentication handler definition
+
+`minetest.set_player_password`, `minetest_set_player_privs`, `minetest_get_player_privs`
+and `minetest.auth_reload` call the authetification handler.
+
+### Chat
+* `minetest.chat_send_all(text)`
+* `minetest.chat_send_player(name, text)`
+
+### Environment access
+* `minetest.set_node(pos, node)`
+* `minetest.add_node(pos, node): alias set_node(pos, node)`
+ * Set node at position (`node = {name="foo", param1=0, param2=0}`)
+* `minetest.swap_node(pos, node)`
+ * Set node at position, but don't remove metadata
+* `minetest.remove_node(pos)`
+ * Equivalent to `set_node(pos, "air")`
+* `minetest.get_node(pos)`
+ * Returns the node at the given position as table in the format
+ `{name="node_name", param1=0, param2=0}`, returns `{name="ignore", param1=0, param2=0}`
+ for unloaded areas.
+* `minetest.get_node_or_nil(pos)`
+ * Same as `get_node` but returns `nil` for unloaded areas.
+* `minetest.get_node_light(pos, timeofday)`
+ * Gets the light value at the given position. Note that the light value
+ "inside" the node at the given position is returned, so you usually want
+ to get the light value of a neighbor.
+ * `pos`: The position where to measure the light.
+ * `timeofday`: `nil` for current time, `0` for night, `0.5` for day
+ * Returns a number between `0` and `15` or `nil`
+* `minetest.place_node(pos, node)`
+ * Place node with the same effects that a player would cause
+* `minetest.dig_node(pos)`
+ * Dig node with the same effects that a player would cause
+ * Returns `true` if successful, `false` on failure (e.g. protected location)
+* `minetest.punch_node(pos)`
+ * Punch node with the same effects that a player would cause
+* `minetest.spawn_falling_node(pos)`
+ * Change node into falling node
+ * Returns `true` if successful, `false` on failure
+
+* `minetest.find_nodes_with_meta(pos1, pos2)`
+ * Get a table of positions of nodes that have metadata within a region {pos1, pos2}
+* `minetest.get_meta(pos)`
+ * Get a `NodeMetaRef` at that position
+* `minetest.get_node_timer(pos)`
+ * Get `NodeTimerRef`
+
+* `minetest.add_entity(pos, name, [staticdata])`: Spawn Lua-defined entity at position
+ * Returns `ObjectRef`, or `nil` if failed
+* `minetest.add_item(pos, item)`: Spawn item
+ * Returns `ObjectRef`, or `nil` if failed
+* `minetest.get_player_by_name(name)`: Get an `ObjectRef` to a player
+* `minetest.get_objects_inside_radius(pos, radius)`
+ * `radius`: using an euclidean metric
+* `minetest.set_timeofday(val)`
+ * `val` is between `0` and `1`; `0` for midnight, `0.5` for midday
+* `minetest.get_timeofday()`
+* `minetest.get_gametime()`: returns the time, in seconds, since the world was created
+* `minetest.get_day_count()`: returns number days elapsed since world was created,
+ * accounting for time changes.
+* `minetest.find_node_near(pos, radius, nodenames, [search_center])`: returns pos or `nil`
+ * `radius`: using a maximum metric
+ * `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"`
+ * `search_center` is an optional boolean (default: `false`)
+ If true `pos` is also checked for the nodes
+* `minetest.find_nodes_in_area(pos1, pos2, nodenames)`: returns a list of positions
+ * `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"`
+ * First return value: Table with all node positions
+ * Second return value: Table with the count of each node with the node name as index
+ * Area volume is limited to 4,096,000 nodes
+* `minetest.find_nodes_in_area_under_air(pos1, pos2, nodenames)`: returns a list of positions
+ * `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"`
+ * Return value: Table with all node positions with a node air above
+ * Area volume is limited to 4,096,000 nodes
+* `minetest.get_perlin(noiseparams)`
+* `minetest.get_perlin(seeddiff, octaves, persistence, scale)`
+ * Return world-specific perlin noise (`int(worldseed)+seeddiff`)
+* `minetest.get_voxel_manip([pos1, pos2])`
+ * Return voxel manipulator object.
+ * Loads the manipulator from the map if positions are passed.
+* `minetest.set_gen_notify(flags, {deco_ids})`
+ * Set the types of on-generate notifications that should be collected
+ * `flags` is a flag field with the available flags: `dungeon`, `temple`, `cave_begin`,
+ `cave_end`, `large_cave_begin`, `large_cave_end`, `decoration`
+ * The second parameter is a list of IDS of decorations which notification is requested for
+* `get_gen_notify()`: returns a flagstring and a table with the `deco_id`s
+* `minetest.get_mapgen_object(objectname)`
+ * Return requested mapgen object if available (see "Mapgen objects")
+* `minetest.get_biome_id(biome_name)`
+ * Returns the biome id, as used in the biomemap Mapgen object, for a
+ given biome_name string.
+* `minetest.get_mapgen_params()` Returns mapgen parameters, a table containing
+ `mgname`, `seed`, `chunksize`, `water_level`, and `flags`.
+ * Deprecated: use `minetest.get_mapgen_setting(name)` instead
+* `minetest.set_mapgen_params(MapgenParams)`
+ * Deprecated: use `minetest.set_mapgen_setting(name, value, override)` instead
+ * Set map generation parameters
+ * Function cannot be called after the registration period; only initialization
+ and `on_mapgen_init`
+ * Takes a table as an argument with the fields `mgname`, `seed`, `water_level`,
+ and `flags`.
+ * Leave field unset to leave that parameter unchanged
+ * `flags` contains a comma-delimited string of flags to set,
+ or if the prefix `"no"` is attached, clears instead.
+ * `flags` is in the same format and has the same options as `mg_flags` in `minetest.conf`
+* `minetest.get_mapgen_setting(name)`
+ * Gets the *active* mapgen setting (or nil if none exists) in string format with the following
+ order of precedence:
+ 1) Settings loaded from map_meta.txt or overrides set during mod execution
+ 2) Settings set by mods without a metafile override
+ 3) Settings explicitly set in the user config file, minetest.conf
+ 4) Settings set as the user config default
+* `minetest.get_mapgen_setting_noiseparams(name)`
+ * Same as above, but returns the value as a NoiseParams table if the setting `name` exists
+ and is a valid NoiseParams
+* `minetest.set_mapgen_setting(name, value, [override_meta])`
+ * Sets a mapgen param to `value`, and will take effect if the corresponding mapgen setting
+ is not already present in map_meta.txt.
+ * `override_meta` is an optional boolean (default: `false`). If this is set to true,
+ the setting will become the active setting regardless of the map metafile contents.
+ * Note: to set the seed, use `"seed"`, not `"fixed_map_seed"`
+* `minetest.set_mapgen_setting_noiseparams(name, value, [override_meta])`
+ * Same as above, except value is a NoiseParams table.
+* `minetest.set_noiseparams(name, noiseparams, set_default)`
+ * Sets the noiseparams setting of `name` to the noiseparams table specified in `noiseparams`.
+ * `set_default` is an optional boolean (default: `true`) that specifies whether the setting
+ should be applied to the default config or current active config
+* `minetest.get_noiseparams(name)`: returns a table of the noiseparams for name
+* `minetest.generate_ores(vm, pos1, pos2)`
+ * Generate all registered ores within the VoxelManip `vm` and in the area from `pos1` to `pos2`.
+ * `pos1` and `pos2` are optional and default to mapchunk minp and maxp.
+* `minetest.generate_decorations(vm, pos1, pos2)`
+ * Generate all registered decorations within the VoxelManip `vm` and in the area from `pos1` to `pos2`.
+ * `pos1` and `pos2` are optional and default to mapchunk minp and maxp.
+* `minetest.clear_objects([options])`
+ * Clear all objects in the environment
+ * Takes an optional table as an argument with the field `mode`.
+ * mode = `"full"`: Load and go through every mapblock, clearing objects (default).
+ * mode = `"quick"`: Clear objects immediately in loaded mapblocks;
+ clear objects in unloaded mapblocks only when the mapblocks are next activated.
+* `minetest.emerge_area(pos1, pos2, [callback], [param])`
+ * Queue all blocks in the area from `pos1` to `pos2`, inclusive, to be asynchronously
+ * fetched from memory, loaded from disk, or if inexistent, generates them.
+ * If `callback` is a valid Lua function, this will be called for each block emerged.
+ * The function signature of callback is:
+ * `function EmergeAreaCallback(blockpos, action, calls_remaining, param)`
+ * - `blockpos` is the *block* coordinates of the block that had been emerged
+ * - `action` could be one of the following constant values:
+ * `minetest.EMERGE_CANCELLED`, `minetest.EMERGE_ERRORED`, `minetest.EMERGE_FROM_MEMORY`,
+ * `minetest.EMERGE_FROM_DISK`, `minetest.EMERGE_GENERATED`
+ * - `calls_remaining` is the number of callbacks to be expected after this one
+ * - `param` is the user-defined parameter passed to emerge_area (or nil if the
+ * parameter was absent)
+* `minetest.delete_area(pos1, pos2)`
+ * delete all mapblocks in the area from pos1 to pos2, inclusive
+* `minetest.line_of_sight(pos1, pos2, stepsize)`: returns `boolean, pos`
+ * Check if there is a direct line of sight between `pos1` and `pos2`
+ * Returns the position of the blocking node when `false`
+ * `pos1`: First position
+ * `pos2`: Second position
+ * `stepsize`: smaller gives more accurate results but requires more computing
+ time. Default is `1`.
+* `minetest.find_path(pos1,pos2,searchdistance,max_jump,max_drop,algorithm)`
+ * returns table containing path
+ * returns a table of 3D points representing a path from `pos1` to `pos2` or `nil`
+ * `pos1`: start position
+ * `pos2`: end position
+ * `searchdistance`: number of blocks to search in each direction using a maximum metric
+ * `max_jump`: maximum height difference to consider walkable
+ * `max_drop`: maximum height difference to consider droppable
+ * `algorithm`: One of `"A*_noprefetch"` (default), `"A*"`, `"Dijkstra"`
+* `minetest.spawn_tree (pos, {treedef})`
+ * spawns L-system tree at given `pos` with definition in `treedef` table
+* `minetest.transforming_liquid_add(pos)`
+ * add node to liquid update queue
+* `minetest.get_node_max_level(pos)`
+ * get max available level for leveled node
+* `minetest.get_node_level(pos)`
+ * get level of leveled node (water, snow)
+* `minetest.set_node_level(pos, level)`
+ * set level of leveled node, default `level` equals `1`
+ * if `totallevel > maxlevel`, returns rest (`total-max`).
+* `minetest.add_node_level(pos, level)`
+ * increase level of leveled node by level, default `level` equals `1`
+ * if `totallevel > maxlevel`, returns rest (`total-max`)
+ * can be negative for decreasing
+* `minetest.fix_light(pos1, pos2)`: returns `true`/`false`
+ * resets the light in a cuboid-shaped part of
+ the map and removes lighting bugs.
+ * Loads the area if it is not loaded.
+ * `pos1` is the corner of the cuboid with the least coordinates
+ (in node coordinates), inclusive.
+ * `pos2` is the opposite corner of the cuboid, inclusive.
+ * The actual updated cuboid might be larger than the specified one,
+ because only whole map blocks can be updated.
+ The actual updated area consists of those map blocks that intersect
+ with the given cuboid.
+ * However, the neighborhood of the updated area might change
+ as well, as light can spread out of the cuboid, also light
+ might be removed.
+ * returns `false` if the area is not fully generated,
+ `true` otherwise
+* `minetest.check_single_for_falling(pos)`
+ * causes an unsupported `group:falling_node` node to fall and causes an
+ unattached `group:attached_node` node to fall.
+ * does not spread these updates to neighbours.
+* `minetest.check_for_falling(pos)`
+ * causes an unsupported `group:falling_node` node to fall and causes an
+ unattached `group:attached_node` node to fall.
+ * spread these updates to neighbours and can cause a cascade
+ of nodes to fall.
+
+### Inventory
+`minetest.get_inventory(location)`: returns an `InvRef`
+
+* `location` = e.g.
+ * `{type="player", name="celeron55"}`
+ * `{type="node", pos={x=, y=, z=}}`
+ * `{type="detached", name="creative"}`
+* `minetest.create_detached_inventory(name, callbacks, [player_name])`: returns an `InvRef`
+ * callbacks: See "Detached inventory callbacks"
+ * `player_name`: Make detached inventory available to one player exclusively,
+ by default they will be sent to every player (even if not used).
+ Note that this parameter is mostly just a workaround and will be removed in future releases.
+ * Creates a detached inventory. If it already exists, it is cleared.
+* `minetest.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)`:
+ returns left over ItemStack
+ * See `minetest.item_eat` and `minetest.register_on_item_eat`
+
+### Formspec
+* `minetest.show_formspec(playername, formname, formspec)`
+ * `playername`: name of player to show formspec
+ * `formname`: name passed to `on_player_receive_fields` callbacks.
+ It should follow the `"modname:<whatever>"` naming convention
+ * `formspec`: formspec to display
+* `minetest.close_formspec(playername, formname)`
+ * `playername`: name of player to close formspec
+ * `formname`: has to exactly match the one given in `show_formspec`, or the formspec will
+ not close.
+ * calling `show_formspec(playername, formname, "")` is equal to this expression
+ * to close a formspec regardless of the formname, call
+ `minetest.close_formspec(playername, "")`. **USE THIS ONLY WHEN ABSOLUTELY NECESSARY!**
+* `minetest.formspec_escape(string)`: returns a string
+ * escapes the characters "[", "]", "\", "," and ";", which can not be used in formspecs
+* `minetest.explode_table_event(string)`: returns a table
+ * returns e.g. `{type="CHG", row=1, column=2}`
+ * `type` is one of:
+ * `"INV"`: no row selected)
+ * `"CHG"`: selected)
+ * `"DCL"`: double-click
+* `minetest.explode_textlist_event(string)`: returns a table
+ * returns e.g. `{type="CHG", index=1}`
+ * `type` is one of:
+ * `"INV"`: no row selected)
+ * `"CHG"`: selected)
+ * `"DCL"`: double-click
+* `minetest.explode_scrollbar_event(string)`: returns a table
+ * returns e.g. `{type="CHG", value=500}`
+ * `type` is one of:
+ * `"INV"`: something failed
+ * `"CHG"`: has been changed
+ * `"VAL"`: not changed
+
+### Item handling
+* `minetest.inventorycube(img1, img2, img3)`
+ * Returns a string for making an image of a cube (useful as an item image)
+* `minetest.get_pointed_thing_position(pointed_thing, above)`
+ * Get position of a `pointed_thing` (that you can get from somewhere)
+* `minetest.dir_to_facedir(dir, is6d)`
+ * Convert a vector to a facedir value, used in `param2` for `paramtype2="facedir"`;
+ * passing something non-`nil`/`false` for the optional second parameter causes it to
+ take the y component into account
+* `minetest.facedir_to_dir(facedir)`
+ * Convert a facedir back into a vector aimed directly out the "back" of a node
+* `minetest.dir_to_wallmounted(dir)`
+ * Convert a vector to a wallmounted value, used for `paramtype2="wallmounted"`
+* `minetest.wallmounted_to_dir(wallmounted)`
+ * Convert a wallmounted value back into a vector aimed directly out the "back" of a node
+* `minetest.dir_to_yaw(dir)`
+ * Convert a vector into a yaw (angle)
+* `minetest.yaw_to_dir(yaw)`
+ * Convert yaw (angle) to a vector
+* `minetest.is_colored_paramtype(ptype)`
+ * Returns a boolean. Returns `true` if the given `paramtype2` contains color
+ information (`color`, `colorwallmounted` or `colorfacedir`).
+* `minetest.strip_param2_color(param2, paramtype2)`
+ * Removes everything but the color information from the
+ given `param2` value.
+ * Returns `nil` if the given `paramtype2` does not contain color information
+* `minetest.get_node_drops(nodename, toolname)`
+ * Returns list of item names.
+ * **Note**: This will be removed or modified in a future version.
+* `minetest.get_craft_result(input)`: returns `output, decremented_input`
+ * `input.method` = `"normal"` or `"cooking"` or `"fuel"`
+ * `input.width` = for example `3`
+ * `input.items` = for example
+ `{ stack1, stack2, stack3, stack4, stack 5, stack 6, stack 7, stack 8, stack 9 }`
+ * `output.item` = `ItemStack`, if unsuccessful: empty `ItemStack`
+ * `output.time` = a number, if unsuccessful: `0`
+ * `output.replacements` = list of `ItemStack`s that couldn't be placed in
+ `decremented_input.items`
+ * `decremented_input` = like `input`
+* `minetest.get_craft_recipe(output)`: returns input
+ * returns last registered recipe for output item (node)
+ * `output` is a node or item type such as `"default:torch"`
+ * `input.method` = `"normal"` or `"cooking"` or `"fuel"`
+ * `input.width` = for example `3`
+ * `input.items` = for example
+ `{ stack1, stack2, stack3, stack4, stack 5, stack 6, stack 7, stack 8, stack 9 }`
+ * `input.items` = `nil` if no recipe found
+* `minetest.get_all_craft_recipes(query item)`: returns a table or `nil`
+ * returns indexed table with all registered recipes for query item (node)
+ or `nil` if no recipe was found
+ * recipe entry table:
+
+ {
+ method = 'normal' or 'cooking' or 'fuel'
+ width = 0-3, 0 means shapeless recipe
+ items = indexed [1-9] table with recipe items
+ output = string with item name and quantity
+ }
+ * Example query for `"default:gold_ingot"` will return table:
+
+ {
+ [1]={method = "cooking", width = 3, output = "default:gold_ingot",
+ items = {1 = "default:gold_lump"}},
+ [2]={method = "normal", width = 1, output = "default:gold_ingot 9",
+ items = {1 = "default:goldblock"}}
+ }
+* `minetest.handle_node_drops(pos, drops, digger)`
+ * `drops`: list of itemstrings
+ * Handles drops from nodes after digging: Default action is to put them into
+ digger's inventory
+ * Can be overridden to get different functionality (e.g. dropping items on
+ ground)
+
+### Rollback
+* `minetest.rollback_get_node_actions(pos, range, seconds, limit)`:
+ returns `{{actor, pos, time, oldnode, newnode}, ...}`
+ * Find who has done something to a node, or near a node
+ * `actor`: `"player:<name>"`, also `"liquid"`.
+* `minetest.rollback_revert_actions_by(actor, seconds)`: returns `boolean, log_messages`
+ * Revert latest actions of someone
+ * `actor`: `"player:<name>"`, also `"liquid"`.
+
+### Defaults for the `on_*` item definition functions
+These functions return the leftover itemstack.
+
+* `minetest.item_place_node(itemstack, placer, pointed_thing[, param2, prevent_after_place])`
+ * Place item as a node
+ * `param2` overrides `facedir` and wallmounted `param2`
+ * `prevent_after_place`: if set to `true`, `after_place_node` is not called
+ for the newly placed node to prevent a callback and placement loop
+ * returns `itemstack, success`
+* `minetest.item_place_object(itemstack, placer, pointed_thing)`
+ * Place item as-is
+* `minetest.item_place(itemstack, placer, pointed_thing, param2)`
+ * Use one of the above based on what the item is.
+ * Calls `on_rightclick` of `pointed_thing.under` if defined instead
+ * **Note**: is not called when wielded item overrides `on_place`
+ * `param2` overrides `facedir` and wallmounted `param2`
+ * returns `itemstack, success`
+* `minetest.item_drop(itemstack, dropper, pos)`
+ * Drop the item
+* `minetest.item_eat(hp_change, replace_with_item)`
+ * Eat the item.
+ * `replace_with_item` is the itemstring which is added to the inventory.
+ If the player is eating a stack, then replace_with_item goes to a
+ different spot. Can be `nil`
+ * See `minetest.do_item_eat`
+
+### Defaults for the `on_punch` and `on_dig` node definition callbacks
+* `minetest.node_punch(pos, node, puncher, pointed_thing)`
+ * Calls functions registered by `minetest.register_on_punchnode()`
+* `minetest.node_dig(pos, node, digger)`
+ * Checks if node can be dug, puts item into inventory, removes node
+ * Calls functions registered by `minetest.registered_on_dignodes()`
+
+### Sounds
+* `minetest.sound_play(spec, parameters)`: returns a handle
+ * `spec` is a `SimpleSoundSpec`
+ * `parameters` is a sound parameter table
+* `minetest.sound_stop(handle)`
+* `minetest.sound_fade(handle, step, gain)`
+ * `handle` is a handle returned by `minetest.sound_play`
+ * `step` determines how fast a sound will fade.
+ Negative step will lower the sound volume, positive step will increase the sound volume
+ * `gain` the target gain for the fade.
+
+### Timing
+* `minetest.after(time, func, ...)`
+ * Call the function `func` after `time` seconds, may be fractional
+ * Optional: Variable number of arguments that are passed to `func`
+
+### Server
+* `minetest.request_shutdown([message],[reconnect],[delay])`: request for server shutdown. Will display `message` to clients,
+ `reconnect` == true displays a reconnect button,
+ `delay` adds an optional delay (in seconds) before shutdown
+ negative delay cancels the current active shutdown
+ zero delay triggers an immediate shutdown.
+* `minetest.cancel_shutdown_requests()`: cancel current delayed shutdown
+* `minetest.get_server_status()`: returns server status string
+* `minetest.get_server_uptime()`: returns the server uptime in seconds
+* `minetest.remove_player(name)`: remove player from database (if he is not connected).
+ * Does not remove player authentication data, minetest.player_exists will continue to return true.
+ * Returns a code (0: successful, 1: no such player, 2: player is connected)
+
+### Bans
+* `minetest.get_ban_list()`: returns the ban list (same as `minetest.get_ban_description("")`)
+* `minetest.get_ban_description(ip_or_name)`: returns ban description (string)
+* `minetest.ban_player(name)`: ban a player
+* `minetest.unban_player_or_ip(name)`: unban player or IP address
+* `minetest.kick_player(name, [reason])`: disconnect a player with a optional reason
+
+### Particles
+* `minetest.add_particle(particle definition)`
+ * Deprecated: `minetest.add_particle(pos, velocity, acceleration, expirationtime,
+ size, collisiondetection, texture, playername)`
+
+* `minetest.add_particlespawner(particlespawner definition)`
+ * Add a `ParticleSpawner`, an object that spawns an amount of particles over `time` seconds
+ * Returns an `id`, and -1 if adding didn't succeed
+ * `Deprecated: minetest.add_particlespawner(amount, time,
+ minpos, maxpos,
+ minvel, maxvel,
+ minacc, maxacc,
+ minexptime, maxexptime,
+ minsize, maxsize,
+ collisiondetection, texture, playername)`
+
+* `minetest.delete_particlespawner(id, player)`
+ * Delete `ParticleSpawner` with `id` (return value from `minetest.add_particlespawner`)
+ * If playername is specified, only deletes on the player's client,
+ * otherwise on all clients
+
+### Schematics
+* `minetest.create_schematic(p1, p2, probability_list, filename, slice_prob_list)`
+ * Create a schematic from the volume of map specified by the box formed by p1 and p2.
+ * Apply the specified probability values to the specified nodes in `probability_list`.
+ * `probability_list` is an array of tables containing two fields, `pos` and `prob`.
+ * `pos` is the 3D vector specifying the absolute coordinates of the
+ node being modified,
+ * `prob` is the integer value from `0` to `255` of the probability (see: Schematic specifier).
+ * If there are two or more entries with the same pos value, the
+ last entry is used.
+ * If `pos` is not inside the box formed by `p1` and `p2`, it is ignored.
+ * If `probability_list` equals `nil`, no probabilities are applied.
+ * Slice probability works in the same manner, except takes a field
+ called `ypos` instead which
+ indicates the y position of the slice with a probability applied.
+ * If slice probability list equals `nil`, no slice probabilities are applied.
+ * Saves schematic in the Minetest Schematic format to filename.
+
+* `minetest.place_schematic(pos, schematic, rotation, replacements, force_placement)`
+ * Place the schematic specified by schematic (see: Schematic specifier) at `pos`.
+ * `rotation` can equal `"0"`, `"90"`, `"180"`, `"270"`, or `"random"`.
+ * If the `rotation` parameter is omitted, the schematic is not rotated.
+ * `replacements` = `{["old_name"] = "convert_to", ...}`
+ * `force_placement` is a boolean indicating whether nodes other than `air` and
+ `ignore` are replaced by the schematic
+ * Returns nil if the schematic could not be loaded.
+
+* `minetest.place_schematic_on_vmanip(vmanip, pos, schematic, rotation, replacement, force_placement)`:
+ * This function is analagous to minetest.place_schematic, but places a schematic onto the
+ specified VoxelManip object `vmanip` instead of the whole map.
+ * Returns false if any part of the schematic was cut-off due to the VoxelManip not
+ containing the full area required, and true if the whole schematic was able to fit.
+ * Returns nil if the schematic could not be loaded.
+ * After execution, any external copies of the VoxelManip contents are invalidated.
+
+* `minetest.serialize_schematic(schematic, format, options)`
+ * Return the serialized schematic specified by schematic (see: Schematic specifier)
+ * in the `format` of either "mts" or "lua".
+ * "mts" - a string containing the binary MTS data used in the MTS file format
+ * "lua" - a string containing Lua code representing the schematic in table format
+ * `options` is a table containing the following optional parameters:
+ * If `lua_use_comments` is true and `format` is "lua", the Lua code generated will have (X, Z)
+ * position comments for every X row generated in the schematic data for easier reading.
+ * If `lua_num_indent_spaces` is a nonzero number and `format` is "lua", the Lua code generated
+ * will use that number of spaces as indentation instead of a tab character.
+
+### HTTP Requests:
+* `minetest.request_http_api()`:
+ * returns `HTTPApiTable` containing http functions if the calling mod has been granted
+ access by being listed in the `secure.http_mods` or `secure.trusted_mods` setting,
+ otherwise returns `nil`.
+ * The returned table contains the functions `fetch`, `fetch_async` and `fetch_async_get`
+ described below.
+ * Only works at init time and must be called from the mod's main scope (not from a function).
+ * Function only exists if minetest server was built with cURL support.
+ * **DO NOT ALLOW ANY OTHER MODS TO ACCESS THE RETURNED TABLE, STORE IT IN
+ A LOCAL VARIABLE!**
+* `HTTPApiTable.fetch(HTTPRequest req, callback)`
+ * Performs given request asynchronously and calls callback upon completion
+ * callback: `function(HTTPRequestResult res)`
+ * Use this HTTP function if you are unsure, the others are for advanced use.
+* `HTTPApiTable.fetch_async(HTTPRequest req)`: returns handle
+ * Performs given request asynchronously and returns handle for `HTTPApiTable.fetch_async_get`
+* `HTTPApiTable.fetch_async_get(handle)`: returns HTTPRequestResult
+ * Return response data for given asynchronous HTTP request
+
+### Storage API:
+* `minetest.get_mod_storage()`:
+ * returns reference to mod private `StorageRef`
+ * must be called during mod load time
+
+### Misc.
+* `minetest.get_connected_players()`: returns list of `ObjectRefs`
+* `minetest.is_player(o)`: boolean, whether `o` is a player
+* `minetest.player_exists(name)`: boolean, whether player exists (regardless of online status)
+* `minetest.hud_replace_builtin(name, hud_definition)`
+ * Replaces definition of a builtin hud element
+ * `name`: `"breath"` or `"health"`
+ * `hud_definition`: definition to replace builtin definition
+* `minetest.hash_node_position({x=,y=,z=})`: returns an 48-bit integer
+ * Gives a unique hash number for a node position (16+16+16=48bit)
+* `minetest.get_position_from_hash(hash)`: returns a position
+ * Inverse transform of `minetest.hash_node_position`
+* `minetest.get_item_group(name, group)`: returns a rating
+ * Get rating of a group of an item. (`0` means: not in group)
+* `minetest.get_node_group(name, group)`: returns a rating
+ * Deprecated: An alias for the former.
+* `minetest.raillike_group(name)`: returns a rating
+ * Returns rating of the connect_to_raillike group corresponding to name
+ * If name is not yet the name of a connect_to_raillike group, a new group id
+ * is created, with that name
+* `minetest.get_content_id(name)`: returns an integer
+ * Gets the internal content ID of `name`
+* `minetest.get_name_from_content_id(content_id)`: returns a string
+ * Gets the name of the content with that content ID
+* `minetest.parse_json(string[, nullvalue])`: returns something
+ * Convert a string containing JSON data into the Lua equivalent
+ * `nullvalue`: returned in place of the JSON null; defaults to `nil`
+ * On success returns a table, a string, a number, a boolean or `nullvalue`
+ * On failure outputs an error message and returns `nil`
+ * Example: `parse_json("[10, {\"a\":false}]")`, returns `{10, {a = false}}`
+* `minetest.write_json(data[, styled])`: returns a string or `nil` and an error message
+ * Convert a Lua table into a JSON string
+ * styled: Outputs in a human-readable format if this is set, defaults to false
+ * Unserializable things like functions and userdata will cause an error.
+ * **Warning**: JSON is more strict than the Lua table format.
+ 1. You can only use strings and positive integers of at least one as keys.
+ 2. You can not mix string and integer keys.
+ This is due to the fact that JSON has two distinct array and object values.
+ * Example: `write_json({10, {a = false}})`, returns `"[10, {\"a\": false}]"`
+* `minetest.serialize(table)`: returns a string
+ * Convert a table containing tables, strings, numbers, booleans and `nil`s
+ into string form readable by `minetest.deserialize`
+ * Example: `serialize({foo='bar'})`, returns `'return { ["foo"] = "bar" }'`
+* `minetest.deserialize(string)`: returns a table
+ * Convert a string returned by `minetest.deserialize` into a table
+ * `string` is loaded in an empty sandbox environment.
+ * Will load functions, but they cannot access the global environment.
+ * Example: `deserialize('return { ["foo"] = "bar" }')`, returns `{foo='bar'}`
+ * Example: `deserialize('print("foo")')`, returns `nil` (function call fails)
+ * `error:[string "print("foo")"]:1: attempt to call global 'print' (a nil value)`
+* `minetest.compress(data, method, ...)`: returns `compressed_data`
+ * Compress a string of data.
+ * `method` is a string identifying the compression method to be used.
+ * Supported compression methods:
+ * Deflate (zlib): `"deflate"`
+ * `...` indicates method-specific arguments. Currently defined arguments are:
+ * Deflate: `level` - Compression level, `0`-`9` or `nil`.
+* `minetest.decompress(compressed_data, method, ...)`: returns data
+ * Decompress a string of data (using ZLib).
+ * See documentation on `minetest.compress()` for supported compression methods.
+ * currently supported.
+ * `...` indicates method-specific arguments. Currently, no methods use this.
+* `minetest.encode_base64(string)`: returns string encoded in base64
+ * Encodes a string in base64.
+* `minetest.decode_base64(string)`: returns string
+ * Decodes a string encoded in base64.
+* `minetest.is_protected(pos, name)`: returns boolean
+ * Returns true, if player `name` shouldn't be abled to dig at `pos` or do other
+ actions, defineable by mods, due to some mod-defined ownership-like concept.
+ Returns false or nil, if the player is allowed to do such actions.
+ * `name` will be "" for non-players or unknown players.
+ * This function should be overridden by protection mods and should be used to
+ check if a player can interact at a position.
+ * This function should call the old version of itself if the position is not
+ protected by the mod.
+ * Example:
+
+ local old_is_protected = minetest.is_protected
+ function minetest.is_protected(pos, name)
+ if mymod:position_protected_from(pos, name) then
+ return true
+ end
+ return old_is_protected(pos, name)
+ end
+* `minetest.record_protection_violation(pos, name)`
+ * This function calls functions registered with
+ `minetest.register_on_protection_violation`.
+* `minetest.rotate_and_place(itemstack, placer, pointed_thing[, infinitestacks,
+ orient_flags, prevent_after_place])`
+ * Attempt to predict the desired orientation of the facedir-capable node
+ defined by `itemstack`, and place it accordingly (on-wall, on the floor,
+ or hanging from the ceiling).
+ * `infinitestacks`: if `true`, the itemstack is not changed. Otherwise the
+ stacks are handled normally.
+ * `orient_flags`: Optional table containing extra tweaks to the placement code:
+ * `invert_wall`: if `true`, place wall-orientation on the ground and
+ ground-orientation on the wall.
+ * `force_wall` : if `true`, always place the node in wall orientation.
+ * `force_ceiling`: if `true`, always place on the ceiling.
+ * `force_floor`: if `true`, always place the node on the floor.
+ * `force_facedir`: if `true`, forcefully reset the facedir to north
+ when placing on the floor or ceiling.
+ * The first four options are mutually-exclusive; the last in the list
+ takes precedence over the first.
+ * `prevent_after_place` is directly passed to `minetest.item_place_node`
+ * Returns the new itemstack after placement
+* `minetest.rotate_node(itemstack, placer, pointed_thing)`
+ * calls `rotate_and_place()` with `infinitestacks` set according to the state
+ of the creative mode setting, checks for "sneak" to set the `invert_wall`
+ parameter and `prevent_after_place` set to `true`.
+
+* `minetest.forceload_block(pos[, transient])`
+ * forceloads the position `pos`.
+ * returns `true` if area could be forceloaded
+ * If `transient` is `false` or absent, the forceload will be persistent
+ (saved between server runs). If `true`, the forceload will be transient
+ (not saved between server runs).
+
+* `minetest.forceload_free_block(pos[, transient])`
+ * stops forceloading the position `pos`
+ * If `transient` is `false` or absent, frees a persistent forceload.
+ If `true`, frees a transient forceload.
+
+* `minetest.request_insecure_environment()`: returns an environment containing
+ insecure functions if the calling mod has been listed as trusted in the
+ `secure.trusted_mods` setting or security is disabled, otherwise returns `nil`.
+ * Only works at init time and must be called from the mod's main scope (not from a function).
+ * **DO NOT ALLOW ANY OTHER MODS TO ACCESS THE RETURNED ENVIRONMENT, STORE IT IN
+ A LOCAL VARIABLE!**
+
+* `minetest.global_exists(name)`
+ * Checks if a global variable has been set, without triggering a warning.
+
+### Global objects
+* `minetest.env`: `EnvRef` of the server environment and world.
+ * Any function in the minetest namespace can be called using the syntax
+ `minetest.env:somefunction(somearguments)`
+ instead of `minetest.somefunction(somearguments)`
+ * Deprecated, but support is not to be dropped soon
+
+### Global tables
+* `minetest.registered_items`
+ * Map of registered items, indexed by name
+* `minetest.registered_nodes`
+ * Map of registered node definitions, indexed by name
+* `minetest.registered_craftitems`
+ * Map of registered craft item definitions, indexed by name
+* `minetest.registered_tools`
+ * Map of registered tool definitions, indexed by name
+* `minetest.registered_entities`
+ * Map of registered entity prototypes, indexed by name
+* `minetest.object_refs`
+ * Map of object references, indexed by active object id
+* `minetest.luaentities`
+ * Map of Lua entities, indexed by active object id
+* `minetest.registered_chatcommands`
+ * Map of registered chat command definitions, indexed by name
+* `minetest.registered_ores`
+ * List of registered ore definitions.
+* `minetest.registered_biomes`
+ * List of registered biome definitions.
+* `minetest.registered_decorations`
+ * List of registered decoration definitions.
+
+Class reference
+---------------
+
+### `MetaDataRef`
+See `StorageRef`, `NodeMetaRef` and `ItemStackMetaRef`.
+
+#### Methods
+* `set_string(name, value)`
+* `get_string(name)`
+* `set_int(name, value)`
+* `get_int(name)`
+* `set_float(name, value)`
+* `get_float(name)`
+* `to_table()`: returns `nil` or a table with keys:
+ * `fields`: key-value storage
+ * `inventory`: `{list1 = {}, ...}}` (NodeMetaRef only)
+* `from_table(nil or {})`
+ * Any non-table value will clear the metadata
+ * See "Node Metadata" for an example
+ * returns `true` on success
+* `equals(other)`
+ * returns `true` if this metadata has the same key-value pairs as `other`
+
+### `NodeMetaRef`
+Node metadata: reference extra data and functionality stored in a node.
+Can be obtained via `minetest.get_meta(pos)`.
+
+#### Methods
+* All methods in MetaDataRef
+* `get_inventory()`: returns `InvRef`
+* `mark_as_private(name or {name1, name2, ...})`: Mark specific vars as private
+ This will prevent them from being sent to the client. Note that the "private"
+ status will only be remembered if an associated key-value pair exists, meaning
+ it's best to call this when initializing all other meta (e.g. `on_construct`).
+
+### `ItemStackMetaRef`
+ItemStack metadata: reference extra data and functionality stored in a stack.
+Can be obtained via `item:get_meta()`.
+
+#### Methods
+* All methods in MetaDataRef
+
+### `StorageRef`
+Mod metadata: per mod metadata, saved automatically.
+Can be obtained via `minetest.get_mod_storage()` during load time.
+
+#### Methods
+* All methods in MetaDataRef
+
+### `NodeTimerRef`
+Node Timers: a high resolution persistent per-node timer.
+Can be gotten via `minetest.get_node_timer(pos)`.
+
+#### Methods
+* `set(timeout,elapsed)`
+ * set a timer's state
+ * `timeout` is in seconds, and supports fractional values (0.1 etc)
+ * `elapsed` is in seconds, and supports fractional values (0.1 etc)
+ * will trigger the node's `on_timer` function after `(timeout - elapsed)` seconds
+* `start(timeout)`
+ * start a timer
+ * equivalent to `set(timeout,0)`
+* `stop()`
+ * stops the timer
+* `get_timeout()`: returns current timeout in seconds
+ * if `timeout` equals `0`, timer is inactive
+* `get_elapsed()`: returns current elapsed time in seconds
+ * the node's `on_timer` function will be called after `(timeout - elapsed)` seconds
+* `is_started()`: returns boolean state of timer
+ * returns `true` if timer is started, otherwise `false`
+
+### `ObjectRef`
+Moving things in the game are generally these.
+
+This is basically a reference to a C++ `ServerActiveObject`
+
+#### Methods
+* `remove()`: remove object (after returning from Lua)
+ * Note: Doesn't work on players, use `minetest.kick_player` instead
+* `get_pos()`: returns `{x=num, y=num, z=num}`
+* `set_pos(pos)`; `pos`=`{x=num, y=num, z=num}`
+* `move_to(pos, continuous=false)`: interpolated move
+* `punch(puncher, time_from_last_punch, tool_capabilities, direction)`
+ * `puncher` = another `ObjectRef`,
+ * `time_from_last_punch` = time since last punch action of the puncher
+ * `direction`: can be `nil`
+* `right_click(clicker)`; `clicker` is another `ObjectRef`
+* `get_hp()`: returns number of hitpoints (2 * number of hearts)
+* `set_hp(hp)`: set number of hitpoints (2 * number of hearts)
+* `get_inventory()`: returns an `InvRef`
+* `get_wield_list()`: returns the name of the inventory list the wielded item is in
+* `get_wield_index()`: returns the index of the wielded item
+* `get_wielded_item()`: returns an `ItemStack`
+* `set_wielded_item(item)`: replaces the wielded item, returns `true` if successful
+* `set_armor_groups({group1=rating, group2=rating, ...})`
+* `get_armor_groups()`: returns a table with the armor group ratings
+* `set_animation({x=1,y=1}, frame_speed=15, frame_blend=0, frame_loop=true)`
+* `get_animation()`: returns `range`, `frame_speed`, `frame_blend` and `frame_loop`
+* `set_attach(parent, bone, position, rotation)`
+ * `bone`: string
+ * `position`: `{x=num, y=num, z=num}` (relative)
+ * `rotation`: `{x=num, y=num, z=num}` = Rotation on each axis, in degrees
+* `get_attach()`: returns parent, bone, position, rotation or nil if it isn't attached
+* `set_detach()`
+* `set_bone_position(bone, position, rotation)`
+ * `bone`: string
+ * `position`: `{x=num, y=num, z=num}` (relative)
+ * `rotation`: `{x=num, y=num, z=num}`
+* `get_bone_position(bone)`: returns position and rotation of the bone
+* `set_properties(object property table)`
+* `get_properties()`: returns object property table
+* `is_player()`: returns true for players, false otherwise
+* `get_nametag_attributes()`
+ * returns a table with the attributes of the nametag of an object
+ * {
+ color = {a=0..255, r=0..255, g=0..255, b=0..255},
+ text = "",
+ }
+* `set_nametag_attributes(attributes)`
+ * sets the attributes of the nametag of an object
+ * `attributes`:
+ {
+ color = ColorSpec,
+ text = "My Nametag",
+ }
+
+##### LuaEntitySAO-only (no-op for other objects)
+* `set_velocity({x=num, y=num, z=num})`
+* `get_velocity()`: returns `{x=num, y=num, z=num}`
+* `set_acceleration({x=num, y=num, z=num})`
+* `get_acceleration()`: returns `{x=num, y=num, z=num}`
+* `set_yaw(radians)`
+* `get_yaw()`: returns number in radians
+* `set_texture_mod(mod)`
+* `get_texture_mod()` returns current texture modifier
+* `set_sprite(p={x=0,y=0}, num_frames=1, framelength=0.2,
+ select_horiz_by_yawpitch=false)`
+ * Select sprite from spritesheet with optional animation and DM-style
+ texture selection based on yaw relative to camera
+* `get_entity_name()` (**Deprecated**: Will be removed in a future version)
+* `get_luaentity()`
+
+##### Player-only (no-op for other objects)
+* `get_player_name()`: returns `""` if is not a player
+* `get_player_velocity()`: returns `nil` if is not a player, otherwise a
+ table {x, y, z} representing the player's instantaneous velocity in nodes/s
+* `get_look_dir()`: get camera direction as a unit vector
+* `get_look_vertical()`: pitch in radians
+ * Angle ranges between -pi/2 and pi/2, which are straight up and down respectively.
+* `get_look_horizontal()`: yaw in radians
+ * Angle is counter-clockwise from the +z direction.
+* `set_look_vertical(radians)`: sets look pitch
+ * radians - Angle from looking forward, where positive is downwards.
+* `set_look_horizontal(radians)`: sets look yaw
+ * radians - Angle from the +z direction, where positive is counter-clockwise.
+* `get_look_pitch()`: pitch in radians - Deprecated as broken. Use `get_look_vertical`.
+ * Angle ranges between -pi/2 and pi/2, which are straight down and up respectively.
+* `get_look_yaw()`: yaw in radians - Deprecated as broken. Use `get_look_horizontal`.
+ * Angle is counter-clockwise from the +x direction.
+* `set_look_pitch(radians)`: sets look pitch - Deprecated. Use `set_look_vertical`.
+* `set_look_yaw(radians)`: sets look yaw - Deprecated. Use `set_look_horizontal`.
+* `get_breath()`: returns players breath
+* `set_breath(value)`: sets players breath
+ * values:
+ * `0`: player is drowning,
+ * `1`-`10`: remaining number of bubbles
+ * `11`: bubbles bar is not shown
+* `set_attribute(attribute, value)`:
+ * Sets an extra attribute with value on player.
+ * `value` must be a string, or a number which will be converted to a string.
+ * If `value` is `nil`, remove attribute from player.
+* `get_attribute(attribute)`:
+ * Returns value (a string) for extra attribute.
+ * Returns `nil` if no attribute found.
+* `set_inventory_formspec(formspec)`
+ * Redefine player's inventory form
+ * Should usually be called in `on_joinplayer`
+* `get_inventory_formspec()`: returns a formspec string
+* `get_player_control()`: returns table with player pressed keys
+ * `{jump=bool,right=bool,left=bool,LMB=bool,RMB=bool,sneak=bool,aux1=bool,down=bool,up=bool}`
+* `get_player_control_bits()`: returns integer with bit packed player pressed keys
+ * bit nr/meaning: 0/up ,1/down ,2/left ,3/right ,4/jump ,5/aux1 ,6/sneak ,7/LMB ,8/RMB
+* `set_physics_override(override_table)`
+ * `override_table` is a table with the following fields:
+ * `speed`: multiplier to default walking speed value (default: `1`)
+ * `jump`: multiplier to default jump value (default: `1`)
+ * `gravity`: multiplier to default gravity value (default: `1`)
+ * `sneak`: whether player can sneak (default: `true`)
+ * `sneak_glitch`: whether player can use the new move code replications
+ of the old sneak side-effects: sneak ladders and 2 node sneak jump
+ (default: `false`)
+ * `new_move`: use new move/sneak code. When `false` the exact old code
+ is used for the specific old sneak behaviour (default: `true`)
+* `get_physics_override()`: returns the table given to `set_physics_override`
+* `hud_add(hud definition)`: add a HUD element described by HUD def, returns ID
+ number on success
+* `hud_remove(id)`: remove the HUD element of the specified id
+* `hud_change(id, stat, value)`: change a value of a previously added HUD element
+ * element `stat` values: `position`, `name`, `scale`, `text`, `number`, `item`, `dir`
+* `hud_get(id)`: gets the HUD element definition structure of the specified ID
+* `hud_set_flags(flags)`: sets specified HUD flags to `true`/`false`
+ * `flags`: (is visible) `hotbar`, `healthbar`, `crosshair`, `wielditem`, `minimap`
+ * pass a table containing a `true`/`false` value of each flag to be set or unset
+ * if a flag equals `nil`, the flag is not modified
+ * note that setting `minimap` modifies the client's permission to view the minimap -
+ * the client may locally elect to not view the minimap
+* `hud_get_flags()`: returns a table containing status of hud flags
+ * returns `{ hotbar=true, healthbar=true, crosshair=true, wielditem=true, breathbar=true, minimap=true }`
+* `hud_set_hotbar_itemcount(count)`: sets number of items in builtin hotbar
+ * `count`: number of items, must be between `1` and `23`
+* `hud_get_hotbar_itemcount`: returns number of visible items
+* `hud_set_hotbar_image(texturename)`
+ * sets background image for hotbar
+* `hud_get_hotbar_image`: returns texturename
+* `hud_set_hotbar_selected_image(texturename)`
+ * sets image for selected item of hotbar
+* `hud_get_hotbar_selected_image`: returns texturename
+* `set_sky(bgcolor, type, {texture names}, clouds)`
+ * `bgcolor`: ColorSpec, defaults to white
+ * `type`: Available types:
+ * `"regular"`: Uses 0 textures, `bgcolor` ignored
+ * `"skybox"`: Uses 6 textures, `bgcolor` used
+ * `"plain"`: Uses 0 textures, `bgcolor` used
+ * `clouds`: Boolean for whether clouds appear in front of `"skybox"` or
+ `"plain"` custom skyboxes (default: `true`)
+* `get_sky()`: returns bgcolor, type, table of textures, clouds
+* `set_clouds(parameters)`: set cloud parameters
+ * `parameters` is a table with the following optional fields:
+ * `density`: from `0` (no clouds) to `1` (full clouds) (default `0.4`)
+ * `color`: basic cloud color, with alpha channel (default `#fff0f0e5`)
+ * `ambient`: cloud color lower bound, use for a "glow at night" effect (default `#000000`)
+ * `height`: cloud height, i.e. y of cloud base (default per conf, usually `120`)
+ * `thickness`: cloud thickness in nodes (default `16`)
+ * `speed`: 2D cloud speed + direction in nodes per second (default `{x=0, y=-2}`)
+* `get_clouds()`: returns a table with the current cloud parameters as in `set_clouds`
+* `override_day_night_ratio(ratio or nil)`
+ * `0`...`1`: Overrides day-night ratio, controlling sunlight to a specific amount
+ * `nil`: Disables override, defaulting to sunlight based on day-night cycle
+* `get_day_night_ratio()`: returns the ratio or nil if it isn't overridden
+* `set_local_animation(stand/idle, walk, dig, walk+dig, frame_speed=frame_speed)`
+
+ set animation for player model in third person view
+
+ set_local_animation({x=0, y=79}, -- < stand/idle animation key frames
+ {x=168, y=187}, -- < walk animation key frames
+ {x=189, y=198}, -- < dig animation key frames
+ {x=200, y=219}, -- < walk+dig animation key frames
+ frame_speed=30): -- < animation frame speed
+* `get_local_animation()`: returns stand, walk, dig, dig+walk tables and `frame_speed`
+* `set_eye_offset({x=0,y=0,z=0},{x=0,y=0,z=0})`: defines offset value for camera per player
+ * in first person view
+ * in third person view (max. values `{x=-10/10,y=-10,15,z=-5/5}`)
+* `get_eye_offset()`: returns `offset_first` and `offset_third`
+
+### `InvRef`
+An `InvRef` is a reference to an inventory.
+
+#### Methods
+* `is_empty(listname)`: return `true` if list is empty
+* `get_size(listname)`: get size of a list
+* `set_size(listname, size)`: set size of a list
+ * returns `false` on error (e.g. invalid `listname` or `size`)
+* `get_width(listname)`: get width of a list
+* `set_width(listname, width)`: set width of list; currently used for crafting
+* `get_stack(listname, i)`: get a copy of stack index `i` in list
+* `set_stack(listname, i, stack)`: copy `stack` to index `i` in list
+* `get_list(listname)`: return full list
+* `set_list(listname, list)`: set full list (size will not change)
+* `get_lists()`: returns list of inventory lists
+* `set_lists(lists)`: sets inventory lists (size will not change)
+* `add_item(listname, stack)`: add item somewhere in list, returns leftover `ItemStack`
+* `room_for_item(listname, stack):` returns `true` if the stack of items
+ can be fully added to the list
+* `contains_item(listname, stack, [match_meta])`: returns `true` if
+ the stack of items can be fully taken from the list.
+ If `match_meta` is false, only the items' names are compared (default: `false`).
+* `remove_item(listname, stack)`: take as many items as specified from the list,
+ returns the items that were actually removed (as an `ItemStack`) -- note that
+ any item metadata is ignored, so attempting to remove a specific unique
+ item this way will likely remove the wrong one -- to do that use `set_stack`
+ with an empty `ItemStack`
+* `get_location()`: returns a location compatible to `minetest.get_inventory(location)`
+ * returns `{type="undefined"}` in case location is not known
+
+### `AreaStore`
+A fast access data structure to store areas, and find areas near a given position or area.
+Every area has a `data` string attribute to store additional information.
+You can create an empty `AreaStore` by calling `AreaStore()`, or `AreaStore(type_name)`.
+If you chose the parameter-less constructor, a fast implementation will be automatically
+chosen for you.
+
+#### Methods
+* `get_area(id, include_borders, include_data)`: returns the area with the id `id`.
+ (optional) Boolean values `include_borders` and `include_data` control what's copied.
+ Returns nil if specified area id does not exist.
+* `get_areas_for_pos(pos, include_borders, include_data)`: returns all areas that contain
+ the position `pos`. (optional) Boolean values `include_borders` and `include_data` control
+ what's copied.
+* `get_areas_in_area(edge1, edge2, accept_overlap, include_borders, include_data)`:
+ returns all areas that contain all nodes inside the area specified by `edge1` and `edge2` (inclusive).
+ If `accept_overlap` is true, also areas are returned that have nodes in common with the specified area.
+ (optional) Boolean values `include_borders` and `include_data` control what's copied.
+* `insert_area(edge1, edge2, data, [id])`: inserts an area into the store. Returns the new area's ID,
+ or nil if the insertion failed. The (inclusive) positions `edge1` and `edge2` describe the area.
+ `data` is a string stored with the area. If passed, `id` will be used as the internal area ID,
+ it must be a unique number between 0 and 2^32-2. If you use the `id` parameter you must always use it,
+ or insertions are likely to fail due to conflicts.
+* `reserve(count)`: reserves resources for at most `count` many contained areas.
+ Only needed for efficiency, and only some implementations profit.
+* `remove_area(id)`: removes the area with the given id from the store, returns success.
+* `set_cache_params(params)`: sets params for the included prefiltering cache.
+ Calling invalidates the cache, so that its elements have to be newly generated.
+ * `params`:
+ {
+ enabled = boolean, -- whether to enable, default true
+ block_radius = number, -- the radius (in nodes) of the areas the cache generates
+ prefiltered lists for, minimum 16, default 64
+ limit = number, -- the cache's size, minimum 20, default 1000
+ }
+* `to_string()`: Experimental. Returns area store serialized as a (binary) string.
+* `to_file(filename)`: Experimental. Like `to_string()`, but writes the data to a file.
+* `from_string(str)`: Experimental. Deserializes string and loads it into the AreaStore.
+ Returns success and, optionally, an error message.
+* `from_file(filename)`: Experimental. Like `from_string()`, but reads the data from a file.
+
+### `ItemStack`
+An `ItemStack` is a stack of items.
+
+It can be created via `ItemStack(x)`, where x is an `ItemStack`,
+an itemstring, a table or `nil`.
+
+#### Methods
+* `is_empty()`: Returns `true` if stack is empty.
+* `get_name()`: Returns item name (e.g. `"default:stone"`).
+* `set_name(item_name)`: Returns boolean whether item was cleared
+* `get_count()`: Returns number of items on the stack.
+* `set_count(count)`: Returns boolean whether item was cleared
+* `get_wear()`: Returns tool wear (`0`-`65535`), `0` for non-tools.
+* `set_wear(wear)`: Returns boolean whether item was cleared
+* `get_meta()`: Returns ItemStackMetaRef. See section for more details
+* `get_metadata()`: (DEPRECATED) Returns metadata (a string attached to an item stack).
+* `set_metadata(metadata)`: (DEPRECATED) Returns true.
+* `clear()`: removes all items from the stack, making it empty.
+* `replace(item)`: replace the contents of this stack.
+ * `item` can also be an itemstring or table.
+* `to_string()`: Returns the stack in itemstring form.
+* `to_table()`: Returns the stack in Lua table form.
+* `get_stack_max()`: Returns the maximum size of the stack (depends on the item).
+* `get_free_space()`: Returns `get_stack_max() - get_count()`.
+* `is_known()`: Returns `true` if the item name refers to a defined item type.
+* `get_definition()`: Returns the item definition table.
+* `get_tool_capabilities()`: Returns the digging properties of the item,
+ or those of the hand if none are defined for this item type
+* `add_wear(amount)`: Increases wear by `amount` if the item is a tool.
+* `add_item(item)`: Put some item or stack onto this stack.
+ Returns leftover `ItemStack`.
+* `item_fits(item)`: Returns `true` if item or stack can be fully added to
+ this one.
+* `take_item(n=1)`: Take (and remove) up to `n` items from this stack.
+ Returns taken `ItemStack`.
+* `peek_item(n=1)`: copy (don't remove) up to `n` items from this stack.
+ Returns taken `ItemStack`.
+
+### `PseudoRandom`
+A 16-bit pseudorandom number generator.
+Uses a well-known LCG algorithm introduced by K&R.
+
+It can be created via `PseudoRandom(seed)`.
+
+#### Methods
+* `next()`: return next integer random number [`0`...`32767`]
+* `next(min, max)`: return next integer random number [`min`...`max`]
+ * `((max - min) == 32767) or ((max-min) <= 6553))` must be true
+ due to the simple implementation making bad distribution otherwise.
+
+### `PcgRandom`
+A 32-bit pseudorandom number generator.
+Uses PCG32, an algorithm of the permuted congruential generator family, offering very strong randomness.
+
+It can be created via `PcgRandom(seed)` or `PcgRandom(seed, sequence)`.
+
+#### Methods
+* `next()`: return next integer random number [`-2147483648`...`2147483647`]
+* `next(min, max)`: return next integer random number [`min`...`max`]
+* `rand_normal_dist(min, max, num_trials=6)`: return normally distributed random number [`min`...`max`]
+ * This is only a rough approximation of a normal distribution with:
+ * `mean = (max - min) / 2`, and
+ * `variance = (((max - min + 1) ^ 2) - 1) / (12 * num_trials)`
+ * Increasing `num_trials` improves accuracy of the approximation
+
+### `SecureRandom`
+Interface for the operating system's crypto-secure PRNG.
+
+It can be created via `SecureRandom()`. The constructor returns nil if a secure random device cannot be
+be found on the system.
+
+#### Methods
+* `next_bytes([count])`: return next `count` (default 1, capped at 2048) many random bytes, as a string.
+
+### `PerlinNoise`
+A perlin noise generator.
+It can be created via `PerlinNoise(seed, octaves, persistence, scale)`
+or `PerlinNoise(noiseparams)`.
+Alternatively with `minetest.get_perlin(seeddiff, octaves, persistence, scale)`
+or `minetest.get_perlin(noiseparams)`.
+
+#### Methods
+* `get2d(pos)`: returns 2D noise value at `pos={x=,y=}`
+* `get3d(pos)`: returns 3D noise value at `pos={x=,y=,z=}`
+
+### `PerlinNoiseMap`
+A fast, bulk perlin noise generator.
+
+It can be created via `PerlinNoiseMap(noiseparams, size)` or
+`minetest.get_perlin_map(noiseparams, size)`.
+
+Format of `size` is `{x=dimx, y=dimy, z=dimz}`. The `z` conponent is ommitted
+for 2D noise, and it must be must be larger than 1 for 3D noise (otherwise
+`nil` is returned).
+
+For each of the functions with an optional `buffer` parameter: If `buffer` is not
+nil, this table will be used to store the result instead of creating a new table.
+
+
+#### Methods
+* `get2dMap(pos)`: returns a `<size.x>` times `<size.y>` 2D array of 2D noise
+ with values starting at `pos={x=,y=}`
+* `get3dMap(pos)`: returns a `<size.x>` times `<size.y>` times `<size.z>` 3D array
+ of 3D noise with values starting at `pos={x=,y=,z=}`
+* `get2dMap_flat(pos, buffer)`: returns a flat `<size.x * size.y>` element array of 2D noise
+ with values starting at `pos={x=,y=}`
+* `get3dMap_flat(pos, buffer)`: Same as `get2dMap_flat`, but 3D noise
+* `calc2dMap(pos)`: Calculates the 2d noise map starting at `pos`. The result is stored internally.
+* `calc3dMap(pos)`: Calculates the 3d noise map starting at `pos`. The result is stored internally.
+* `getMapSlice(slice_offset, slice_size, buffer)`: In the form of an array, returns a slice of the
+ most recently computed noise results. The result slice begins at coordinates `slice_offset` and
+ takes a chunk of `slice_size`.
+ E.g. to grab a 2-slice high horizontal 2d plane of noise starting at buffer offset y = 20:
+ `noisevals = noise:getMapSlice({y=20}, {y=2})`
+ It is important to note that `slice_offset` offset coordinates begin at 1, and are relative to
+ the starting position of the most recently calculated noise.
+ To grab a single vertical column of noise starting at map coordinates x = 1023, y=1000, z = 1000:
+ `noise:calc3dMap({x=1000, y=1000, z=1000})`
+ `noisevals = noise:getMapSlice({x=24, z=1}, {x=1, z=1})`
+
+### `VoxelManip`
+
+#### About VoxelManip
+VoxelManip is a scripting interface to the internal 'Map Voxel Manipulator' facility. The purpose of
+this object is for fast, low-level, bulk access to reading and writing Map content. As such, setting
+map nodes through VoxelManip will lack many of the higher level features and concepts you may be used
+to with other methods of setting nodes. For example, nodes will not have their construction and
+destruction callbacks run, and no rollback information is logged.
+
+It is important to note that VoxelManip is designed for speed, and *not* ease of use or flexibility.
+If your mod requires a map manipulation facility that will handle 100% of all edge cases, or the use
+of high level node placement features, perhaps `minetest.set_node()` is better suited for the job.
+
+In addition, VoxelManip might not be faster, or could even be slower, for your specific use case.
+VoxelManip is most effective when setting very large areas of map at once - for example, if only
+setting a 5x5x5 node area, a `minetest.set_node()` loop may be more optimal. Always profile code
+using both methods of map manipulation to determine which is most appropriate for your usage.
+
+#### Using VoxelManip
+A VoxelManip object can be created any time using either:
+`VoxelManip([p1, p2])`, or `minetest.get_voxel_manip([p1, p2])`.
+
+If the optional position parameters are present for either of these routines, the specified region
+will be pre-loaded into the VoxelManip object on creation. Otherwise, the area of map you wish to
+manipulate must first be loaded into the VoxelManip object using `VoxelManip:read_from_map()`.
+
+Note that `VoxelManip:read_from_map()` returns two position vectors. The region formed by these
+positions indicate the minimum and maximum (respectively) positions of the area actually loaded in
+the VoxelManip, which may be larger than the area requested. For convenience, the loaded area
+coordinates can also be queried any time after loading map data with `VoxelManip:get_emerged_area()`.
+
+Now that the VoxelManip object is populated with map data, your mod can fetch a copy of this data
+using either of two methods. `VoxelManip:get_node_at()`, which retrieves an individual node in a
+MapNode formatted table at the position requested is the simplest method to use, but also the slowest.
+
+Nodes in a VoxelManip object may also be read in bulk to a flat array table using:
+`VoxelManip:get_data()` for node content (in Content ID form, see section 'Content IDs'),
+`VoxelManip:get_light_data()` for node light levels, and
+`VoxelManip:get_param2_data()` for the node type-dependent "param2" values.
+
+See section 'Flat array format' for more details.
+
+It is very important to understand that the tables returned by any of the above three functions
+represent a snapshot of the VoxelManip's internal state at the time of the call. This copy of the
+data will *not* magically update itself if another function modifies the internal VoxelManip state.
+Any functions that modify a VoxelManip's contents work on the VoxelManip's internal state unless
+otherwise explicitly stated.
+
+Once the bulk data has been edited to your liking, the internal VoxelManip state can be set using:
+`VoxelManip:set_data()` for node content (in Content ID form, see section 'Content IDs'),
+`VoxelManip:set_light_data()` for node light levels, and
+`VoxelManip:set_param2_data()` for the node type-dependent `param2` values.
+
+The parameter to each of the above three functions can use any table at all in the same flat array
+format as produced by `get_data()` et al. and is *not required* to be a table retrieved from `get_data()`.
+
+Once the internal VoxelManip state has been modified to your liking, the changes can be committed back
+to the map by calling `VoxelManip:write_to_map()`.
+
+
+##### Flat array format
+Let
+ `Nx = p2.X - p1.X + 1`,
+ `Ny = p2.Y - p1.Y + 1`, and
+ `Nz = p2.Z - p1.Z + 1`.
+
+Then, for a loaded region of p1..p2, this array ranges from `1` up to and including the value of
+the expression `Nx * Ny * Nz`.
+
+Positions offset from p1 are present in the array with the format of:
+
+```
+[
+ (0, 0, 0), (1, 0, 0), (2, 0, 0), ... (Nx, 0, 0),
+ (0, 1, 0), (1, 1, 0), (2, 1, 0), ... (Nx, 1, 0),
+ ...
+ (0, Ny, 0), (1, Ny, 0), (2, Ny, 0), ... (Nx, Ny, 0),
+ (0, 0, 1), (1, 0, 1), (2, 0, 1), ... (Nx, 0, 1),
+ ...
+ (0, Ny, 2), (1, Ny, 2), (2, Ny, 2), ... (Nx, Ny, 2),
+ ...
+ (0, Ny, Nz), (1, Ny, Nz), (2, Ny, Nz), ... (Nx, Ny, Nz)
+]
+```
+
+and the array index for a position p contained completely in p1..p2 is:
+
+`(p.Z - p1.Z) * Ny * Nx + (p.Y - p1.Y) * Nx + (p.X - p1.X) + 1`
+
+Note that this is the same "flat 3D array" format as `PerlinNoiseMap:get3dMap_flat()`.
+VoxelArea objects (see section 'VoxelArea') can be used to simplify calculation of the index
+for a single point in a flat VoxelManip array.
+
+##### Content IDs
+A Content ID is a unique integer identifier for a specific node type. These IDs are used by VoxelManip
+in place of the node name string for `VoxelManip:get_data()` and `VoxelManip:set_data()`. You can use
+`minetest.get_content_id()` to look up the Content ID for the specified node name, and
+`minetest.get_name_from_content_id()` to look up the node name string for a given Content ID.
+After registration of a node, its Content ID will remain the same throughout execution of the mod.
+Note that the node being queried needs to have already been been registered.
+
+The following builtin node types have their Content IDs defined as constants:
+
+* `minetest.CONTENT_UNKNOWN`: ID for "unknown" nodes
+* `minetest.CONTENT_AIR`: ID for "air" nodes
+* `minetest.CONTENT_IGNORE`: ID for "ignore" nodes
+
+##### Mapgen VoxelManip objects
+Inside of `on_generated()` callbacks, it is possible to retrieve the same VoxelManip object used by the
+core's Map Generator (commonly abbreviated Mapgen). Most of the rules previously described still apply
+but with a few differences:
+
+* The Mapgen VoxelManip object is retrieved using: `minetest.get_mapgen_object("voxelmanip")`
+* This VoxelManip object already has the region of map just generated loaded into it; it's not necessary
+ to call `VoxelManip:read_from_map()` before using a Mapgen VoxelManip.
+* The `on_generated()` callbacks of some mods may place individual nodes in the generated area using
+ non-VoxelManip map modification methods. Because the same Mapgen VoxelManip object is passed through
+ each `on_generated()` callback, it becomes necessary for the Mapgen VoxelManip object to maintain
+ consistency with the current map state. For this reason, calling any of the following functions:
+ `minetest.add_node()`, `minetest.set_node()`, or `minetest.swap_node()`
+ will also update the Mapgen VoxelManip object's internal state active on the current thread.
+* After modifying the Mapgen VoxelManip object's internal buffer, it may be necessary to update lighting
+ information using either: `VoxelManip:calc_lighting()` or `VoxelManip:set_lighting()`.
+
+##### Other API functions operating on a VoxelManip
+If any VoxelManip contents were set to a liquid node, `VoxelManip:update_liquids()` must be called
+for these liquid nodes to begin flowing. It is recommended to call this function only after having
+written all buffered data back to the VoxelManip object, save for special situations where the modder
+desires to only have certain liquid nodes begin flowing.
+
+The functions `minetest.generate_ores()` and `minetest.generate_decorations()` will generate all
+registered decorations and ores throughout the full area inside of the specified VoxelManip object.
+
+`minetest.place_schematic_on_vmanip()` is otherwise identical to `minetest.place_schematic()`,
+except instead of placing the specified schematic directly on the map at the specified position, it
+will place the schematic inside of the VoxelManip.
+
+##### Notes
+* Attempting to read data from a VoxelManip object before map is read will result in a zero-length
+ array table for `VoxelManip:get_data()`, and an "ignore" node at any position for
+ `VoxelManip:get_node_at()`.
+* If either a region of map has not yet been generated or is out-of-bounds of the map, that region is
+ filled with "ignore" nodes.
+* Other mods, or the core itself, could possibly modify the area of map currently loaded into a VoxelManip
+ object. With the exception of Mapgen VoxelManips (see above section), the internal buffers are not
+ updated. For this reason, it is strongly encouraged to complete the usage of a particular VoxelManip
+ object in the same callback it had been created.
+* If a VoxelManip object will be used often, such as in an `on_generated()` callback, consider passing
+ a file-scoped table as the optional parameter to `VoxelManip:get_data()`, which serves as a static
+ buffer the function can use to write map data to instead of returning a new table each call. This
+ greatly enhances performance by avoiding unnecessary memory allocations.
+
+#### Methods
+* `read_from_map(p1, p2)`: Loads a chunk of map into the VoxelManip object containing
+ the region formed by `p1` and `p2`.
+ * returns actual emerged `pmin`, actual emerged `pmax`
+* `write_to_map([light])`: Writes the data loaded from the `VoxelManip` back to the map.
+ * **important**: data must be set using `VoxelManip:set_data()` before calling this
+ * if `light` is true, then lighting is automatically recalculated.
+ The default value is true.
+ If `light` is false, no light calculations happen, and you should correct
+ all modified blocks with `minetest.fix_light()` as soon as possible.
+ Keep in mind that modifying the map where light is incorrect can cause
+ more lighting bugs.
+* `get_node_at(pos)`: Returns a `MapNode` table of the node currently loaded in
+ the `VoxelManip` at that position
+* `set_node_at(pos, node)`: Sets a specific `MapNode` in the `VoxelManip` at that position
+* `get_data([buffer])`: Retrieves the node content data loaded into the `VoxelManip` object
+ * returns raw node data in the form of an array of node content IDs
+ * if the param `buffer` is present, this table will be used to store the result instead
+* `set_data(data)`: Sets the data contents of the `VoxelManip` object
+* `update_map()`: Does nothing, kept for compatibility.
+* `set_lighting(light, [p1, p2])`: Set the lighting within the `VoxelManip` to a uniform value
+ * `light` is a table, `{day=<0...15>, night=<0...15>}`
+ * To be used only by a `VoxelManip` object from `minetest.get_mapgen_object`
+ * (`p1`, `p2`) is the area in which lighting is set;
+ defaults to the whole area if left out
+* `get_light_data()`: Gets the light data read into the `VoxelManip` object
+ * Returns an array (indices 1 to volume) of integers ranging from `0` to `255`
+ * Each value is the bitwise combination of day and night light values (`0` to `15` each)
+ * `light = day + (night * 16)`
+* `set_light_data(light_data)`: Sets the `param1` (light) contents of each node
+ in the `VoxelManip`
+ * expects lighting data in the same format that `get_light_data()` returns
+* `get_param2_data([buffer])`: Gets the raw `param2` data read into the `VoxelManip` object
+ * Returns an array (indices 1 to volume) of integers ranging from `0` to `255`
+ * If the param `buffer` is present, this table will be used to store the result instead
+* `set_param2_data(param2_data)`: Sets the `param2` contents of each node in the `VoxelManip`
+* `calc_lighting([p1, p2], [propagate_shadow])`: Calculate lighting within the `VoxelManip`
+ * To be used only by a `VoxelManip` object from `minetest.get_mapgen_object`
+ * (`p1`, `p2`) is the area in which lighting is set; defaults to the whole area
+ if left out or nil
+ * `propagate_shadow` is an optional boolean deciding whether shadows in a generated
+ mapchunk above are propagated down into the mapchunk; defaults to `true` if left out
+* `update_liquids()`: Update liquid flow
+* `was_modified()`: Returns `true` or `false` if the data in the voxel manipulator
+ had been modified since the last read from map, due to a call to
+ `minetest.set_data()` on the loaded area elsewhere
+* `get_emerged_area()`: Returns actual emerged minimum and maximum positions.
+
+### `VoxelArea`
+A helper class for voxel areas.
+It can be created via `VoxelArea:new{MinEdge=pmin, MaxEdge=pmax}`.
+The coordinates are *inclusive*, like most other things in Minetest.
+
+#### Methods
+* `getExtent()`: returns a 3D vector containing the size of the area formed by
+ `MinEdge` and `MaxEdge`
+* `getVolume()`: returns the volume of the area formed by `MinEdge` and `MaxEdge`
+* `index(x, y, z)`: returns the index of an absolute position in a flat array starting at `1`
+ * useful for things like `VoxelManip`, raw Schematic specifiers,
+ `PerlinNoiseMap:get2d`/`3dMap`, and so on
+* `indexp(p)`: same as above, except takes a vector
+* `position(i)`: returns the absolute position vector corresponding to index `i`
+* `contains(x, y, z)`: check if (`x`,`y`,`z`) is inside area formed by `MinEdge` and `MaxEdge`
+* `containsp(p)`: same as above, except takes a vector
+* `containsi(i)`: same as above, except takes an index `i`
+* `iter(minx, miny, minz, maxx, maxy, maxz)`: returns an iterator that returns indices
+ * from (`minx`,`miny`,`minz`) to (`maxx`,`maxy`,`maxz`) in the order of `[z [y [x]]]`
+* `iterp(minp, maxp)`: same as above, except takes a vector
+
+### `Settings`
+An interface to read config files in the format of `minetest.conf`.
+
+It can be created via `Settings(filename)`.
+
+#### Methods
+* `get(key)`: returns a value
+* `get_bool(key)`: returns a boolean
+* `set(key, value)`
+ * Setting names can't contain whitespace or any of `="{}#`.
+ * Setting values can't contain the sequence `\n"""`.
+ * Setting names starting with "secure." can't be set on the main settings object (`minetest.settings`).
+* `set_bool(key, value)`
+ * See documentation for set() above.
+* `remove(key)`: returns a boolean (`true` for success)
+* `get_names()`: returns `{key1,...}`
+* `write()`: returns a boolean (`true` for success)
+ * Writes changes to file.
+* `to_table()`: returns `{[key1]=value1,...}`
+
+Mapgen objects
+--------------
+A mapgen object is a construct used in map generation. Mapgen objects can be used
+by an `on_generate` callback to speed up operations by avoiding unnecessary
+recalculations; these can be retrieved using the `minetest.get_mapgen_object()`
+function. If the requested Mapgen object is unavailable, or `get_mapgen_object()`
+was called outside of an `on_generate()` callback, `nil` is returned.
+
+The following Mapgen objects are currently available:
+
+### `voxelmanip`
+This returns three values; the `VoxelManip` object to be used, minimum and maximum
+emerged position, in that order. All mapgens support this object.
+
+### `heightmap`
+Returns an array containing the y coordinates of the ground levels of nodes in
+the most recently generated chunk by the current mapgen.
+
+### `biomemap`
+Returns an array containing the biome IDs of nodes in the most recently
+generated chunk by the current mapgen.
+
+### `heatmap`
+Returns an array containing the temperature values of nodes in the most
+recently generated chunk by the current mapgen.
+
+### `humiditymap`
+Returns an array containing the humidity values of nodes in the most recently
+generated chunk by the current mapgen.
+
+### `gennotify`
+Returns a table mapping requested generation notification types to arrays of
+positions at which the corresponding generated structures are located at within
+the current chunk. To set the capture of positions of interest to be recorded
+on generate, use `minetest.set_gen_notify()`.
+
+Possible fields of the table returned are:
+
+* `dungeon`
+* `temple`
+* `cave_begin`
+* `cave_end`
+* `large_cave_begin`
+* `large_cave_end`
+* `decoration`
+
+Decorations have a key in the format of `"decoration#id"`, where `id` is the
+numeric unique decoration ID.
+
+Registered entities
+-------------------
+* Functions receive a "luaentity" as `self`:
+ * It has the member `.name`, which is the registered name `("mod:thing")`
+ * It has the member `.object`, which is an `ObjectRef` pointing to the object
+ * The original prototype stuff is visible directly via a metatable
+* Callbacks:
+ * `on_activate(self, staticdata, dtime_s)`
+ * Called when the object is instantiated.
+ * `dtime_s` is the time passed since the object was unloaded, which can
+ be used for updating the entity state.
+ * `on_step(self, dtime)`
+ * Called on every server tick, after movement and collision processing.
+ `dtime` is usually 0.1 seconds, as per the `dedicated_server_step` setting
+ `in minetest.conf`.
+ * `on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir)`
+ * Called when somebody punches the object.
+ * Note that you probably want to handle most punches using the
+ automatic armor group system.
+ * `puncher`: an `ObjectRef` (can be `nil`)
+ * `time_from_last_punch`: Meant for disallowing spamming of clicks (can be `nil`)
+ * `tool_capabilities`: capability table of used tool (can be `nil`)
+ * `dir`: unit vector of direction of punch. Always defined. Points from
+ the puncher to the punched.
+ * `on_rightclick(self, clicker)`
+ * `get_staticdata(self)`
+ * Should return a string that will be passed to `on_activate` when
+ the object is instantiated the next time.
+
+L-system trees
+--------------
+**Warning**
+L-system generation currently creates lighting bugs in the form of mapblock-sized shadows.
+Often these bugs appear as subtle shadows in water.
+
+### Tree definition
+
+ treedef={
+ axiom, --string initial tree axiom
+ rules_a, --string rules set A
+ rules_b, --string rules set B
+ rules_c, --string rules set C
+ rules_d, --string rules set D
+ trunk, --string trunk node name
+ leaves, --string leaves node name
+ leaves2, --string secondary leaves node name
+ leaves2_chance,--num chance (0-100) to replace leaves with leaves2
+ angle, --num angle in deg
+ iterations, --num max # of iterations, usually 2 -5
+ random_level, --num factor to lower nr of iterations, usually 0 - 3
+ trunk_type, --string single/double/crossed) type of trunk: 1 node,
+ -- 2x2 nodes or 3x3 in cross shape
+ thin_branches, --boolean true -> use thin (1 node) branches
+ fruit, --string fruit node name
+ fruit_chance, --num chance (0-100) to replace leaves with fruit node
+ seed, --num random seed; if no seed is provided, the engine will create one
+ }
+
+### Key for Special L-System Symbols used in Axioms
+
+* `G`: move forward one unit with the pen up
+* `F`: move forward one unit with the pen down drawing trunks and branches
+* `f`: move forward one unit with the pen down drawing leaves (100% chance)
+* `T`: move forward one unit with the pen down drawing trunks only
+* `R`: move forward one unit with the pen down placing fruit
+* `A`: replace with rules set A
+* `B`: replace with rules set B
+* `C`: replace with rules set C
+* `D`: replace with rules set D
+* `a`: replace with rules set A, chance 90%
+* `b`: replace with rules set B, chance 80%
+* `c`: replace with rules set C, chance 70%
+* `d`: replace with rules set D, chance 60%
+* `+`: yaw the turtle right by `angle` parameter
+* `-`: yaw the turtle left by `angle` parameter
+* `&`: pitch the turtle down by `angle` parameter
+* `^`: pitch the turtle up by `angle` parameter
+* `/`: roll the turtle to the right by `angle` parameter
+* `*`: roll the turtle to the left by `angle` parameter
+* `[`: save in stack current state info
+* `]`: recover from stack state info
+
+### Example
+Spawn a small apple tree:
+
+ pos = {x=230,y=20,z=4}
+ apple_tree={
+ axiom="FFFFFAFFBF",
+ rules_a="[&&&FFFFF&&FFFF][&&&++++FFFFF&&FFFF][&&&----FFFFF&&FFFF]",
+ rules_b="[&&&++FFFFF&&FFFF][&&&--FFFFF&&FFFF][&&&------FFFFF&&FFFF]",
+ trunk="default:tree",
+ leaves="default:leaves",
+ angle=30,
+ iterations=2,
+ random_level=0,
+ trunk_type="single",
+ thin_branches=true,
+ fruit_chance=10,
+ fruit="default:apple"
+ }
+ minetest.spawn_tree(pos,apple_tree)
+
+Definition tables
+-----------------
+
+### Object Properties
+
+ {
+ hp_max = 1,
+ physical = true,
+ collide_with_objects = true, -- collide with other objects if physical = true
+ weight = 5,
+ collisionbox = {-0.5, 0.0, -0.5, 0.5, 1.0, 0.5},
+ visual = "cube" / "sprite" / "upright_sprite" / "mesh" / "wielditem",
+ visual_size = {x = 1, y = 1},
+ mesh = "model",
+ textures = {}, -- number of required textures depends on visual
+ colors = {}, -- number of required colors depends on visual
+ spritediv = {x = 1, y = 1},
+ initial_sprite_basepos = {x = 0, y = 0},
+ is_visible = true,
+ makes_footstep_sound = false,
+ automatic_rotate = false,
+ stepheight = 0,
+ automatic_face_movement_dir = 0.0,
+ -- ^ Automatically set yaw to movement direction, offset in degrees,
+ -- 'false' to disable.
+ automatic_face_movement_max_rotation_per_sec = -1,
+ -- ^ Limit automatic rotation to this value in degrees per second,
+ -- value < 0 no limit.
+ backface_culling = true, -- false to disable backface_culling for model
+ nametag = "", -- by default empty, for players their name is shown if empty
+ nametag_color = <color>, -- sets color of nametag as ColorSpec
+ infotext = "", -- by default empty, text to be shown when pointed at object
+ static_save = true,
+ -- ^ If false, never save this object statically. It will simply be deleted when the block gets unloaded.
+ -- ^ The get_staticdata() callback is never called then.
+ -- ^ Defaults to 'true'
+ }
+
+### Entity definition (`register_entity`)
+
+ {
+ -- Deprecated: Everything in object properties is read directly from here
+
+ initial_properties = --[[<initial object properties>]],
+
+ on_activate = function(self, staticdata, dtime_s),
+ on_step = function(self, dtime),
+ on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir),
+ on_rightclick = function(self, clicker),
+ get_staticdata = function(self),
+ -- ^ Called sometimes; the string returned is passed to on_activate when
+ -- the entity is re-activated from static state
+
+ -- Also you can define arbitrary member variables here (see item definition for
+ -- more info)
+ _custom_field = whatever,
+ }
+
+### ABM (ActiveBlockModifier) definition (`register_abm`)
+
+ {
+ label = "Lava cooling",
+ -- ^ Descriptive label for profiling purposes (optional).
+ -- Definitions with identical labels will be listed as one.
+ -- In the following two fields, also group:groupname will work.
+ nodenames = {"default:lava_source"},
+ neighbors = {"default:water_source", "default:water_flowing"}, -- Any of these --[[
+ ^ If left out or empty, any neighbor will do ]]
+ interval = 1.0, -- Operation interval in seconds
+ chance = 1, -- Chance of trigger per-node per-interval is 1.0 / this
+ catch_up = true, -- If true, catch-up behaviour is enabled --[[
+ ^ The chance value is temporarily reduced when returning to
+ an area to simulate time lost by the area being unattended.
+ ^ Note chance value can often be reduced to 1 ]]
+ action = func(pos, node, active_object_count, active_object_count_wider),
+ }
+
+### LBM (LoadingBlockModifier) definition (`register_lbm`)
+
+ {
+ label = "Upgrade legacy doors",
+ -- ^ Descriptive label for profiling purposes (optional).
+ -- Definitions with identical labels will be listed as one.
+ name = "modname:replace_legacy_door",
+ nodenames = {"default:lava_source"},
+ -- ^ List of node names to trigger the LBM on.
+ -- Also non-registered nodes will work.
+ -- Groups (as of group:groupname) will work as well.
+ run_at_every_load = false,
+ -- ^ Whether to run the LBM's action every time a block gets loaded,
+ -- and not just for blocks that were saved last time before LBMs were
+ -- introduced to the world.
+ action = func(pos, node),
+ }
+
+### Item definition (`register_node`, `register_craftitem`, `register_tool`)
+
+ {
+ description = "Steel Axe",
+ groups = {}, -- key = name, value = rating; rating = 1..3.
+ if rating not applicable, use 1.
+ e.g. {wool = 1, fluffy = 3}
+ {soil = 2, outerspace = 1, crumbly = 1}
+ {bendy = 2, snappy = 1},
+ {hard = 1, metal = 1, spikes = 1}
+ inventory_image = "default_tool_steelaxe.png",
+ wield_image = "",
+ palette = "",
+ --[[
+ ^ An image file containing the palette of a node.
+ ^ You can set the currently used color as the
+ ^ "palette_index" field of the item stack metadata.
+ ^ The palette is always stretched to fit indices
+ ^ between 0 and 255, to ensure compatibility with
+ ^ "colorfacedir" and "colorwallmounted" nodes.
+ ]]
+ color = "0xFFFFFFFF",
+ --[[
+ ^ The color of the item. The palette overrides this.
+ ]]
+ wield_scale = {x = 1, y = 1, z = 1},
+ stack_max = 99,
+ range = 4.0,
+ liquids_pointable = false,
+ tool_capabilities = {
+ full_punch_interval = 1.0,
+ max_drop_level = 0,
+ groupcaps = {
+ -- For example:
+ choppy = {times = {[1] = 2.50, [2] = 1.40, [3] = 1.00}, uses = 20, maxlevel = 2},
+ },
+ damage_groups = {groupname = damage},
+ },
+ node_placement_prediction = nil,
+ --[[
+ ^ If nil and item is node, prediction is made automatically
+ ^ If nil and item is not a node, no prediction is made
+ ^ If "" and item is anything, no prediction is made
+ ^ Otherwise should be name of node which the client immediately places
+ on ground when the player places the item. Server will always update
+ actual result to client in a short moment.
+ ]]
+ sound = {
+ breaks = "default_tool_break", -- tools only
+ place = --[[<SimpleSoundSpec>]],
+ },
+
+ on_place = func(itemstack, placer, pointed_thing),
+ --[[
+ ^ Shall place item and return the leftover itemstack
+ ^ The placer may be any ObjectRef or nil.
+ ^ default: minetest.item_place ]]
+ on_secondary_use = func(itemstack, user, pointed_thing),
+ --[[
+ ^ Same as on_place but called when pointing at nothing.
+ ^ The user may be any ObjectRef or nil.
+ ^ pointed_thing : always { type = "nothing" }
+ ]]
+ on_drop = func(itemstack, dropper, pos),
+ --[[
+ ^ Shall drop item and return the leftover itemstack
+ ^ The dropper may be any ObjectRef or nil.
+ ^ default: minetest.item_drop ]]
+ on_use = func(itemstack, user, pointed_thing),
+ --[[
+ ^ default: nil
+ ^ Function must return either nil if no item shall be removed from
+ inventory, or an itemstack to replace the original itemstack.
+ e.g. itemstack:take_item(); return itemstack
+ ^ Otherwise, the function is free to do what it wants.
+ ^ The user may be any ObjectRef or nil.
+ ^ The default functions handle regular use cases.
+ ]]
+ after_use = func(itemstack, user, node, digparams),
+ --[[
+ ^ default: nil
+ ^ If defined, should return an itemstack and will be called instead of
+ wearing out the tool. If returns nil, does nothing.
+ If after_use doesn't exist, it is the same as:
+ function(itemstack, user, node, digparams)
+ itemstack:add_wear(digparams.wear)
+ return itemstack
+ end
+ ^ The user may be any ObjectRef or nil.
+ ]]
+ _custom_field = whatever,
+ --[[
+ ^ Add your own custom fields. By convention, all custom field names
+ should start with `_` to avoid naming collisions with future engine
+ usage.
+ ]]
+ }
+
+### Tile definition
+* `"image.png"`
+* `{name="image.png", animation={Tile Animation definition}}`
+* `{name="image.png", backface_culling=bool, tileable_vertical=bool,
+ tileable_horizontal=bool}`
+ * backface culling enabled by default for most nodes
+ * tileable flags are info for shaders, how they should treat texture
+ when displacement mapping is used
+ Directions are from the point of view of the tile texture,
+ not the node it's on
+* `{name="image.png", color=ColorSpec}`
+ * the texture's color will be multiplied with this color.
+ * the tile's color overrides the owning node's color in all cases.
+* deprecated, yet still supported field names:
+ * `image` (name)
+
+### Tile animation definition
+
+ {
+ type = "vertical_frames",
+ aspect_w = 16,
+ -- ^ specify width of a frame in pixels
+ aspect_h = 16,
+ -- ^ specify height of a frame in pixels
+ length = 3.0,
+ -- ^ specify full loop length
+ }
+
+ {
+ type = "sheet_2d",
+ frames_w = 5,
+ -- ^ specify width in number of frames
+ frames_h = 3,
+ -- ^ specify height in number of frames
+ frame_length = 0.5,
+ -- ^ specify length of a single frame
+ }
+
+### Node definition (`register_node`)
+
+ {
+ -- <all fields allowed in item definitions>,
+
+ drawtype = "normal", -- See "Node drawtypes"
+ visual_scale = 1.0, --[[
+ ^ Supported for drawtypes "plantlike", "signlike", "torchlike",
+ ^ "firelike", "mesh".
+ ^ For plantlike and firelike, the image will start at the bottom of the
+ ^ node, for the other drawtypes the image will be centered on the node.
+ ^ Note that positioning for "torchlike" may still change. ]]
+ tiles = {tile definition 1, def2, def3, def4, def5, def6}, --[[
+ ^ Textures of node; +Y, -Y, +X, -X, +Z, -Z (old field name: tile_images)
+ ^ List can be shortened to needed length ]]
+ overlay_tiles = {tile definition 1, def2, def3, def4, def5, def6}, --[[
+ ^ Same as `tiles`, but these textures are drawn on top of the
+ ^ base tiles. You can use this to colorize only specific parts of
+ ^ your texture. If the texture name is an empty string, that
+ ^ overlay is not drawn. Since such tiles are drawn twice, it
+ ^ is not recommended to use overlays on very common nodes.
+ special_tiles = {tile definition 1, Tile definition 2}, --[[
+ ^ Special textures of node; used rarely (old field name: special_materials)
+ ^ List can be shortened to needed length ]]
+ color = ColorSpec, --[[
+ ^ The node's original color will be multiplied with this color.
+ ^ If the node has a palette, then this setting only has an effect
+ ^ in the inventory and on the wield item. ]]
+ use_texture_alpha = false, -- Use texture's alpha channel
+ palette = "palette.png", --[[
+ ^ The node's `param2` is used to select a pixel from the image
+ ^ (pixels are arranged from left to right and from top to bottom).
+ ^ The node's color will be multiplied with the selected pixel's
+ ^ color. Tiles can override this behavior.
+ ^ Only when `paramtype2` supports palettes. ]]
+ post_effect_color = "green#0F", -- If player is inside node, see "ColorSpec"
+ paramtype = "none", -- See "Nodes" --[[
+ ^ paramtype = "light" allows light to propagate from or through the node with light value
+ ^ falling by 1 per node. This line is essential for a light source node to spread its light. ]]
+ paramtype2 = "none", -- See "Nodes"
+ place_param2 = nil, -- Force value for param2 when player places node
+ is_ground_content = true, -- If false, the cave generator will not carve through this
+ sunlight_propagates = false, -- If true, sunlight will go infinitely through this
+ walkable = true, -- If true, objects collide with node
+ pointable = true, -- If true, can be pointed at
+ diggable = true, -- If false, can never be dug
+ climbable = false, -- If true, can be climbed on (ladder)
+ buildable_to = false, -- If true, placed nodes can replace this node
+ floodable = false, --[[
+ ^ If true, liquids flow into and replace this node.
+ ^ Warning: making a liquid node 'floodable' does not work and may cause problems. ]]
+ liquidtype = "none", -- "none"/"source"/"flowing"
+ liquid_alternative_flowing = "", -- Flowing version of source liquid
+ liquid_alternative_source = "", -- Source version of flowing liquid
+ liquid_viscosity = 0, -- Higher viscosity = slower flow (max. 7)
+ liquid_renewable = true, --[[
+ ^ If true, a new liquid source can be created by placing two or more sources nearby ]]
+ leveled = 0, --[[
+ ^ Block contains level in param2. Value is default level, used for snow.
+ ^ Don't forget to use "leveled" type nodebox. ]]
+ liquid_range = 8, -- number of flowing nodes around source (max. 8)
+ drowning = 0, -- Player will take this amount of damage if no bubbles are left
+ light_source = 0, --[[
+ ^ Amount of light emitted by node.
+ ^ To set the maximum (currently 14), use the value 'minetest.LIGHT_MAX'.
+ ^ A value outside the range 0 to minetest.LIGHT_MAX causes undefined behavior.]]
+ damage_per_second = 0, -- If player is inside node, this damage is caused
+ node_box = {type="regular"}, -- See "Node boxes"
+ connects_to = nodenames, --[[
+ * Used for nodebox nodes with the type == "connected"
+ * Specifies to what neighboring nodes connections will be drawn
+ * e.g. `{"group:fence", "default:wood"}` or `"default:stone"` ]]
+ connect_sides = { "top", "bottom", "front", "left", "back", "right" }, --[[
+ ^ Tells connected nodebox nodes to connect only to these sides of this node. ]]
+ mesh = "model",
+ selection_box = {type="regular"}, -- See "Node boxes" --[[
+ ^ If drawtype "nodebox" is used and selection_box is nil, then node_box is used. ]]
+ legacy_facedir_simple = false, -- Support maps made in and before January 2012
+ legacy_wallmounted = false, -- Support maps made in and before January 2012
+ waving = 0, --[[ valid for mesh, nodebox, plantlike, allfaces_optional nodes
+ ^ 1 - wave node like plants (top of node moves, bottom is fixed)
+ ^ 2 - wave node like leaves (whole node moves side-to-side synchronously)
+ ^ caveats: not all models will properly wave
+ ^ plantlike drawtype nodes can only wave like plants
+ ^ allfaces_optional drawtype nodes can only wave like leaves --]]
+ sounds = {
+ footstep = <SimpleSoundSpec>,
+ dig = <SimpleSoundSpec>, -- "__group" = group-based sound (default)
+ dug = <SimpleSoundSpec>,
+ place = <SimpleSoundSpec>,
+ place_failed = <SimpleSoundSpec>,
+ },
+ drop = "", -- Name of dropped node when dug. Default is the node itself.
+ -- Alternatively:
+ drop = {
+ max_items = 1, -- Maximum number of items to drop.
+ items = { -- Choose max_items randomly from this list.
+ {
+ items = {"foo:bar", "baz:frob"}, -- Items to drop.
+ rarity = 1, -- Probability of dropping is 1 / rarity.
+ inherit_color = true, -- To inherit palette color from the node
+ },
+ },
+ },
+
+ on_construct = func(pos), --[[
+ ^ Node constructor; called after adding node
+ ^ Can set up metadata and stuff like that
+ ^ Not called for bulk node placement (i.e. schematics and VoxelManip)
+ ^ default: nil ]]
+ on_destruct = func(pos), --[[
+ ^ Node destructor; called before removing node
+ ^ Not called for bulk node placement (i.e. schematics and VoxelManip)
+ ^ default: nil ]]
+ after_destruct = func(pos, oldnode), --[[
+ ^ Node destructor; called after removing node
+ ^ Not called for bulk node placement (i.e. schematics and VoxelManip)
+ ^ default: nil ]]
+ on_flood = func(pos, oldnode, newnode), --[[
+ ^ Called when a liquid (newnode) is about to flood oldnode, if
+ ^ it has `floodable = true` in the nodedef. Not called for bulk
+ ^ node placement (i.e. schematics and VoxelManip) or air nodes. If
+ ^ return true the node is not flooded, but on_flood callback will
+ ^ most likely be called over and over again every liquid update
+ ^ interval. Default: nil.
+ ^ Warning: making a liquid node 'floodable' does not work and may cause problems. ]]
+
+ after_place_node = func(pos, placer, itemstack, pointed_thing) --[[
+ ^ Called after constructing node when node was placed using
+ minetest.item_place_node / minetest.place_node
+ ^ If return true no item is taken from itemstack
+ ^ `placer` may be any valid ObjectRef or nil
+ ^ default: nil ]]
+ after_dig_node = func(pos, oldnode, oldmetadata, digger), --[[
+ ^ oldmetadata is in table format
+ ^ Called after destructing node when node was dug using
+ minetest.node_dig / minetest.dig_node
+ ^ default: nil ]]
+ can_dig = function(pos, [player]) --[[
+ ^ returns true if node can be dug, or false if not
+ ^ default: nil ]]
+
+ on_punch = func(pos, node, puncher, pointed_thing), --[[
+ ^ default: minetest.node_punch
+ ^ By default: Calls minetest.register_on_punchnode callbacks ]]
+ on_rightclick = func(pos, node, clicker, itemstack, pointed_thing), --[[
+ ^ default: nil
+ ^ itemstack will hold clicker's wielded item
+ ^ Shall return the leftover itemstack
+ ^ Note: pointed_thing can be nil, if a mod calls this function
+ This function does not get triggered by clients <=0.4.16 if the
+ "formspec" node metadata field is set ]]
+
+ on_dig = func(pos, node, digger), --[[
+ ^ default: minetest.node_dig
+ ^ By default: checks privileges, wears out tool and removes node ]]
+
+ on_timer = function(pos,elapsed), --[[
+ ^ default: nil
+ ^ called by NodeTimers, see minetest.get_node_timer and NodeTimerRef
+ ^ elapsed is the total time passed since the timer was started
+ ^ return true to run the timer for another cycle with the same timeout value ]]
+
+ on_receive_fields = func(pos, formname, fields, sender), --[[
+ ^ fields = {name1 = value1, name2 = value2, ...}
+ ^ Called when an UI form (e.g. sign text input) returns data
+ ^ default: nil ]]
+
+ allow_metadata_inventory_move = func(pos, from_list, from_index,
+ to_list, to_index, count, player), --[[
+ ^ Called when a player wants to move items inside the inventory
+ ^ Return value: number of items allowed to move ]]
+
+ allow_metadata_inventory_put = func(pos, listname, index, stack, player), --[[
+ ^ Called when a player wants to put something into the inventory
+ ^ Return value: number of items allowed to put
+ ^ Return value: -1: Allow and don't modify item count in inventory ]]
+
+ allow_metadata_inventory_take = func(pos, listname, index, stack, player), --[[
+ ^ Called when a player wants to take something out of the inventory
+ ^ Return value: number of items allowed to take
+ ^ Return value: -1: Allow and don't modify item count in inventory ]]
+
+ on_metadata_inventory_move = func(pos, from_list, from_index,
+ to_list, to_index, count, player),
+ on_metadata_inventory_put = func(pos, listname, index, stack, player),
+ on_metadata_inventory_take = func(pos, listname, index, stack, player), --[[
+ ^ Called after the actual action has happened, according to what was allowed.
+ ^ No return value ]]
+
+ on_blast = func(pos, intensity), --[[
+ ^ intensity: 1.0 = mid range of regular TNT
+ ^ If defined, called when an explosion touches the node, instead of
+ removing the node ]]
+ }
+
+### Recipe for `register_craft` (shaped)
+
+ {
+ output = 'default:pick_stone',
+ recipe = {
+ {'default:cobble', 'default:cobble', 'default:cobble'},
+ {'', 'default:stick', ''},
+ {'', 'default:stick', ''}, -- Also groups; e.g. 'group:crumbly'
+ },
+ replacements = --[[<optional list of item pairs,
+ replace one input item with another item on crafting>]]
+ }
+
+### Recipe for `register_craft` (shapeless)
+
+ {
+ type = "shapeless",
+ output = 'mushrooms:mushroom_stew',
+ recipe = {
+ "mushrooms:bowl",
+ "mushrooms:mushroom_brown",
+ "mushrooms:mushroom_red",
+ },
+ replacements = --[[<optional list of item pairs,
+ replace one input item with another item on crafting>]]
+ }
+
+### Recipe for `register_craft` (tool repair)
+
+ {
+ type = "toolrepair",
+ additional_wear = -0.02,
+ }
+
+### Recipe for `register_craft` (cooking)
+
+ {
+ type = "cooking",
+ output = "default:glass",
+ recipe = "default:sand",
+ cooktime = 3,
+ }
+
+### Recipe for `register_craft` (furnace fuel)
+
+ {
+ type = "fuel",
+ recipe = "default:leaves",
+ burntime = 1,
+ }
+
+### Ore definition (`register_ore`)
+
+ {
+ ore_type = "scatter", -- See "Ore types"
+ ore = "default:stone_with_coal",
+ wherein = "default:stone",
+ -- ^ a list of nodenames is supported too
+ clust_scarcity = 8*8*8,
+ -- ^ Ore has a 1 out of clust_scarcity chance of spawning in a node
+ -- ^ This value should be *MUCH* higher than your intuition might tell you!
+ clust_num_ores = 8,
+ -- ^ Number of ores in a cluster
+ clust_size = 3,
+ -- ^ Size of the bounding box of the cluster
+ -- ^ In this example, there is a 3x3x3 cluster where 8 out of the 27 nodes are coal ore
+ y_min = -31000,
+ y_max = 64,
+ flags = "",
+ -- ^ Attributes for this ore generation
+ noise_threshold = 0.5,
+ -- ^ If noise is above this threshold, ore is placed. Not needed for a uniform distribution
+ noise_params = {offset=0, scale=1, spread={x=100, y=100, z=100}, seed=23, octaves=3, persist=0.70}
+ -- ^ NoiseParams structure describing the perlin noise used for ore distribution.
+ -- ^ Needed for sheet ore_type. Omit from scatter ore_type for a uniform ore distribution
+ random_factor = 1.0,
+ -- ^ Multiplier of the randomness contribution to the noise value at any
+ -- given point to decide if ore should be placed. Set to 0 for solid veins.
+ -- ^ This parameter is only valid for ore_type == "vein".
+ biomes = {"desert", "rainforest"}
+ -- ^ List of biomes in which this decoration occurs. Occurs in all biomes if this is omitted,
+ -- ^ and ignored if the Mapgen being used does not support biomes.
+ -- ^ Can be a list of (or a single) biome names, IDs, or definitions.
+ }
+
+### Biome definition (`register_biome`)
+
+**Note**
+The Biome API is still in an experimental phase and subject to change.
+
+ {
+ name = "tundra",
+ node_dust = "default:snow",
+ -- ^ Node dropped onto upper surface after all else is generated.
+ node_top = "default:dirt_with_snow",
+ depth_top = 1,
+ -- ^ Node forming surface layer of biome and thickness of this layer.
+ node_filler = "default:permafrost",
+ depth_filler = 3,
+ -- ^ Node forming lower layer of biome and thickness of this layer.
+ node_stone = "default:bluestone",
+ -- ^ Node that replaces all stone nodes between roughly y_min and y_max.
+ node_water_top = "default:ice",
+ depth_water_top = 10,
+ -- ^ Node forming a surface layer in seawater with the defined thickness.
+ node_water = "",
+ -- ^ Node that replaces all seawater nodes not in the defined surface layer.
+ node_river_water = "default:ice",
+ -- ^ Node that replaces river water in mapgens that use default:river_water.
+ node_riverbed = "default:gravel",
+ depth_riverbed = 2,
+ -- ^ Node placed under river water and thickness of this layer.
+ y_min = 1,
+ y_max = 31000,
+ -- ^ Lower and upper limits for biome.
+ -- ^ Because biome is not recalculated for every node in a node column
+ -- ^ some biome materials can exceed their limits, especially stone.
+ -- ^ For each node column in a mapchunk, biome is only recalculated at column
+ -- ^ top and at each of these surfaces:
+ -- ^ Ground below air, water below air, ground below water.
+ -- ^ The selected biome then stays in effect for all nodes below until
+ -- ^ column base or the next biome recalculation.
+ heat_point = 0,
+ humidity_point = 50,
+ -- ^ Characteristic average temperature and humidity for the biome.
+ -- ^ These values create 'biome points' on a voronoi diagram that has heat
+ -- ^ and humidity as axes. The resulting voronoi cells determine which
+ -- ^ heat/humidity points belong to which biome, and therefore determine
+ -- ^ the area and location of each biome in the world.
+ -- ^ The biome points need to be carefully and evenly spaced on the voronoi
+ -- ^ diagram to result in roughly equal size biomes.
+ -- ^ Heat and humidity have average values of 50, vary mostly between
+ -- ^ 0 and 100 but also often exceed these values.
+ -- ^ Heat is not in degrees celcius, both values are abstract.
+ }
+
+### Decoration definition (`register_decoration`)
+
+ {
+ deco_type = "simple", -- See "Decoration types"
+ place_on = "default:dirt_with_grass",
+ -- ^ Node (or list of nodes) that the decoration can be placed on
+ sidelen = 8,
+ -- ^ Size of divisions made in the chunk being generated.
+ -- ^ If the chunk size is not evenly divisible by sidelen, sidelen is made equal to the chunk size.
+ fill_ratio = 0.02,
+ -- ^ Ratio of the area to be uniformly filled by the decoration.
+ -- ^ Used only if noise_params is not specified.
+ noise_params = {offset=0, scale=.45, spread={x=100, y=100, z=100}, seed=354, octaves=3, persist=0.7},
+ -- ^ NoiseParams structure describing the perlin noise used for decoration distribution.
+ -- ^ The result of this is multiplied by the 2d area of the division being decorated.
+ biomes = {"Oceanside", "Hills", "Plains"},
+ -- ^ List of biomes in which this decoration occurs. Occurs in all biomes if this is omitted,
+ -- ^ and ignored if the Mapgen being used does not support biomes.
+ -- ^ Can be a list of (or a single) biome names, IDs, or definitions.
+ y_min = -31000
+ y_max = 31000
+ -- ^ Minimum and maximum `y` positions these decorations can be generated at.
+ -- ^ This parameter refers to the `y` position of the decoration base, so
+ -- the actual maximum height would be `height_max + size.Y`.
+ spawn_by = "default:water",
+ -- ^ Node (or list of nodes) that the decoration only spawns next to.
+ -- ^ Checks two horizontal planes of neighbouring nodes (including diagonal neighbours),
+ -- ^ one plane at Y = surface and one plane at Y = surface = + 1.
+ num_spawn_by = 1,
+ -- ^ Number of spawn_by nodes that must be surrounding the decoration position to occur.
+ -- ^ If absent or -1, decorations occur next to any nodes.
+ flags = "liquid_surface, force_placement",
+ -- ^ Flags for all decoration types.
+ -- ^ "liquid_surface": Instead of placement on the highest solid surface
+ -- ^ in a mapchunk column, placement is on the highest liquid surface.
+ -- ^ Placement is disabled if solid nodes are found above the liquid surface.
+ -- ^ "force_placement": Nodes other than "air" and "ignore" are replaced by the decoration.
+
+ ----- Simple-type parameters
+ decoration = "default:grass",
+ -- ^ The node name used as the decoration.
+ -- ^ If instead a list of strings, a randomly selected node from the list is placed as the decoration.
+ height = 1,
+ -- ^ Number of nodes high the decoration is made.
+ -- ^ If height_max is not 0, this is the lower bound of the randomly selected height.
+ height_max = 0,
+ -- ^ Number of nodes the decoration can be at maximum.
+ -- ^ If absent, the parameter 'height' is used as a constant.
+ param2 = 0,
+ -- ^ Param2 value of placed decoration node.
+
+ ----- Schematic-type parameters
+ schematic = "foobar.mts",
+ -- ^ If schematic is a string, it is the filepath relative to the current working directory of the
+ -- ^ specified Minetest schematic file.
+ -- ^ - OR -, could be the ID of a previously registered schematic
+ -- ^ - OR -, could instead be a table containing two mandatory fields, size and data,
+ -- ^ and an optional table yslice_prob:
+ schematic = {
+ size = {x=4, y=6, z=4},
+ data = {
+ {name="default:cobble", param1=255, param2=0},
+ {name="default:dirt_with_grass", param1=255, param2=0},
+ {name="ignore", param1=255, param2=0},
+ {name="air", param1=255, param2=0},
+ ...
+ },
+ yslice_prob = {
+ {ypos=2, prob=128},
+ {ypos=5, prob=64},
+ ...
+ },
+ },
+ -- ^ See 'Schematic specifier' for details.
+ replacements = {["oldname"] = "convert_to", ...},
+ flags = "place_center_x, place_center_y, place_center_z",
+ -- ^ Flags for schematic decorations. See 'Schematic attributes'.
+ rotation = "90" -- rotate schematic 90 degrees on placement
+ -- ^ Rotation can be "0", "90", "180", "270", or "random".
+ }
+
+### Chat command definition (`register_chatcommand`)
+
+ {
+ params = "<name> <privilege>", -- Short parameter description
+ description = "Remove privilege from player", -- Full description
+ privs = {privs=true}, -- Require the "privs" privilege to run
+ func = function(name, param), -- Called when command is run.
+ -- Returns boolean success and text output.
+ }
+
+### Detached inventory callbacks
+
+ {
+ allow_move = func(inv, from_list, from_index, to_list, to_index, count, player),
+ -- ^ Called when a player wants to move items inside the inventory
+ -- ^ Return value: number of items allowed to move
+
+ allow_put = func(inv, listname, index, stack, player),
+ -- ^ Called when a player wants to put something into the inventory
+ -- ^ Return value: number of items allowed to put
+ -- ^ Return value: -1: Allow and don't modify item count in inventory
+
+ allow_take = func(inv, listname, index, stack, player),
+ -- ^ Called when a player wants to take something out of the inventory
+ -- ^ Return value: number of items allowed to take
+ -- ^ Return value: -1: Allow and don't modify item count in inventory
+
+ on_move = func(inv, from_list, from_index, to_list, to_index, count, player),
+ on_put = func(inv, listname, index, stack, player),
+ on_take = func(inv, listname, index, stack, player),
+ -- ^ Called after the actual action has happened, according to what was allowed.
+ -- ^ No return value
+ }
+
+### HUD Definition (`hud_add`, `hud_get`)
+
+ {
+ hud_elem_type = "image", -- see HUD element types
+ -- ^ type of HUD element, can be either of "image", "text", "statbar", or "inventory"
+ position = {x=0.5, y=0.5},
+ -- ^ Left corner position of element
+ name = "<name>",
+ scale = {x=2, y=2},
+ text = "<text>",
+ number = 2,
+ item = 3,
+ -- ^ Selected item in inventory. 0 for no item selected.
+ direction = 0,
+ -- ^ Direction: 0: left-right, 1: right-left, 2: top-bottom, 3: bottom-top
+ alignment = {x=0, y=0},
+ -- ^ See "HUD Element Types"
+ offset = {x=0, y=0},
+ -- ^ See "HUD Element Types"
+ size = { x=100, y=100 },
+ -- ^ Size of element in pixels
+ }
+
+### Particle definition (`add_particle`)
+
+ {
+ pos = {x=0, y=0, z=0},
+ velocity = {x=0, y=0, z=0},
+ acceleration = {x=0, y=0, z=0},
+ -- ^ Spawn particle at pos with velocity and acceleration
+ expirationtime = 1,
+ -- ^ Disappears after expirationtime seconds
+ size = 1,
+ collisiondetection = false,
+ -- ^ collisiondetection: if true collides with physical objects
+ collision_removal = false,
+ -- ^ collision_removal: if true then particle is removed when it collides,
+ -- ^ requires collisiondetection = true to have any effect
+ vertical = false,
+ -- ^ vertical: if true faces player using y axis only
+ texture = "image.png",
+ -- ^ Uses texture (string)
+ playername = "singleplayer",
+ -- ^ optional, if specified spawns particle only on the player's client
+ animation = {Tile Animation definition},
+ -- ^ optional, specifies how to animate the particle texture
+ glow = 0
+ -- ^ optional, specify particle self-luminescence in darkness
+ }
+
+
+### `ParticleSpawner` definition (`add_particlespawner`)
+
+ {
+ amount = 1,
+ time = 1,
+ -- ^ If time is 0 has infinite lifespan and spawns the amount on a per-second base
+ minpos = {x=0, y=0, z=0},
+ maxpos = {x=0, y=0, z=0},
+ minvel = {x=0, y=0, z=0},
+ maxvel = {x=0, y=0, z=0},
+ minacc = {x=0, y=0, z=0},
+ maxacc = {x=0, y=0, z=0},
+ minexptime = 1,
+ maxexptime = 1,
+ minsize = 1,
+ maxsize = 1,
+ -- ^ The particle's properties are random values in between the bounds:
+ -- ^ minpos/maxpos, minvel/maxvel (velocity), minacc/maxacc (acceleration),
+ -- ^ minsize/maxsize, minexptime/maxexptime (expirationtime)
+ collisiondetection = false,
+ -- ^ collisiondetection: if true uses collision detection
+ collision_removal = false,
+ -- ^ collision_removal: if true then particle is removed when it collides,
+ -- ^ requires collisiondetection = true to have any effect
+ attached = ObjectRef,
+ -- ^ attached: if defined, particle positions, velocities and accelerations
+ -- ^ are relative to this object's position and yaw.
+ vertical = false,
+ -- ^ vertical: if true faces player using y axis only
+ texture = "image.png",
+ -- ^ Uses texture (string)
+ playername = "singleplayer"
+ -- ^ Playername is optional, if specified spawns particle only on the player's client
+ }
+
+### `HTTPRequest` definition (`HTTPApiTable.fetch_async`, `HTTPApiTable.fetch_async`)
+
+ {
+ url = "http://example.org",
+ timeout = 10,
+ -- ^ Timeout for connection in seconds. Default is 3 seconds.
+ post_data = "Raw POST request data string" OR { field1 = "data1", field2 = "data2" },
+ -- ^ Optional, if specified a POST request with post_data is performed.
+ -- ^ Accepts both a string and a table. If a table is specified, encodes table
+ -- ^ as x-www-form-urlencoded key-value pairs.
+ -- ^ If post_data ist not specified, a GET request is performed instead.
+ user_agent = "ExampleUserAgent",
+ -- ^ Optional, if specified replaces the default minetest user agent with given string
+ extra_headers = { "Accept-Language: en-us", "Accept-Charset: utf-8" },
+ -- ^ Optional, if specified adds additional headers to the HTTP request. You must make sure
+ -- ^ that the header strings follow HTTP specification ("Key: Value").
+ multipart = boolean
+ -- ^ Optional, if true performs a multipart HTTP request. Default is false.
+ }
+
+### `HTTPRequestResult` definition (`HTTPApiTable.fetch` callback, `HTTPApiTable.fetch_async_get`)
+
+ {
+ completed = true,
+ -- ^ If true, the request has finished (either succeeded, failed or timed out)
+ succeeded = true,
+ -- ^ If true, the request was succesful
+ timeout = false,
+ -- ^ If true, the request timed out
+ code = 200,
+ -- ^ HTTP status code
+ data = "response"
+ }
+
+### Authentication handler definition
+
+ {
+ get_auth = func(name),
+ -- ^ Get authentication data for existing player `name` (`nil` if player doesn't exist)
+ -- ^ returns following structure `{password=<string>, privileges=<table>, last_login=<number or nil>}`
+ create_auth = func(name, password),
+ -- ^ Create new auth data for player `name`
+ -- ^ Note that `password` is not plain-text but an arbitrary representation decided by the engine
+ set_password = func(name, password),
+ -- ^ Set password of player `name` to `password`
+ Auth data should be created if not present
+ set_privileges = func(name, privileges),
+ -- ^ Set privileges of player `name`
+ -- ^ `privileges` is in table form, auth data should be created if not present
+ reload = func(),
+ -- ^ Reload authentication data from the storage location
+ -- ^ Returns boolean indicating success
+ record_login = func(name),
+ -- ^ Called when player joins, used for keeping track of last_login
+ }
+
diff --git a/doc/main_page.dox b/doc/main_page.dox
new file mode 100644
index 000000000..8211d9cae
--- /dev/null
+++ b/doc/main_page.dox
@@ -0,0 +1,8 @@
+/** @mainpage The Minetest engine internal documentation
+
+Welcome to the Minetest engine Doxygen documentation site!\n
+This page documents the internal structure of the Minetest engine's C++ code.\n
+Use the tree view at the left to navigate.
+
+*/
+
diff --git a/doc/menu_lua_api.txt b/doc/menu_lua_api.txt
new file mode 100644
index 000000000..eb0d2ed6c
--- /dev/null
+++ b/doc/menu_lua_api.txt
@@ -0,0 +1,241 @@
+Minetest Lua Mainmenu API Reference 0.4.17
+========================================
+
+Introduction
+-------------
+The main menu is defined as a formspec by Lua in builtin/mainmenu/
+Description of formspec language to show your menu is in lua_api.txt
+
+Callbacks
+---------
+core.buttonhandler(fields): called when a button is pressed.
+^ fields = {name1 = value1, name2 = value2, ...}
+core.event_handler(event)
+^ event: "MenuQuit", "KeyEnter", "ExitButton" or "EditBoxEnter"
+
+Gamedata
+--------
+The "gamedata" table is read when calling core.start(). It should contain:
+{
+ playername = <name>,
+ password = <password>,
+ address = <IP/adress>,
+ port = <port>,
+ selected_world = <index>, -- 0 for client mode
+ singleplayer = <true/false>,
+}
+
+Functions
+---------
+core.start()
+core.close()
+
+Filesystem:
+core.get_builtin_path()
+^ returns path to builtin root
+core.get_modpath() (possible in async calls)
+^ returns path to global modpath
+core.get_modstore_details(modid) (possible in async calls)
+^ modid numeric id of mod in modstore
+^ returns {
+ id = <numeric id of mod in modstore>,
+ title = <human readable title>,
+ basename = <basename for mod>,
+ description = <description of mod>,
+ author = <author of mod>,
+ download_url= <best match download url>,
+ license = <short description of license>,
+ rating = <float value of current rating>
+}
+core.get_modstore_list() (possible in async calls)
+^ returns {
+ [1] = {
+ id = <numeric id of mod in modstore>,
+ title = <human readable title>,
+ basename = <basename for mod>
+ }
+}
+core.get_gamepath() (possible in async calls)
+^ returns path to global gamepath
+core.get_texturepath() (possible in async calls)
+^ returns path to default textures
+core.create_dir(absolute_path) (possible in async calls)
+^ absolute_path to directory to create (needs to be absolute)
+^ returns true/false
+core.delete_dir(absolute_path) (possible in async calls)
+^ absolute_path to directory to delete (needs to be absolute)
+^ returns true/false
+core.copy_dir(source,destination,keep_soure) (possible in async calls)
+^ source folder
+^ destination folder
+^ keep_source DEFAULT true --> if set to false source is deleted after copying
+^ returns true/false
+core.extract_zip(zipfile,destination) [unzip within path required]
+^ zipfile to extract
+^ destination folder to extract to
+^ returns true/false
+core.download_file(url,target) (possible in async calls)
+^ url to download
+^ target to store to
+^ returns true/false
+core.get_version() (possible in async calls)
+^ returns current core version
+core.sound_play(spec, looped) -> handle
+^ spec = SimpleSoundSpec (see lua-api.txt)
+^ looped = bool
+core.sound_stop(handle)
+core.get_video_drivers()
+^ get list of video drivers supported by engine (not all modes are guaranteed to work)
+^ returns list of available video drivers' settings name and 'friendly' display name
+^ e.g. { {name="opengl", friendly_name="OpenGL"}, {name="software", friendly_name="Software Renderer"} }
+^ first element of returned list is guaranteed to be the NULL driver
+
+Formspec:
+core.update_formspec(formspec)
+core.get_table_index(tablename) -> index
+^ can also handle textlists
+core.formspec_escape(string) -> string
+^ escapes characters [ ] \ , ; that can not be used in formspecs
+core.explode_table_event(string) -> table
+^ returns e.g. {type="CHG", row=1, column=2}
+^ type: "INV" (no row selected), "CHG" (selected) or "DCL" (double-click)
+core.explode_textlist_event(string) -> table
+^ returns e.g. {type="CHG", index=1}
+^ type: "INV" (no row selected), "CHG" (selected) or "DCL" (double-click)
+
+GUI:
+core.set_background(type, texturepath,[tile],[minsize])
+^ type: "background", "overlay", "header" or "footer"
+^ tile: tile the image instead of scaling (background only)
+^ minsize: minimum tile size, images are scaled to at least this size prior
+^ doing tiling (background only)
+core.set_clouds(<true/false>)
+core.set_topleft_text(text)
+core.show_keys_menu()
+core.file_open_dialog(formname,caption)
+^ shows a file open dialog
+^ formname is base name of dialog response returned in fields
+^ -if dialog was accepted "_accepted"
+^^ will be added to fieldname containing the path
+^ -if dialog was canceled "_cancelled"
+^ will be added to fieldname value is set to formname itself
+^ returns nil or selected file/folder
+core.get_screen_info()
+^ returns {
+ density = <screen density 0.75,1.0,2.0,3.0 ... (dpi)>,
+ display_width = <width of display>,
+ display_height = <height of display>,
+ window_width = <current window width>,
+ window_height = <current window height>
+ }
+
+Games:
+core.get_game(index)
+^ returns {
+ id = <id>,
+ path = <full path to game>,
+ gamemods_path = <path>,
+ name = <name of game>,
+ menuicon_path = <full path to menuicon>,
+ DEPRECATED:
+ addon_mods_paths = {[1] = <path>,},
+}
+core.get_games() -> table of all games in upper format (possible in async calls)
+core.get_mapgen_names([include_hidden=false]) -> table of map generator algorithms
+ registered in the core (possible in async calls)
+
+Favorites:
+core.get_favorites(location) -> list of favorites (possible in async calls)
+^ location: "local" or "online"
+^ returns {
+ [1] = {
+ clients = <number of clients/nil>,
+ clients_max = <maximum number of clients/nil>,
+ version = <server version/nil>,
+ password = <true/nil>,
+ creative = <true/nil>,
+ damage = <true/nil>,
+ pvp = <true/nil>,
+ description = <server description/nil>,
+ name = <server name/nil>,
+ address = <address of server/nil>,
+ port = <port>
+ },
+}
+core.delete_favorite(id, location) -> success
+
+Logging:
+core.debug(line) (possible in async calls)
+^ Always printed to stderr and logfile (print() is redirected here)
+core.log(line) (possible in async calls)
+core.log(loglevel, line) (possible in async calls)
+^ loglevel one of "error", "action", "info", "verbose"
+
+Settings:
+core.setting_set(name, value)
+core.setting_get(name) -> string or nil (possible in async calls)
+core.setting_setbool(name, value)
+core.setting_getbool(name) -> bool or nil (possible in async calls)
+core.setting_save() -> nil, save all settings to config file
+
+Worlds:
+core.get_worlds() -> list of worlds (possible in async calls)
+^ returns {
+ [1] = {
+ path = <full path to world>,
+ name = <name of world>,
+ gameid = <gameid of world>,
+ },
+}
+core.create_world(worldname, gameid)
+core.delete_world(index)
+
+Helpers:
+core.get_us_time()
+^ returns time with microsecond precision
+core.gettext(string) -> string
+^ look up the translation of a string in the gettext message catalog
+fgettext_ne(string, ...)
+^ call core.gettext(string), replace "$1"..."$9" with the given
+^ extra arguments and return the result
+fgettext(string, ...) -> string
+^ same as fgettext_ne(), but calls core.formspec_escape before returning result
+core.parse_json(string[, nullvalue]) -> something (possible in async calls)
+^ see core.parse_json (lua_api.txt)
+dump(obj, dumped={})
+^ Return object serialized as a string
+string:split(separator)
+^ eg. string:split("a,b", ",") == {"a","b"}
+string:trim()
+^ eg. string.trim("\n \t\tfoo bar\t ") == "foo bar"
+core.is_yes(arg) (possible in async calls)
+^ returns whether arg can be interpreted as yes
+minetest.encode_base64(string) (possible in async calls)
+^ Encodes a string in base64.
+minetest.decode_base64(string) (possible in async calls)
+^ Decodes a string encoded in base64.
+
+Version compat:
+core.get_min_supp_proto()
+^ returns the minimum supported network protocol version
+core.get_max_supp_proto()
+^ returns the maximum supported network protocol version
+
+Async:
+core.handle_async(async_job,parameters,finished)
+^ execute a function asynchronously
+^ async_job is a function receiving one parameter and returning one parameter
+^ parameters parameter table passed to async_job
+^ finished function to be called once async_job has finished
+^ the result of async_job is passed to this function
+
+Limitations of Async operations
+ -No access to global lua variables, don't even try
+ -Limited set of available functions
+ e.g. No access to functions modifying menu like core.start,core.close,
+ core.file_open_dialog
+
+
+Class reference
+----------------
+Settings: see lua_api.txt
diff --git a/doc/minetest.6 b/doc/minetest.6
new file mode 100644
index 000000000..a135e541c
--- /dev/null
+++ b/doc/minetest.6
@@ -0,0 +1,114 @@
+.TH minetest 6 "10 September 2013" "" ""
+
+.SH NAME
+minetest, minetestserver \- Multiplayer infinite-world block sandbox
+
+.SH SYNOPSIS
+.B minetest
+[\fB--server SERVER OPTIONS\fR | \fBCLIENT OPTIONS\fR]
+[\fBCOMMON OPTIONS\fR]
+[\fBWORLD PATH\fR]
+
+.B minetestserver
+[\fBSERVER OPTIONS\fR]
+[\fBCOMMON OPTIONS\fR]
+[\fBWORLD PATH\fR]
+
+.SH DESCRIPTION
+.B Minetest
+is one of the first InfiniMiner/Minecraft(/whatever) inspired games (started October 2010), with a goal of taking the survival multiplayer gameplay to a slightly different direction.
+.PP
+The main design philosophy is to keep it technically simple, stable and portable. It will be kept lightweight enough to run on fairly old hardware.
+
+.SH COMMON OPTIONS
+.TP
+.B \-\-help
+Print allowed options and exit
+.TP
+.B \-\-version
+Print version information and exit
+.TP
+.B \-\-config <value>
+Load configuration from specified file
+.TP
+.B \-\-logfile <value>
+Set logfile path (debug.txt)
+.TP
+.B \-\-info
+Print more information to console
+.TP
+.B \-\-verbose
+Print even more information to console
+.TP
+.B \-\-trace
+Print enormous amounts of information to console
+.TP
+.B \-\-gameid <value>
+Set gameid
+.TP
+.B \-\-worldname <value>
+Set world path by name
+.TP
+.B \-\-world <value> | list
+Set world path or list worlds
+.TP
+.B \-\-map\-dir <value>
+Same as \-\-world (deprecated)
+.TP
+.B \-\-port <value>
+Set network port (UDP) to use
+.TP
+.B \-\-run\-unittests
+Run unit tests and exit
+
+.SH CLIENT OPTIONS
+.TP
+.B \-\-address <value>
+Address to connect to
+.TP
+.B \-\-go
+Disable main menu
+.TP
+.B \-\-name <value>
+Set player name
+.TP
+.B \-\-password <value>
+Set password
+.TP
+.B \-\-random\-input
+Enable random user input, for testing (client only)
+.TP
+.B \-\-videomodes
+List available video modes (client only)
+.TP
+.B \-\-speedtests
+Run speed tests
+
+.SH SERVER OPTIONS
+.TP
+.B \-\-migrate <value>
+Migrate from current map backend to another. Possible values are sqlite3,
+leveldb, redis, and dummy.
+.TP
+.B \-\-terminal
+Display an interactive terminal over ncurses during execution.
+
+.SH ENVIRONMENT
+.TP
+.B MINETEST_SUBGAME_PATH
+Colon delimited list of directories to search for subgames.
+
+.SH BUGS
+Please report all bugs to Perttu Ahola <celeron55@gmail.com>.
+
+.SH AUTHOR
+.PP
+Perttu Ahola <celeron55@gmail.com>
+and contributors.
+.PP
+This man page was originally written by
+Juhani Numminen <juhaninumminen0@gmail.com>.
+
+.SH WWW
+http://www.minetest.net/
+
diff --git a/doc/minetestserver.6 b/doc/minetestserver.6
new file mode 100644
index 000000000..db5330d3c
--- /dev/null
+++ b/doc/minetestserver.6
@@ -0,0 +1,2 @@
+.so man6/minetest.6
+
diff --git a/doc/protocol.txt b/doc/protocol.txt
new file mode 100644
index 000000000..b151f88d8
--- /dev/null
+++ b/doc/protocol.txt
@@ -0,0 +1,108 @@
+Minetest protocol (incomplete, early draft):
+Updated 2011-06-18
+
+A custom protocol over UDP.
+Integers are big endian.
+Refer to connection.{h,cpp} for further reference.
+
+Initialization:
+- A dummy reliable packet with peer_id=PEER_ID_INEXISTENT=0 is sent to the server:
+ - Actually this can be sent without the reliable packet header, too, i guess,
+ but the sequence number in the header allows the sender to re-send the
+ packet without accidentally getting a double initialization.
+ - Packet content:
+ # Basic header
+ u32 protocol_id = PROTOCOL_ID = 0x4f457403
+ u16 sender_peer_id = PEER_ID_INEXISTENT = 0
+ u8 channel = 0
+ # Reliable packet header
+ u8 type = TYPE_RELIABLE = 3
+ u16 seqnum = SEQNUM_INITIAL = 65500
+ # Original packet header
+ u8 type = TYPE_ORIGINAL = 1
+ # And no actual payload.
+- Server responds with something like this:
+ - Packet content:
+ # Basic header
+ u32 protocol_id = PROTOCOL_ID = 0x4f457403
+ u16 sender_peer_id = PEER_ID_INEXISTENT = 0
+ u8 channel = 0
+ # Reliable packet header
+ u8 type = TYPE_RELIABLE = 3
+ u16 seqnum = SEQNUM_INITIAL = 65500
+ # Control packet header
+ u8 type = TYPE_CONTROL = 0
+ u8 controltype = CONTROLTYPE_SET_PEER_ID = 1
+ u16 peer_id_new = assigned peer id to client (other than 0 or 1)
+- Then the connection can be disconnected by sending:
+ - Packet content:
+ # Basic header
+ u32 protocol_id = PROTOCOL_ID = 0x4f457403
+ u16 sender_peer_id = whatever was gotten in CONTROLTYPE_SET_PEER_ID
+ u8 channel = 0
+ # Control packet header
+ u8 type = TYPE_CONTROL = 0
+ u8 controltype = CONTROLTYPE_DISCO = 3
+
+- Here's a quick untested connect-disconnect done in PHP:
+# host: ip of server (use gethostbyname(hostname) to get from a dns name)
+# port: port of server
+function check_if_minetestserver_up($host, $port)
+{
+ $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
+ $timeout = array("sec" => 1, "usec" => 0);
+ socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout);
+ $buf = "\x4f\x45\x74\x03\x00\x00\x00\x03\xff\xdc\x01";
+ socket_sendto($socket, $buf, strlen($buf), 0, $host, $port);
+ $buf = socket_read($socket, 1000);
+ if($buf != "")
+ {
+ # We got a reply! read the peer id from it.
+ $peer_id = substr($buf, 9, 2);
+
+ # Disconnect
+ $buf = "\x4f\x45\x74\x03".$peer_id."\x00\x00\x03";
+ socket_sendto($socket, $buf, strlen($buf), 0, $host, $port);
+ socket_close($socket);
+
+ return true;
+ }
+ return false;
+}
+
+- Here's a Python script for checking if a minetest server is up, confirmed working
+#!/usr/bin/env python
+import sys, time, socket
+address = ""
+port = 30000
+if len(sys.argv) <= 1:
+ print("Usage: %s <address>" % sys.argv[0])
+ exit()
+if ':' in sys.argv[1]:
+ address = sys.argv[1].split(':')[0]
+ try:
+ port = int(sys.argv[1].split(':')[1])
+ except ValueError:
+ print("Please specify a valid port")
+ exit()
+else:
+ address = sys.argv[1]
+
+try:
+ start = time.time()
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sock.settimeout(2.0)
+ buf = "\x4f\x45\x74\x03\x00\x00\x00\x01"
+ sock.sendto(buf, (address, port))
+ data, addr = sock.recvfrom(1000)
+ if data:
+ peer_id = data[12:14]
+ buf = "\x4f\x45\x74\x03" + peer_id + "\x00\x00\x03"
+ sock.sendto(buf, (address, port))
+ sock.close()
+ end = time.time()
+ print("%s is up (%0.5fms)" % (sys.argv[1],end-start))
+ else:
+ print("%s seems to be down " % sys.argv[1])
+except:
+ print("%s seems to be down " % sys.argv[1])
diff --git a/doc/texture_packs.txt b/doc/texture_packs.txt
new file mode 100644
index 000000000..1813c29e4
--- /dev/null
+++ b/doc/texture_packs.txt
@@ -0,0 +1,161 @@
+Minetest Texture Pack Reference
+===============================
+
+Texture packs allow you to replace textures provided by a mod with your own
+textures.
+
+Texture pack directory structure
+--------------------------------
+
+ textures
+ |-- Texture Pack
+ | |-- screenshot.png
+ | |-- description.txt
+ | |-- override.txt
+ | |-- your_texture_1.png
+ | |-- your_texture_2.png
+ `-- Another Texture Pack
+
+### Texture Pack
+This is a directory containing the entire contents of a single texture pack.
+It can be chosen more or less freely and will also become the name of the
+texture pack. The name must not be “base”.
+
+### `description.txt`
+A file containing a short description of the texture pack to be shown in the
+texture packs tab.
+
+### `screenshot.png`
+A preview image showing an in-game screenshot of this texture pack; it will be
+shown in the texture packs tab. It should have an aspect ratio of 3:2 and a
+minimum size of 300×200 pixels.
+
+### `your_texture_1.png`, `your_texture_2.png`, etc.
+Any other PNG files will be interpreted as textures. They must have the same
+names as the textures they are supposed to override. For example, to override
+the apple texture of Minetest Game, add a PNG file named `default_apple.png`.
+
+The custom textures do not necceessarily require the same size as their
+originals, but this might be required for a few particular textures. When
+unsure, just test your texture pack in-game.
+
+Texture modifiers
+-----------------
+
+See lua_api.txt for texture modifiers
+
+Special textures
+----------------
+
+These texture names are hardcoded into the engine but can also be overwritten
+by texture packs. All existing fallback textures can be found in the directory
+`textures/base/pack`.
+
+### Gameplay textures
+
+* `bubble.png`: the bubble texture when the player is drowning
+
+* `crack_anylength.png`: node overlay texture when digging
+
+* `crosshair.png`
+ * the crosshair texture in the center of the screen. The settings
+ `crosshair_color` and `crosshair_alpha` are used to create a cross
+ when no texture was found
+
+* `halo.png`: used for the node highlighting mesh
+
+* `heart.png`: used to display the health points of the player
+
+* `minimap_mask_round.png`: round minimap mask, white gets replaced by the map
+* `minimap_mask_square.png`: mask used for the square minimap
+* `minimap_overlay_round.png`: overlay texture for the round minimap
+* `minimap_overlay_square.png`: overlay texture for the square minimap
+* `object_marker_red.png`: texture for players on the minimap
+* `player_marker.png`: texture for the own player on the square minimap
+
+* `player.png`: front texture of the 2D upright sprite player
+* `player_back.png`: back texture of the 2D upright sprite player
+
+* `progress_bar.png`: foreground texture of the loading screen's progress bar
+* `progress_bar_bg.png`: background texture of the loading screen's progress bar
+
+* `moon.png`: texture of the moon. Default texture is generated by Minetest
+* `moon_tonemap.png`: tonemap to be used when `moon.png` was found
+* `sun.png`: texture of the sun. Default texture is generated by Minetest
+* `sun_tonemap.png`: tonemap to be used when `sun.png` was found
+* `sunrisebg.png`: shown sky texture when the sun rises
+
+* `smoke_puff.png`: texture used when an object died by punching
+
+* `unknown_item.png`: shown texture when an item definition was not found
+* `unknown_node.png`: shown texture when a node definition was not found
+* `unknown_object.png`: shown texture when an entity definition was not found
+
+* `wieldhand.png`: texture of the wieldhand
+
+### Mainmenu textures
+
+* `menu_bg.png`: used as mainmenu background when the clouds are disabled
+* `menu_header.png`: header texture when no texture pack is selected
+
+* `no_screenshot.png`
+ * texture when no screenshot was found for a texture pack or mod
+
+* `server_flags_creative.png`: icon for creative servers
+* `server_flags_damage.png`: icon for enabled damage on servers
+* `server_flags_favorite.png`: icon for your favorite servers
+* `server_flags_pvp.png`: icon for enabled PvP on servers
+
+### Android textures
+
+* `down_arrow.png`
+* `left_arrow.png`
+* `right_arrow.png`
+* `up_arrow.png`
+
+* `drop_btn.png`
+* `fast_btn.png`
+* `fly_btn.png`
+* `jump_btn.png`
+* `noclip_btn.png`
+
+* `camera_btn.png`
+* `chat_btn.png`
+* `inventory_btn.png`
+* `rangeview_btn.png`
+
+* `debug_btn.png`
+* `gear_icon.png`
+* `rare_controls.png`
+
+Texture Overrides
+-----------------
+
+You can override the textures of a node from a texture pack using
+texture overrides. To do this, create a file in a texture pack
+called override.txt
+
+Each line in an override.txt file is a rule. It consists of
+
+ nodename face-selector texture
+
+For example,
+
+ default:dirt_with_grass sides default_stone.png
+
+You can use ^ operators as usual:
+
+ default:dirt_with_grass sides default_stone.png^[brighten
+
+Here are face selectors you can choose from:
+
+| face-selector | behavior |
+|---------------|---------------------------------------------------|
+| left | x- |
+| right | x+ |
+| front | z- |
+| back | z+ |
+| top | y+ |
+| bottom | y- |
+| sides | x-, x+, z-, z+ |
+| all | All faces. You can also use '*' instead of 'all'. |
diff --git a/doc/world_format.txt b/doc/world_format.txt
new file mode 100644
index 000000000..976d14fd5
--- /dev/null
+++ b/doc/world_format.txt
@@ -0,0 +1,631 @@
+=============================
+Minetest World Format 22...27
+=============================
+
+This applies to a world format carrying the block serialization version
+22...27, used at least in
+- 0.4.dev-20120322 ... 0.4.dev-20120606 (22...23)
+- 0.4.0 (23)
+- 24 was never released as stable and existed for ~2 days
+- 27 was added in 0.4.15-dev
+
+The block serialization version does not fully specify every aspect of this
+format; if compliance with this format is to be checked, it needs to be
+done by detecting if the files and data indeed follows it.
+
+Legacy stuff
+=============
+Data can, in theory, be contained in the flat file directory structure
+described below in Version 17, but it is not officially supported. Also you
+may stumble upon all kinds of oddities in not-so-recent formats.
+
+Files
+======
+Everything is contained in a directory, the name of which is freeform, but
+often serves as the name of the world.
+
+Currently the authentication and ban data is stored on a per-world basis.
+It can be copied over from an old world to a newly created world.
+
+World
+|-- auth.txt ----- Authentication data
+|-- env_meta.txt - Environment metadata
+|-- ipban.txt ---- Banned ips/users
+|-- map_meta.txt - Map metadata
+|-- map.sqlite --- Map data
+|-- players ------ Player directory
+| |-- player1 -- Player file
+| '-- Foo ------ Player file
+`-- world.mt ----- World metadata
+
+auth.txt
+---------
+Contains authentication data, player per line.
+ <name>:<password hash>:<privilege1,...>
+
+Legacy format (until 0.4.12) of password hash is <name><password> SHA1'd,
+in the base64 encoding.
+
+Format (since 0.4.13) of password hash is #1#<salt>#<verifier>, with the
+parts inside <> encoded in the base64 encoding.
+<verifier> is an RFC 2945 compatible SRP verifier,
+of the given salt, password, and the player's name lowercased,
+using the 2048-bit group specified in RFC 5054 and the SHA-256 hash function.
+
+Example lines:
+- Player "celeron55", no password, privileges "interact" and "shout":
+ celeron55::interact,shout
+- Player "Foo", password "bar", privilege "shout", with a legacy password hash:
+ foo:iEPX+SQWIR3p67lj/0zigSWTKHg:shout
+- Player "Foo", password "bar", privilege "shout", with a 0.4.13 pw hash:
+ foo:#1#hPpy4O3IAn1hsNK00A6wNw#Kpu6rj7McsrPCt4euTb5RA5ltF7wdcWGoYMcRngwDi11cZhPuuR9i5Bo7o6A877TgcEwoc//HNrj9EjR/CGjdyTFmNhiermZOADvd8eu32FYK1kf7RMC0rXWxCenYuOQCG4WF9mMGiyTPxC63VAjAMuc1nCZzmy6D9zt0SIKxOmteI75pAEAIee2hx4OkSXRIiU4Zrxo1Xf7QFxkMY4x77vgaPcvfmuzom0y/fU1EdSnZeopGPvzMpFx80ODFx1P34R52nmVl0W8h4GNo0k8ZiWtRCdrJxs8xIg7z5P1h3Th/BJ0lwexpdK8sQZWng8xaO5ElthNuhO8UQx1l6FgEA:shout
+- Player "bar", no password, no privileges:
+ bar::
+
+env_meta.txt
+-------------
+Simple global environment variables.
+Example content (added indentation):
+ game_time = 73471
+ time_of_day = 19118
+ EnvArgsEnd
+
+ipban.txt
+----------
+Banned IP addresses and usernames.
+Example content (added indentation):
+ 123.456.78.9|foo
+ 123.456.78.10|bar
+
+map_meta.txt
+-------------
+Simple global map variables.
+Example content (added indentation):
+ seed = 7980462765762429666
+ [end_of_params]
+
+map.sqlite
+-----------
+Map data.
+See Map File Format below.
+
+player1, Foo
+-------------
+Player data.
+Filename can be anything.
+See Player File Format below.
+
+world.mt
+---------
+World metadata.
+Example content (added indentation):
+ gameid = mesetint
+
+Player File Format
+===================
+
+- Should be pretty self-explanatory.
+- Note: position is in nodes * 10
+
+Example content (added indentation):
+ hp = 11
+ name = celeron55
+ pitch = 39.77
+ position = (-5231.97,15,1961.41)
+ version = 1
+ yaw = 101.37
+ PlayerArgsEnd
+ List main 32
+ Item default:torch 13
+ Item default:pick_steel 1 50112
+ Item experimental:tnt
+ Item default:cobble 99
+ Item default:pick_stone 1 13104
+ Item default:shovel_steel 1 51838
+ Item default:dirt 61
+ Item default:rail 78
+ Item default:coal_lump 3
+ Item default:cobble 99
+ Item default:leaves 22
+ Item default:gravel 52
+ Item default:axe_steel 1 2045
+ Item default:cobble 98
+ Item default:sand 61
+ Item default:water_source 94
+ Item default:glass 2
+ Item default:mossycobble
+ Item default:pick_steel 1 64428
+ Item animalmaterials:bone
+ Item default:sword_steel
+ Item default:sapling
+ Item default:sword_stone 1 10647
+ Item default:dirt 99
+ Empty
+ Empty
+ Empty
+ Empty
+ Empty
+ Empty
+ Empty
+ Empty
+ EndInventoryList
+ List craft 9
+ Empty
+ Empty
+ Empty
+ Empty
+ Empty
+ Empty
+ Empty
+ Empty
+ Empty
+ EndInventoryList
+ List craftpreview 1
+ Empty
+ EndInventoryList
+ List craftresult 1
+ Empty
+ EndInventoryList
+ EndInventory
+
+Map File Format
+================
+
+Minetest maps consist of MapBlocks, chunks of 16x16x16 nodes.
+
+In addition to the bulk node data, MapBlocks stored on disk also contain
+other things.
+
+History
+--------
+We need a bit of history in here. Initially Minetest stored maps in a
+format called the "sectors" format. It was a directory/file structure like
+this:
+ sectors2/XXX/ZZZ/YYYY
+For example, the MapBlock at (0,1,-2) was this file:
+ sectors2/000/ffd/0001
+
+Eventually Minetest outgrow this directory structure, as filesystems were
+struggling under the amount of files and directories.
+
+Large servers seriously needed a new format, and thus the base of the
+current format was invented, suggested by celeron55 and implemented by
+JacobF.
+
+SQLite3 was slammed in, and blocks files were directly inserted as blobs
+in a single table, indexed by integer primary keys, oddly mangled from
+coordinates.
+
+Today we know that SQLite3 allows multiple primary keys (which would allow
+storing coordinates separately), but the format has been kept unchanged for
+that part. So, this is where it has come.
+</history>
+
+So here goes
+-------------
+map.sqlite is an sqlite3 database, containing a single table, called
+"blocks". It looks like this:
+
+ CREATE TABLE `blocks` (`pos` INT NOT NULL PRIMARY KEY,`data` BLOB);
+
+The key
+--------
+"pos" is created from the three coordinates of a MapBlock using this
+algorithm, defined here in Python:
+
+ def getBlockAsInteger(p):
+ return int64(p[2]*16777216 + p[1]*4096 + p[0])
+
+ def int64(u):
+ while u >= 2**63:
+ u -= 2**64
+ while u <= -2**63:
+ u += 2**64
+ return u
+
+It can be converted the other way by using this code:
+
+ def getIntegerAsBlock(i):
+ x = unsignedToSigned(i % 4096, 2048)
+ i = int((i - x) / 4096)
+ y = unsignedToSigned(i % 4096, 2048)
+ i = int((i - y) / 4096)
+ z = unsignedToSigned(i % 4096, 2048)
+ return x,y,z
+
+ def unsignedToSigned(i, max_positive):
+ if i < max_positive:
+ return i
+ else:
+ return i - 2*max_positive
+
+The blob
+---------
+The blob is the data that would have otherwise gone into the file.
+
+See below for description.
+
+MapBlock serialization format
+==============================
+NOTE: Byte order is MSB first (big-endian).
+NOTE: Zlib data is in such a format that Python's zlib at least can
+ directly decompress.
+
+u8 version
+- map format version number, see serialisation.h for the latest number
+
+u8 flags
+- Flag bitmasks:
+ - 0x01: is_underground: Should be set to 0 if there will be no light
+ obstructions above the block. If/when sunlight of a block is updated
+ and there is no block above it, this value is checked for determining
+ whether sunlight comes from the top.
+ - 0x02: day_night_differs: Whether the lighting of the block is different
+ on day and night. Only blocks that have this bit set are updated when
+ day transforms to night.
+ - 0x04: lighting_expired: Not used in version 27 and above. If true,
+ lighting is invalid and should be updated. If you can't calculate
+ lighting in your generator properly, you could try setting this 1 to
+ everything and setting the uppermost block in every sector as
+ is_underground=0. I am quite sure it doesn't work properly, though.
+ - 0x08: generated: True if the block has been generated. If false, block
+ is mostly filled with CONTENT_IGNORE and is likely to contain eg. parts
+ of trees of neighboring blocks.
+
+u16 lighting_complete
+- Added in version 27.
+- This contains 12 flags, each of them corresponds to a direction.
+- Indicates if the light is correct at the sides of a map block.
+ Lighting may not be correct if the light changed, but a neighbor
+ block was not loaded at that time.
+ If these flags are false, Minetest will automatically recompute light
+ when both this block and its required neighbor are loaded.
+- The bit order is:
+ nothing, nothing, nothing, nothing,
+ night X-, night Y-, night Z-, night Z+, night Y+, night X+,
+ day X-, day Y-, day Z-, day Z+, day Y+, day X+.
+ Where 'day' is for the day light bank, 'night' is for the night
+ light bank.
+ The 'nothing' bits should be always set, as they will be used
+ to indicate if direct sunlight spreading is finished.
+- Example: if the block at (0, 0, 0) has
+ lighting_complete = 0b1111111111111110,
+ then Minetest will correct lighting in the day light bank when
+ the block at (1, 0, 0) is also loaded.
+
+u8 content_width
+- Number of bytes in the content (param0) fields of nodes
+if map format version <= 23:
+ - Always 1
+if map format version >= 24:
+ - Always 2
+
+u8 params_width
+- Number of bytes used for parameters per node
+- Always 2
+
+zlib-compressed node data:
+if content_width == 1:
+ - content:
+ u8[4096]: param0 fields
+ u8[4096]: param1 fields
+ u8[4096]: param2 fields
+if content_width == 2:
+ - content:
+ u16[4096]: param0 fields
+ u8[4096]: param1 fields
+ u8[4096]: param2 fields
+- The location of a node in each of those arrays is (z*16*16 + y*16 + x).
+
+zlib-compressed node metadata list
+- content:
+if map format version <= 22:
+ u16 version (=1)
+ u16 count of metadata
+ foreach count:
+ u16 position (p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X)
+ u16 type_id
+ u16 content_size
+ u8[content_size] content of metadata. Format depends on type_id, see below.
+if map format version >= 23:
+ u8 version (=1) -- Note the type is u8, while for map format version <= 22 it's u16
+ u16 count of metadata
+ foreach count:
+ u16 position (p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X)
+ u32 num_vars
+ foreach num_vars:
+ u16 key_len
+ u8[key_len] key
+ u32 val_len
+ u8[val_len] value
+ serialized inventory
+
+- Node timers
+if map format version == 23:
+ u8 unused version (always 0)
+if map format version == 24: (NOTE: Not released as stable)
+ u8 nodetimer_version
+ if nodetimer_version == 0:
+ (nothing else)
+ if nodetimer_version == 1:
+ u16 num_of_timers
+ foreach num_of_timers:
+ u16 timer position (z*16*16 + y*16 + x)
+ s32 timeout*1000
+ s32 elapsed*1000
+if map format version >= 25:
+ -- Nothing right here, node timers are serialized later
+
+u8 static object version:
+- Always 0
+
+u16 static_object_count
+
+foreach static_object_count:
+ u8 type (object type-id)
+ s32 pos_x_nodes * 10000
+ s32 pos_y_nodes * 10000
+ s32 pos_z_nodes * 10000
+ u16 data_size
+ u8[data_size] data
+
+u32 timestamp
+- Timestamp when last saved, as seconds from starting the game.
+- 0xffffffff = invalid/unknown timestamp, nothing should be done with the time
+ difference when loaded
+
+u8 name-id-mapping version
+- Always 0
+
+u16 num_name_id_mappings
+
+foreach num_name_id_mappings
+ u16 id
+ u16 name_len
+ u8[name_len] name
+
+- Node timers
+if map format version == 25:
+ u8 length of the data of a single timer (always 2+4+4=10)
+ u16 num_of_timers
+ foreach num_of_timers:
+ u16 timer position (z*16*16 + y*16 + x)
+ s32 timeout*1000
+ s32 elapsed*1000
+
+EOF.
+
+Format of nodes
+----------------
+A node is composed of the u8 fields param0, param1 and param2.
+
+if map format version <= 23:
+ The content id of a node is determined as so:
+ - If param0 < 0x80,
+ content_id = param0
+ - Otherwise
+ content_id = (param0<<4) + (param2>>4)
+if map format version >= 24:
+ The content id of a node is param0.
+
+The purpose of param1 and param2 depend on the definition of the node.
+
+The name-id-mapping
+--------------------
+The mapping maps node content ids to node names.
+
+Node metadata format for map format versions <= 22
+---------------------------------------------------
+The node metadata are serialized depending on the type_id field.
+
+1: Generic metadata
+ serialized inventory
+ u32 len
+ u8[len] text
+ u16 len
+ u8[len] owner
+ u16 len
+ u8[len] infotext
+ u16 len
+ u8[len] inventory drawspec
+ u8 allow_text_input (bool)
+ u8 removal_disabled (bool)
+ u8 enforce_owner (bool)
+ u32 num_vars
+ foreach num_vars
+ u16 len
+ u8[len] name
+ u32 len
+ u8[len] value
+
+14: Sign metadata
+ u16 text_len
+ u8[text_len] text
+
+15: Chest metadata
+ serialized inventory
+
+16: Furnace metadata
+ TBD
+
+17: Locked Chest metadata
+ u16 len
+ u8[len] owner
+ serialized inventory
+
+Static objects
+---------------
+Static objects are persistent freely moving objects in the world.
+
+Object types:
+1: Test object
+2: Item
+3: Rat (deprecated)
+4: Oerkki (deprecated)
+5: Firefly (deprecated)
+6: MobV2 (deprecated)
+7: LuaEntity
+
+1: Item:
+ u8 version
+ version 0:
+ u16 len
+ u8[len] itemstring
+
+7: LuaEntity:
+ u8 version
+ version 1:
+ u16 len
+ u8[len] entity name
+ u32 len
+ u8[len] static data
+ s16 hp
+ s32 velocity.x * 10000
+ s32 velocity.y * 10000
+ s32 velocity.z * 10000
+ s32 yaw * 1000
+
+Itemstring format
+------------------
+eg. 'default:dirt 5'
+eg. 'default:pick_wood 21323'
+eg. '"default:apple" 2'
+eg. 'default:apple'
+- The wear value in tools is 0...65535
+- There are also a number of older formats that you might stumble upon:
+eg. 'node "default:dirt" 5'
+eg. 'NodeItem default:dirt 5'
+eg. 'ToolItem WPick 21323'
+
+Inventory serialization format
+-------------------------------
+- The inventory serialization format is line-based
+- The newline character used is "\n"
+- The end condition of a serialized inventory is always "EndInventory\n"
+- All the slots in a list must always be serialized.
+
+Example (format does not include "---"):
+---
+List foo 4
+Item default:sapling
+Item default:sword_stone 1 10647
+Item default:dirt 99
+Empty
+EndInventoryList
+List bar 9
+Empty
+Empty
+Empty
+Empty
+Empty
+Empty
+Empty
+Empty
+Empty
+EndInventoryList
+EndInventory
+---
+
+==============================================
+Minetest World Format used as of 2011-05 or so
+==============================================
+
+Map data serialization format version 17.
+
+0.3.1 does not use this format, but a more recent one. This exists here for
+historical reasons.
+
+Directory structure:
+sectors/XXXXZZZZ or sectors2/XXX/ZZZ
+XXXX, ZZZZ, XXX and ZZZ being the hexadecimal X and Z coordinates.
+Under these, the block files are stored, called YYYY.
+
+There also exists files map_meta.txt and chunk_meta, that are used by the
+generator. If they are not found or invalid, the generator will currently
+behave quite strangely.
+
+The MapBlock file format (sectors2/XXX/ZZZ/YYYY):
+-------------------------------------------------
+
+NOTE: Byte order is MSB first.
+
+u8 version
+- map format version number, this one is version 17
+
+u8 flags
+- Flag bitmasks:
+ - 0x01: is_underground: Should be set to 0 if there will be no light
+ obstructions above the block. If/when sunlight of a block is updated and
+ there is no block above it, this value is checked for determining whether
+ sunlight comes from the top.
+ - 0x02: day_night_differs: Whether the lighting of the block is different on
+ day and night. Only blocks that have this bit set are updated when day
+ transforms to night.
+ - 0x04: lighting_expired: If true, lighting is invalid and should be updated.
+ If you can't calculate lighting in your generator properly, you could try
+ setting this 1 to everything and setting the uppermost block in every
+ sector as is_underground=0. I am quite sure it doesn't work properly,
+ though.
+
+zlib-compressed map data:
+- content:
+ u8[4096]: content types
+ u8[4096]: param1 values
+ u8[4096]: param2 values
+
+zlib-compressed node metadata
+- content:
+ u16 version (=1)
+ u16 count of metadata
+ foreach count:
+ u16 position (= p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X)
+ u16 type_id
+ u16 content_size
+ u8[content_size] misc. stuff contained in the metadata
+
+u16 mapblockobject_count
+- always write as 0.
+- if read != 0, just fail.
+
+foreach mapblockobject_count:
+ - deprecated, should not be used. Length of this data can only be known by
+ properly parsing it. Just hope not to run into any of this.
+
+u8 static object version:
+- currently 0
+
+u16 static_object_count
+
+foreach static_object_count:
+ u8 type (object type-id)
+ s32 pos_x * 1000
+ s32 pos_y * 1000
+ s32 pos_z * 1000
+ u16 data_size
+ u8[data_size] data
+
+u32 timestamp
+- Timestamp when last saved, as seconds from starting the game.
+- 0xffffffff = invalid/unknown timestamp, nothing will be done with the time
+ difference when loaded (recommended)
+
+Node metadata format:
+---------------------
+
+Sign metadata:
+ u16 string_len
+ u8[string_len] string
+
+Furnace metadata:
+ TBD
+
+Chest metadata:
+ TBD
+
+Locking Chest metadata:
+ u16 string_len
+ u8[string_len] string
+ TBD
+
+// END
+
diff --git a/fonts/Arimo-LICENSE.txt b/fonts/Arimo-LICENSE.txt
new file mode 100644
index 000000000..d68578f6a
--- /dev/null
+++ b/fonts/Arimo-LICENSE.txt
@@ -0,0 +1,2 @@
+Arimo - Apache License, version 2.0
+Arimo-Regular.ttf: Digitized data copyright (c) 2010-2012 Google Corporation.
diff --git a/fonts/Arimo-Regular.ttf b/fonts/Arimo-Regular.ttf
new file mode 100644
index 000000000..9be443c7d
--- /dev/null
+++ b/fonts/Arimo-Regular.ttf
Binary files differ
diff --git a/fonts/Cousine-LICENSE.txt b/fonts/Cousine-LICENSE.txt
new file mode 100644
index 000000000..c84465f01
--- /dev/null
+++ b/fonts/Cousine-LICENSE.txt
@@ -0,0 +1,2 @@
+Cousine - Apache License, version 2.0
+Cousine-Regular.ttf: Digitized data copyright (c) 2010-2012 Google Corporation.
diff --git a/fonts/Cousine-Regular.ttf b/fonts/Cousine-Regular.ttf
new file mode 100644
index 000000000..4d6990a25
--- /dev/null
+++ b/fonts/Cousine-Regular.ttf
Binary files differ
diff --git a/fonts/DroidSansFallbackFull-LICENSE.txt b/fonts/DroidSansFallbackFull-LICENSE.txt
new file mode 100644
index 000000000..d3a2eaa8c
--- /dev/null
+++ b/fonts/DroidSansFallbackFull-LICENSE.txt
@@ -0,0 +1,13 @@
+Copyright (C) 2008 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/fonts/DroidSansFallbackFull.ttf b/fonts/DroidSansFallbackFull.ttf
new file mode 100644
index 000000000..a9df00585
--- /dev/null
+++ b/fonts/DroidSansFallbackFull.ttf
Binary files differ
diff --git a/fonts/mono_dejavu_sans_10.xml b/fonts/mono_dejavu_sans_10.xml
new file mode 100644
index 000000000..0276cedb6
--- /dev/null
+++ b/fonts/mono_dejavu_sans_10.xml
Binary files differ
diff --git a/fonts/mono_dejavu_sans_100.png b/fonts/mono_dejavu_sans_100.png
new file mode 100644
index 000000000..45a312542
--- /dev/null
+++ b/fonts/mono_dejavu_sans_100.png
Binary files differ
diff --git a/fonts/mono_dejavu_sans_11.xml b/fonts/mono_dejavu_sans_11.xml
new file mode 100644
index 000000000..f727ed2bb
--- /dev/null
+++ b/fonts/mono_dejavu_sans_11.xml
Binary files differ
diff --git a/fonts/mono_dejavu_sans_110.png b/fonts/mono_dejavu_sans_110.png
new file mode 100644
index 000000000..c90c0e242
--- /dev/null
+++ b/fonts/mono_dejavu_sans_110.png
Binary files differ
diff --git a/fonts/mono_dejavu_sans_12.xml b/fonts/mono_dejavu_sans_12.xml
new file mode 100644
index 000000000..38f6427be
--- /dev/null
+++ b/fonts/mono_dejavu_sans_12.xml
Binary files differ
diff --git a/fonts/mono_dejavu_sans_120.png b/fonts/mono_dejavu_sans_120.png
new file mode 100644
index 000000000..0cebd70e8
--- /dev/null
+++ b/fonts/mono_dejavu_sans_120.png
Binary files differ
diff --git a/fonts/mono_dejavu_sans_14.xml b/fonts/mono_dejavu_sans_14.xml
new file mode 100644
index 000000000..b90a34960
--- /dev/null
+++ b/fonts/mono_dejavu_sans_14.xml
Binary files differ
diff --git a/fonts/mono_dejavu_sans_140.png b/fonts/mono_dejavu_sans_140.png
new file mode 100644
index 000000000..a413759ea
--- /dev/null
+++ b/fonts/mono_dejavu_sans_140.png
Binary files differ
diff --git a/fonts/mono_dejavu_sans_16.xml b/fonts/mono_dejavu_sans_16.xml
new file mode 100644
index 000000000..3f7d2c2a2
--- /dev/null
+++ b/fonts/mono_dejavu_sans_16.xml
Binary files differ
diff --git a/fonts/mono_dejavu_sans_160.png b/fonts/mono_dejavu_sans_160.png
new file mode 100644
index 000000000..bd8a2f40a
--- /dev/null
+++ b/fonts/mono_dejavu_sans_160.png
Binary files differ
diff --git a/fonts/mono_dejavu_sans_18.xml b/fonts/mono_dejavu_sans_18.xml
new file mode 100644
index 000000000..92865cbfc
--- /dev/null
+++ b/fonts/mono_dejavu_sans_18.xml
Binary files differ
diff --git a/fonts/mono_dejavu_sans_180.png b/fonts/mono_dejavu_sans_180.png
new file mode 100644
index 000000000..a299afcbe
--- /dev/null
+++ b/fonts/mono_dejavu_sans_180.png
Binary files differ
diff --git a/fonts/mono_dejavu_sans_20.xml b/fonts/mono_dejavu_sans_20.xml
new file mode 100644
index 000000000..acd8c77d0
--- /dev/null
+++ b/fonts/mono_dejavu_sans_20.xml
Binary files differ
diff --git a/fonts/mono_dejavu_sans_200.png b/fonts/mono_dejavu_sans_200.png
new file mode 100644
index 000000000..68ee62681
--- /dev/null
+++ b/fonts/mono_dejavu_sans_200.png
Binary files differ
diff --git a/fonts/mono_dejavu_sans_22.xml b/fonts/mono_dejavu_sans_22.xml
new file mode 100644
index 000000000..eafb4def6
--- /dev/null
+++ b/fonts/mono_dejavu_sans_22.xml
Binary files differ
diff --git a/fonts/mono_dejavu_sans_220.png b/fonts/mono_dejavu_sans_220.png
new file mode 100644
index 000000000..042d7e094
--- /dev/null
+++ b/fonts/mono_dejavu_sans_220.png
Binary files differ
diff --git a/fonts/mono_dejavu_sans_24.xml b/fonts/mono_dejavu_sans_24.xml
new file mode 100644
index 000000000..fc8b6232e
--- /dev/null
+++ b/fonts/mono_dejavu_sans_24.xml
Binary files differ
diff --git a/fonts/mono_dejavu_sans_240.png b/fonts/mono_dejavu_sans_240.png
new file mode 100644
index 000000000..d2d68c5bb
--- /dev/null
+++ b/fonts/mono_dejavu_sans_240.png
Binary files differ
diff --git a/fonts/mono_dejavu_sans_26.xml b/fonts/mono_dejavu_sans_26.xml
new file mode 100644
index 000000000..829f09948
--- /dev/null
+++ b/fonts/mono_dejavu_sans_26.xml
Binary files differ
diff --git a/fonts/mono_dejavu_sans_260.png b/fonts/mono_dejavu_sans_260.png
new file mode 100644
index 000000000..3a8cb6c57
--- /dev/null
+++ b/fonts/mono_dejavu_sans_260.png
Binary files differ
diff --git a/fonts/mono_dejavu_sans_28.xml b/fonts/mono_dejavu_sans_28.xml
new file mode 100644
index 000000000..b5b25bd07
--- /dev/null
+++ b/fonts/mono_dejavu_sans_28.xml
Binary files differ
diff --git a/fonts/mono_dejavu_sans_280.png b/fonts/mono_dejavu_sans_280.png
new file mode 100644
index 000000000..ccf62ba48
--- /dev/null
+++ b/fonts/mono_dejavu_sans_280.png
Binary files differ
diff --git a/fonts/mono_dejavu_sans_4.xml b/fonts/mono_dejavu_sans_4.xml
new file mode 100644
index 000000000..cfebb39b3
--- /dev/null
+++ b/fonts/mono_dejavu_sans_4.xml
Binary files differ
diff --git a/fonts/mono_dejavu_sans_40.png b/fonts/mono_dejavu_sans_40.png
new file mode 100644
index 000000000..24ed693f7
--- /dev/null
+++ b/fonts/mono_dejavu_sans_40.png
Binary files differ
diff --git a/fonts/mono_dejavu_sans_6.xml b/fonts/mono_dejavu_sans_6.xml
new file mode 100644
index 000000000..d0e1de21d
--- /dev/null
+++ b/fonts/mono_dejavu_sans_6.xml
Binary files differ
diff --git a/fonts/mono_dejavu_sans_60.png b/fonts/mono_dejavu_sans_60.png
new file mode 100644
index 000000000..326af996f
--- /dev/null
+++ b/fonts/mono_dejavu_sans_60.png
Binary files differ
diff --git a/fonts/mono_dejavu_sans_8.xml b/fonts/mono_dejavu_sans_8.xml
new file mode 100644
index 000000000..c48bf7ccc
--- /dev/null
+++ b/fonts/mono_dejavu_sans_8.xml
Binary files differ
diff --git a/fonts/mono_dejavu_sans_80.png b/fonts/mono_dejavu_sans_80.png
new file mode 100644
index 000000000..04326dbb2
--- /dev/null
+++ b/fonts/mono_dejavu_sans_80.png
Binary files differ
diff --git a/fonts/mono_dejavu_sans_9.xml b/fonts/mono_dejavu_sans_9.xml
new file mode 100644
index 000000000..74e841034
--- /dev/null
+++ b/fonts/mono_dejavu_sans_9.xml
Binary files differ
diff --git a/fonts/mono_dejavu_sans_90.png b/fonts/mono_dejavu_sans_90.png
new file mode 100644
index 000000000..65ac51858
--- /dev/null
+++ b/fonts/mono_dejavu_sans_90.png
Binary files differ
diff --git a/games/minimal/game.conf b/games/minimal/game.conf
new file mode 100644
index 000000000..99bfaf0a4
--- /dev/null
+++ b/games/minimal/game.conf
@@ -0,0 +1,2 @@
+name = Minimal development test
+
diff --git a/games/minimal/menu/background.png b/games/minimal/menu/background.png
new file mode 100644
index 000000000..ea5fbdce5
--- /dev/null
+++ b/games/minimal/menu/background.png
Binary files differ
diff --git a/games/minimal/menu/icon.png b/games/minimal/menu/icon.png
new file mode 100644
index 000000000..8ef675023
--- /dev/null
+++ b/games/minimal/menu/icon.png
Binary files differ
diff --git a/games/minimal/mods/bucket/depends.txt b/games/minimal/mods/bucket/depends.txt
new file mode 100644
index 000000000..3a7daa1d7
--- /dev/null
+++ b/games/minimal/mods/bucket/depends.txt
@@ -0,0 +1,2 @@
+default
+
diff --git a/games/minimal/mods/bucket/init.lua b/games/minimal/mods/bucket/init.lua
new file mode 100644
index 000000000..dcd59ed38
--- /dev/null
+++ b/games/minimal/mods/bucket/init.lua
@@ -0,0 +1,95 @@
+-- bucket (Minetest 0.4 mod)
+-- A bucket, which can pick up water and lava
+
+minetest.register_alias("bucket", "bucket:bucket_empty")
+minetest.register_alias("bucket_water", "bucket:bucket_water")
+minetest.register_alias("bucket_lava", "bucket:bucket_lava")
+
+minetest.register_craft({
+ output = 'bucket:bucket_empty 1',
+ recipe = {
+ {'default:steel_ingot', '', 'default:steel_ingot'},
+ {'', 'default:steel_ingot', ''},
+ }
+})
+
+bucket = {}
+bucket.liquids = {}
+
+-- Register a new liquid
+-- source = name of the source node
+-- flowing = name of the flowing node
+-- itemname = name of the new bucket item (or nil if liquid is not takeable)
+-- inventory_image = texture of the new bucket item (ignored if itemname == nil)
+-- This function can be called from any mod (that depends on bucket).
+function bucket.register_liquid(source, flowing, itemname, inventory_image)
+ bucket.liquids[source] = {
+ source = source,
+ flowing = flowing,
+ itemname = itemname,
+ }
+ bucket.liquids[flowing] = bucket.liquids[source]
+
+ if itemname ~= nil then
+ minetest.register_craftitem(itemname, {
+ inventory_image = inventory_image,
+ stack_max = 1,
+ liquids_pointable = true,
+ on_use = function(itemstack, user, pointed_thing)
+ -- Must be pointing to node
+ if pointed_thing.type ~= "node" then
+ return
+ end
+ -- Check if pointing to a liquid
+ n = minetest.get_node(pointed_thing.under)
+ if bucket.liquids[n.name] == nil then
+ -- Not a liquid
+ minetest.add_node(pointed_thing.above, {name=source})
+ elseif n.name ~= source then
+ -- It's a liquid
+ minetest.add_node(pointed_thing.under, {name=source})
+ end
+ return {name="bucket:bucket_empty"}
+ end
+ })
+ end
+end
+
+minetest.register_craftitem("bucket:bucket_empty", {
+ inventory_image = "bucket.png",
+ stack_max = 1,
+ liquids_pointable = true,
+ on_use = function(itemstack, user, pointed_thing)
+ -- Must be pointing to node
+ if pointed_thing.type ~= "node" then
+ return
+ end
+ -- Check if pointing to a liquid source
+ n = minetest.get_node(pointed_thing.under)
+ liquiddef = bucket.liquids[n.name]
+ if liquiddef ~= nil and liquiddef.source == n.name and liquiddef.itemname ~= nil then
+ minetest.add_node(pointed_thing.under, {name="air"})
+ return {name=liquiddef.itemname}
+ end
+ end,
+})
+
+bucket.register_liquid(
+ "default:water_source",
+ "default:water_flowing",
+ "bucket:bucket_water",
+ "bucket_water.png"
+)
+
+bucket.register_liquid(
+ "default:lava_source",
+ "default:lava_flowing",
+ "bucket:bucket_lava",
+ "bucket_lava.png"
+)
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "bucket:bucket_lava",
+ burntime = 60,
+})
diff --git a/games/minimal/mods/bucket/textures/bucket.png b/games/minimal/mods/bucket/textures/bucket.png
new file mode 100644
index 000000000..b775a9fd3
--- /dev/null
+++ b/games/minimal/mods/bucket/textures/bucket.png
Binary files differ
diff --git a/games/minimal/mods/bucket/textures/bucket_lava.png b/games/minimal/mods/bucket/textures/bucket_lava.png
new file mode 100644
index 000000000..889ed61d4
--- /dev/null
+++ b/games/minimal/mods/bucket/textures/bucket_lava.png
Binary files differ
diff --git a/games/minimal/mods/bucket/textures/bucket_water.png b/games/minimal/mods/bucket/textures/bucket_water.png
new file mode 100644
index 000000000..a3c9d72f7
--- /dev/null
+++ b/games/minimal/mods/bucket/textures/bucket_water.png
Binary files differ
diff --git a/games/minimal/mods/default/init.lua b/games/minimal/mods/default/init.lua
new file mode 100644
index 000000000..bfd938211
--- /dev/null
+++ b/games/minimal/mods/default/init.lua
@@ -0,0 +1,1898 @@
+-- default (Minetest 0.4 mod)
+-- Most default stuff
+
+-- The API documentation in here was moved into doc/lua_api.txt
+
+WATER_ALPHA = 160
+WATER_VISC = 1
+LAVA_VISC = 7
+LIGHT_MAX = 14
+
+-- Definitions made by this mod that other mods can use too
+default = {}
+
+-- Load other files
+dofile(minetest.get_modpath("default").."/mapgen.lua")
+
+-- Set a noticeable inventory formspec for players
+minetest.register_on_joinplayer(function(player)
+ local cb = function(player)
+ minetest.chat_send_player(player:get_player_name(), "This is the [minimal] \"Minimal Development Test\" game. Use [minetest_game] for the real thing.")
+ player:set_attribute("test_attribute", "test_me")
+ player:set_attribute("remove_this", nil)
+ end
+ minetest.after(2.0, cb, player)
+end)
+
+--
+-- Tool definition
+--
+
+-- The hand
+minetest.register_item(":", {
+ type = "none",
+ wield_image = "wieldhand.png",
+ wield_scale = {x=1,y=1,z=2.5},
+ tool_capabilities = {
+ full_punch_interval = 1.0,
+ max_drop_level = 0,
+ groupcaps = {
+ fleshy = {times={[2]=2.00, [3]=1.00}, uses=0, maxlevel=1},
+ crumbly = {times={[2]=3.00, [3]=0.70}, uses=0, maxlevel=1},
+ snappy = {times={[3]=0.40}, uses=0, maxlevel=1},
+ oddly_breakable_by_hand = {times={[1]=7.00,[2]=4.00,[3]=1.40}, uses=0, maxlevel=3},
+ },
+ damage_groups = {fleshy=1},
+ }
+})
+
+--
+-- Picks
+--
+
+minetest.register_tool("default:pick_wood", {
+ description = "Wooden Pickaxe",
+ inventory_image = "default_tool_woodpick.png",
+ tool_capabilities = {
+ max_drop_level=0,
+ groupcaps={
+ cracky={times={[2]=2.00, [3]=1.20}, uses=10, maxlevel=1}
+ },
+ damage_groups = {fleshy=2},
+ },
+})
+minetest.register_tool("default:pick_stone", {
+ description = "Stone Pickaxe",
+ inventory_image = "default_tool_stonepick.png",
+ tool_capabilities = {
+ max_drop_level=0,
+ groupcaps={
+ cracky={times={[1]=2.00, [2]=1.20, [3]=0.80}, uses=20, maxlevel=1}
+ },
+ damage_groups = {fleshy=3},
+ },
+})
+minetest.register_tool("default:pick_steel", {
+ description = "Steel Pickaxe",
+ inventory_image = "default_tool_steelpick.png",
+ tool_capabilities = {
+ max_drop_level=1,
+ groupcaps={
+ cracky={times={[1]=4.00, [2]=1.60, [3]=1.00}, uses=10, maxlevel=2}
+ },
+ damage_groups = {fleshy=4},
+ },
+})
+minetest.register_tool("default:pick_mese", {
+ description = "Mese Pickaxe",
+ inventory_image = "default_tool_mesepick.png",
+ tool_capabilities = {
+ full_punch_interval = 1.0,
+ max_drop_level=3,
+ groupcaps={
+ cracky={times={[1]=2.0, [2]=1.0, [3]=0.5}, uses=20, maxlevel=3},
+ crumbly={times={[1]=2.0, [2]=1.0, [3]=0.5}, uses=20, maxlevel=3},
+ snappy={times={[1]=2.0, [2]=1.0, [3]=0.5}, uses=20, maxlevel=3}
+ },
+ damage_groups = {fleshy=4},
+ },
+})
+
+--
+-- Shovels
+--
+
+minetest.register_tool("default:shovel_wood", {
+ description = "Wooden Shovel",
+ inventory_image = "default_tool_woodshovel.png",
+ tool_capabilities = {
+ max_drop_level=0,
+ groupcaps={
+ crumbly={times={[1]=2.00, [2]=0.80, [3]=0.50}, uses=10, maxlevel=1}
+ },
+ damage_groups = {fleshy=2},
+ },
+})
+minetest.register_tool("default:shovel_stone", {
+ description = "Stone Shovel",
+ inventory_image = "default_tool_stoneshovel.png",
+ tool_capabilities = {
+ max_drop_level=0,
+ groupcaps={
+ crumbly={times={[1]=1.20, [2]=0.50, [3]=0.30}, uses=20, maxlevel=1}
+ },
+ damage_groups = {fleshy=3},
+ },
+})
+minetest.register_tool("default:shovel_steel", {
+ description = "Steel Shovel",
+ inventory_image = "default_tool_steelshovel.png",
+ tool_capabilities = {
+ max_drop_level=1,
+ groupcaps={
+ crumbly={times={[1]=1.00, [2]=0.70, [3]=0.60}, uses=10, maxlevel=2}
+ },
+ damage_groups = {fleshy=4},
+ },
+})
+
+--
+-- Axes
+--
+
+minetest.register_tool("default:axe_wood", {
+ description = "Wooden Axe",
+ inventory_image = "default_tool_woodaxe.png",
+ tool_capabilities = {
+ max_drop_level=0,
+ groupcaps={
+ choppy={times={[2]=1.40, [3]=0.80}, uses=10, maxlevel=1},
+ fleshy={times={[2]=1.50, [3]=0.80}, uses=10, maxlevel=1}
+ },
+ damage_groups = {fleshy=2},
+ },
+})
+minetest.register_tool("default:axe_stone", {
+ description = "Stone Axe",
+ inventory_image = "default_tool_stoneaxe.png",
+ tool_capabilities = {
+ max_drop_level=0,
+ groupcaps={
+ choppy={times={[1]=1.50, [2]=1.00, [3]=0.60}, uses=20, maxlevel=1},
+ fleshy={times={[2]=1.30, [3]=0.70}, uses=20, maxlevel=1}
+ },
+ damage_groups = {fleshy=3},
+ },
+})
+minetest.register_tool("default:axe_steel", {
+ description = "Steel Axe",
+ inventory_image = "default_tool_steelaxe.png",
+ tool_capabilities = {
+ max_drop_level=1,
+ groupcaps={
+ choppy={times={[1]=2.00, [2]=1.60, [3]=1.00}, uses=10, maxlevel=2},
+ fleshy={times={[2]=1.10, [3]=0.60}, uses=40, maxlevel=1}
+ },
+ damage_groups = {fleshy=3},
+ },
+})
+
+--
+-- Swords
+--
+
+minetest.register_tool("default:sword_wood", {
+ description = "Wooden Sword",
+ inventory_image = "default_tool_woodsword.png",
+ tool_capabilities = {
+ full_punch_interval = 1.0,
+ max_drop_level=0,
+ groupcaps={
+ fleshy={times={[2]=1.10, [3]=0.60}, uses=10, maxlevel=1},
+ snappy={times={[2]=1.00, [3]=0.50}, uses=10, maxlevel=1},
+ choppy={times={[3]=1.00}, uses=20, maxlevel=0}
+ },
+ damage_groups = {fleshy=2},
+ }
+})
+minetest.register_tool("default:sword_stone", {
+ description = "Stone Sword",
+ inventory_image = "default_tool_stonesword.png",
+ tool_capabilities = {
+ full_punch_interval = 1.0,
+ max_drop_level=0,
+ groupcaps={
+ fleshy={times={[2]=0.80, [3]=0.40}, uses=20, maxlevel=1},
+ snappy={times={[2]=0.80, [3]=0.40}, uses=20, maxlevel=1},
+ choppy={times={[3]=0.90}, uses=20, maxlevel=0}
+ },
+ damage_groups = {fleshy=4},
+ }
+})
+minetest.register_tool("default:sword_steel", {
+ description = "Steel Sword",
+ inventory_image = "default_tool_steelsword.png",
+ tool_capabilities = {
+ full_punch_interval = 1.0,
+ max_drop_level=1,
+ groupcaps={
+ fleshy={times={[1]=2.00, [2]=0.80, [3]=0.40}, uses=10, maxlevel=2},
+ snappy={times={[2]=0.70, [3]=0.30}, uses=40, maxlevel=1},
+ choppy={times={[3]=0.70}, uses=40, maxlevel=0}
+ },
+ damage_groups = {fleshy=6},
+ }
+})
+
+--
+-- Crafting definition
+--
+
+minetest.register_craft({
+ output = 'default:wood 4',
+ recipe = {
+ {'default:tree'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:stick 4',
+ recipe = {
+ {'default:wood'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:fence_wood 2',
+ recipe = {
+ {'default:stick', 'default:stick', 'default:stick'},
+ {'default:stick', 'default:stick', 'default:stick'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:sign_wall',
+ recipe = {
+ {'default:wood', 'default:wood', 'default:wood'},
+ {'default:wood', 'default:wood', 'default:wood'},
+ {'', 'default:stick', ''},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:torch 4',
+ recipe = {
+ {'default:coal_lump'},
+ {'default:stick'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:pick_wood',
+ recipe = {
+ {'default:wood', 'default:wood', 'default:wood'},
+ {'', 'default:stick', ''},
+ {'', 'default:stick', ''},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:pick_stone',
+ recipe = {
+ {'default:cobble', 'default:cobble', 'default:cobble'},
+ {'', 'default:stick', ''},
+ {'', 'default:stick', ''},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:pick_steel',
+ recipe = {
+ {'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'},
+ {'', 'default:stick', ''},
+ {'', 'default:stick', ''},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:pick_mese',
+ recipe = {
+ {'default:mese', 'default:mese', 'default:mese'},
+ {'', 'default:stick', ''},
+ {'', 'default:stick', ''},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:shovel_wood',
+ recipe = {
+ {'default:wood'},
+ {'default:stick'},
+ {'default:stick'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:shovel_stone',
+ recipe = {
+ {'default:cobble'},
+ {'default:stick'},
+ {'default:stick'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:shovel_steel',
+ recipe = {
+ {'default:steel_ingot'},
+ {'default:stick'},
+ {'default:stick'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:axe_wood',
+ recipe = {
+ {'default:wood', 'default:wood'},
+ {'default:wood', 'default:stick'},
+ {'', 'default:stick'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:axe_stone',
+ recipe = {
+ {'default:cobble', 'default:cobble'},
+ {'default:cobble', 'default:stick'},
+ {'', 'default:stick'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:axe_steel',
+ recipe = {
+ {'default:steel_ingot', 'default:steel_ingot'},
+ {'default:steel_ingot', 'default:stick'},
+ {'', 'default:stick'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:sword_wood',
+ recipe = {
+ {'default:wood'},
+ {'default:wood'},
+ {'default:stick'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:sword_stone',
+ recipe = {
+ {'default:cobble'},
+ {'default:cobble'},
+ {'default:stick'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:sword_steel',
+ recipe = {
+ {'default:steel_ingot'},
+ {'default:steel_ingot'},
+ {'default:stick'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:rail 15',
+ recipe = {
+ {'default:steel_ingot', '', 'default:steel_ingot'},
+ {'default:steel_ingot', 'default:stick', 'default:steel_ingot'},
+ {'default:steel_ingot', '', 'default:steel_ingot'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:chest',
+ recipe = {
+ {'default:wood', 'default:wood', 'default:wood'},
+ {'default:wood', '', 'default:wood'},
+ {'default:wood', 'default:wood', 'default:wood'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:chest_locked',
+ recipe = {
+ {'default:wood', 'default:wood', 'default:wood'},
+ {'default:wood', 'default:steel_ingot', 'default:wood'},
+ {'default:wood', 'default:wood', 'default:wood'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:furnace',
+ recipe = {
+ {'default:cobble', 'default:cobble', 'default:cobble'},
+ {'default:cobble', '', 'default:cobble'},
+ {'default:cobble', 'default:cobble', 'default:cobble'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:steelblock',
+ recipe = {
+ {'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'},
+ {'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'},
+ {'default:steel_ingot', 'default:steel_ingot', 'default:steel_ingot'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:sandstone',
+ recipe = {
+ {'default:sand', 'default:sand'},
+ {'default:sand', 'default:sand'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:clay',
+ recipe = {
+ {'default:clay_lump', 'default:clay_lump'},
+ {'default:clay_lump', 'default:clay_lump'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:brick',
+ recipe = {
+ {'default:clay_brick', 'default:clay_brick'},
+ {'default:clay_brick', 'default:clay_brick'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:paper',
+ recipe = {
+ {'default:papyrus', 'default:papyrus', 'default:papyrus'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:book',
+ recipe = {
+ {'default:paper'},
+ {'default:paper'},
+ {'default:paper'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:bookshelf',
+ recipe = {
+ {'default:wood', 'default:wood', 'default:wood'},
+ {'default:book', 'default:book', 'default:book'},
+ {'default:wood', 'default:wood', 'default:wood'},
+ }
+})
+
+minetest.register_craft({
+ output = 'default:ladder',
+ recipe = {
+ {'default:stick', '', 'default:stick'},
+ {'default:stick', 'default:stick', 'default:stick'},
+ {'default:stick', '', 'default:stick'},
+ }
+})
+
+-- Tool repair
+minetest.register_craft({
+ type = "toolrepair",
+ additional_wear = -0.02,
+})
+
+--
+-- Cooking recipes
+--
+
+minetest.register_craft({
+ type = "cooking",
+ output = "default:glass",
+ recipe = "default:sand",
+})
+
+minetest.register_craft({
+ type = "cooking",
+ output = "default:coal_lump",
+ recipe = "default:tree",
+})
+
+minetest.register_craft({
+ type = "cooking",
+ output = "default:stone",
+ recipe = "default:cobble",
+})
+
+minetest.register_craft({
+ type = "cooking",
+ output = "default:steel_ingot",
+ recipe = "default:iron_lump",
+})
+
+minetest.register_craft({
+ type = "cooking",
+ output = "default:clay_brick",
+ recipe = "default:clay_lump",
+})
+
+--
+-- Fuels
+--
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:tree",
+ burntime = 30,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:junglegrass",
+ burntime = 2,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:leaves",
+ burntime = 1,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:cactus",
+ burntime = 15,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:papyrus",
+ burntime = 1,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:bookshelf",
+ burntime = 30,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:fence_wood",
+ burntime = 15,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:ladder",
+ burntime = 5,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:wood",
+ burntime = 7,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:mese",
+ burntime = 30,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:lava_source",
+ burntime = 60,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:torch",
+ burntime = 4,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:sign_wall",
+ burntime = 10,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:chest",
+ burntime = 30,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:chest_locked",
+ burntime = 30,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:nyancat",
+ burntime = 1,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:nyancat_rainbow",
+ burntime = 1,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:sapling",
+ burntime = 10,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:apple",
+ burntime = 3,
+})
+
+minetest.register_craft({
+ type = "fuel",
+ recipe = "default:coal_lump",
+ burntime = 40,
+})
+
+--
+-- Node definitions
+--
+
+-- Default node sounds
+
+function default.node_sound_defaults(table)
+ table = table or {}
+ table.footstep = table.footstep or
+ {name="", gain=1.0}
+ table.dug = table.dug or
+ {name="default_dug_node", gain=1.0}
+ return table
+end
+
+function default.node_sound_stone_defaults(table)
+ table = table or {}
+ table.footstep = table.footstep or
+ {name="default_hard_footstep", gain=0.2}
+ default.node_sound_defaults(table)
+ return table
+end
+
+function default.node_sound_dirt_defaults(table)
+ table = table or {}
+ table.footstep = table.footstep or
+ {name="", gain=0.5}
+ --table.dug = table.dug or
+ -- {name="default_dirt_break", gain=0.5}
+ table.place = table.place or
+ {name="default_grass_footstep", gain=0.5}
+ default.node_sound_defaults(table)
+ return table
+end
+
+function default.node_sound_sand_defaults(table)
+ table = table or {}
+ table.footstep = table.footstep or
+ {name="default_grass_footstep", gain=0.25}
+ --table.dug = table.dug or
+ -- {name="default_dirt_break", gain=0.25}
+ table.dug = table.dug or
+ {name="", gain=0.25}
+ default.node_sound_defaults(table)
+ return table
+end
+
+function default.node_sound_wood_defaults(table)
+ table = table or {}
+ table.footstep = table.footstep or
+ {name="default_hard_footstep", gain=0.3}
+ default.node_sound_defaults(table)
+ return table
+end
+
+function default.node_sound_leaves_defaults(table)
+ table = table or {}
+ table.footstep = table.footstep or
+ {name="default_grass_footstep", gain=0.25}
+ table.dig = table.dig or
+ {name="default_dig_crumbly", gain=0.4}
+ table.dug = table.dug or
+ {name="", gain=1.0}
+ default.node_sound_defaults(table)
+ return table
+end
+
+function default.node_sound_glass_defaults(table)
+ table = table or {}
+ table.footstep = table.footstep or
+ {name="default_stone_footstep", gain=0.25}
+ table.dug = table.dug or
+ {name="default_break_glass", gain=1.0}
+ default.node_sound_defaults(table)
+ return table
+end
+
+-- Register nodes
+
+minetest.register_node("default:stone", {
+ description = "Stone",
+ tiles ={"default_stone.png"},
+ groups = {cracky=3},
+ drop = 'default:cobble',
+ legacy_mineral = true,
+ sounds = default.node_sound_stone_defaults(),
+})
+
+minetest.register_node("default:stone_with_coal", {
+ description = "Stone with coal",
+ tiles ={"default_stone.png^default_mineral_coal.png"},
+ groups = {cracky=3},
+ drop = 'default:coal_lump',
+ sounds = default.node_sound_stone_defaults(),
+})
+
+minetest.register_node("default:stone_with_iron", {
+ description = "Stone with iron",
+ tiles ={"default_stone.png^default_mineral_iron.png"},
+ groups = {cracky=3},
+ drop = 'default:iron_lump',
+ sounds = default.node_sound_stone_defaults(),
+})
+
+minetest.register_node("default:dirt_with_grass", {
+ description = "Dirt with grass",
+ tiles ={"default_grass.png", "default_dirt.png",
+ {name = "default_dirt.png^default_grass_side.png",
+ tileable_vertical = false}},
+ groups = {crumbly=3, soil=1},
+ drop = 'default:dirt',
+ sounds = default.node_sound_dirt_defaults({
+ footstep = {name="default_grass_footstep", gain=0.4},
+ }),
+})
+
+minetest.register_node("default:dirt_with_grass_footsteps", {
+ description = "Dirt with grass and footsteps",
+ tiles ={"default_grass_footsteps.png", "default_dirt.png",
+ {name = "default_dirt.png^default_grass_side.png",
+ tileable_vertical = false}},
+ groups = {crumbly=3, soil=1},
+ drop = 'default:dirt',
+ sounds = default.node_sound_dirt_defaults({
+ footstep = {name="default_grass_footstep", gain=0.4},
+ }),
+})
+
+minetest.register_node("default:dirt", {
+ description = "Dirt",
+ tiles ={"default_dirt.png"},
+ groups = {crumbly=3, soil=1},
+ sounds = default.node_sound_dirt_defaults(),
+})
+
+minetest.register_node("default:sand", {
+ description = "Sand",
+ tiles ={"default_sand.png"},
+ groups = {crumbly=3, falling_node=1},
+ sounds = default.node_sound_sand_defaults(),
+})
+
+minetest.register_node("default:gravel", {
+ description = "Gravel",
+ tiles ={"default_gravel.png"},
+ groups = {crumbly=2, falling_node=1},
+ sounds = default.node_sound_dirt_defaults({
+ footstep = {name="default_gravel_footstep", gain=0.45},
+ }),
+})
+
+minetest.register_node("default:sandstone", {
+ description = "Sandstone",
+ tiles ={"default_sandstone.png"},
+ groups = {crumbly=2,cracky=2},
+ drop = 'default:sand',
+ sounds = default.node_sound_stone_defaults(),
+})
+
+minetest.register_node("default:clay", {
+ description = "Clay",
+ tiles ={"default_clay.png"},
+ groups = {crumbly=3},
+ drop = 'default:clay_lump 4',
+ sounds = default.node_sound_dirt_defaults({
+ footstep = "",
+ }),
+})
+
+minetest.register_node("default:brick", {
+ description = "Brick",
+ tiles ={"default_brick.png"},
+ is_ground_content = false,
+ groups = {cracky=3},
+ drop = 'default:clay_brick 4',
+ sounds = default.node_sound_stone_defaults(),
+})
+
+minetest.register_node("default:tree", {
+ description = "Tree",
+ tiles ={"default_tree_top.png", "default_tree_top.png", "default_tree.png"},
+ is_ground_content = false,
+ groups = {snappy=2,choppy=2,oddly_breakable_by_hand=1},
+ sounds = default.node_sound_wood_defaults(),
+})
+
+minetest.register_node("default:junglegrass", {
+ description = "Jungle Grass",
+ drawtype = "plantlike",
+ visual_scale = 1.3,
+ tiles ={"default_junglegrass.png"},
+ inventory_image = "default_junglegrass.png",
+ wield_image = "default_junglegrass.png",
+ paramtype = "light",
+ walkable = false,
+ groups = {snappy=3,attached_node=1},
+ sounds = default.node_sound_leaves_defaults(),
+})
+
+minetest.register_node("default:leaves", {
+ description = "Leaves",
+ drawtype = "allfaces_optional",
+ visual_scale = 1.3,
+ tiles ={"default_leaves.png"},
+ paramtype = "light",
+ is_ground_content = false,
+ groups = {snappy=3},
+ drop = {
+ max_items = 1,
+ items = {
+ {
+ -- player will get sapling with 1/20 chance
+ items = {'default:sapling'},
+ rarity = 20,
+ },
+ {
+ -- player will get leaves only if he get no saplings,
+ -- this is because max_items is 1
+ items = {'default:leaves'},
+ }
+ }
+ },
+ sounds = default.node_sound_leaves_defaults(),
+})
+
+minetest.register_node("default:cactus", {
+ description = "Cactus",
+ tiles ={"default_cactus_top.png", "default_cactus_top.png", "default_cactus_side.png"},
+ groups = {snappy=2,choppy=3},
+ sounds = default.node_sound_wood_defaults(),
+})
+
+minetest.register_node("default:papyrus", {
+ description = "Papyrus",
+ drawtype = "plantlike",
+ tiles ={"default_papyrus.png"},
+ inventory_image = "default_papyrus.png",
+ wield_image = "default_papyrus.png",
+ paramtype = "light",
+ walkable = false,
+ groups = {snappy=3},
+ sounds = default.node_sound_leaves_defaults(),
+})
+
+minetest.register_node("default:bookshelf", {
+ description = "Bookshelf",
+ tiles ={"default_wood.png", "default_wood.png", "default_bookshelf.png"},
+ is_ground_content = false,
+ groups = {snappy=2,choppy=3,oddly_breakable_by_hand=2},
+ sounds = default.node_sound_wood_defaults(),
+})
+
+minetest.register_node("default:glass", {
+ description = "Glass",
+ drawtype = "glasslike",
+ tiles ={"default_glass.png"},
+ paramtype = "light",
+ is_ground_content = false,
+ sunlight_propagates = true,
+ groups = {snappy=2,cracky=3,oddly_breakable_by_hand=3},
+ sounds = default.node_sound_glass_defaults(),
+})
+
+minetest.register_node("default:fence_wood", {
+ description = "Wooden Fence",
+ drawtype = "fencelike",
+ tiles ={"default_wood.png"},
+ inventory_image = "default_fence.png",
+ wield_image = "default_fence.png",
+ paramtype = "light",
+ is_ground_content = false,
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/7, -1/2, -1/7, 1/7, 1/2, 1/7},
+ },
+ groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2},
+ sounds = default.node_sound_wood_defaults(),
+})
+
+minetest.register_node("default:rail", {
+ description = "Rail",
+ drawtype = "raillike",
+ tiles ={"default_rail.png", "default_rail_curved.png", "default_rail_t_junction.png", "default_rail_crossing.png"},
+ inventory_image = "default_rail.png",
+ wield_image = "default_rail.png",
+ paramtype = "light",
+ is_ground_content = false,
+ walkable = false,
+ selection_box = {
+ type = "fixed",
+ fixed = {-1/2, -1/2, -1/2, 1/2, -1/2+1/16, 1/2},
+ },
+ groups = {bendy=2,snappy=1,dig_immediate=2},
+})
+
+minetest.register_node("default:ladder", {
+ description = "Ladder",
+ drawtype = "signlike",
+ tiles ={"default_ladder.png"},
+ inventory_image = "default_ladder.png",
+ wield_image = "default_ladder.png",
+ paramtype = "light",
+ paramtype2 = "wallmounted",
+ is_ground_content = false,
+ walkable = false,
+ climbable = true,
+ selection_box = {
+ type = "wallmounted",
+ --wall_top = = <default>
+ --wall_bottom = = <default>
+ --wall_side = = <default>
+ },
+ groups = {snappy=2,choppy=2,oddly_breakable_by_hand=3},
+ legacy_wallmounted = true,
+ sounds = default.node_sound_wood_defaults(),
+})
+
+minetest.register_node("default:wood", {
+ description = "Wood",
+ tiles ={"default_wood.png"},
+ is_ground_content = false,
+ groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2},
+ sounds = default.node_sound_wood_defaults(),
+})
+
+minetest.register_node("default:mese", {
+ description = "Mese",
+ tiles ={"default_mese.png"},
+ groups = {cracky=1,level=2},
+ sounds = default.node_sound_defaults(),
+})
+
+minetest.register_node("default:cloud", {
+ description = "Cloud",
+ tiles ={"default_cloud.png"},
+ is_ground_content = false,
+ sounds = default.node_sound_defaults(),
+})
+
+minetest.register_node("default:water_flowing", {
+ description = "Water (flowing)",
+ drawtype = "flowingliquid",
+ tiles = {"default_water.png"},
+ special_tiles = {
+ {name = "default_water.png", backface_culling = false},
+ {name = "default_water.png", backface_culling = true},
+ },
+ alpha = WATER_ALPHA,
+ paramtype = "light",
+ paramtype2 = "flowingliquid",
+ walkable = false,
+ pointable = false,
+ diggable = false,
+ buildable_to = true,
+ is_ground_content = false,
+ drop = "",
+ drowning = 1,
+ liquidtype = "flowing",
+ liquid_alternative_flowing = "default:water_flowing",
+ liquid_alternative_source = "default:water_source",
+ liquid_viscosity = WATER_VISC,
+ post_effect_color = {a = 64, r = 100, g = 100, b = 200},
+ groups = {water = 3, liquid = 3},
+})
+
+minetest.register_node("default:water_source", {
+ description = "Water",
+ drawtype = "liquid",
+ tiles = {"default_water.png"},
+ special_tiles = {
+ -- New-style water source material (mostly unused)
+ {name = "default_water.png", backface_culling = false},
+ },
+ alpha = WATER_ALPHA,
+ paramtype = "light",
+ walkable = false,
+ pointable = false,
+ diggable = false,
+ buildable_to = true,
+ is_ground_content = false,
+ drop = "",
+ drowning = 1,
+ liquidtype = "source",
+ liquid_alternative_flowing = "default:water_flowing",
+ liquid_alternative_source = "default:water_source",
+ liquid_viscosity = WATER_VISC,
+ post_effect_color = {a = 64, r = 100, g = 100, b = 200},
+ groups = {water = 3, liquid = 3},
+})
+
+minetest.register_node("default:river_water_source", {
+ description = "River Water Source",
+ drawtype = "liquid",
+ tiles = {"default_river_water.png"},
+ special_tiles = {
+ -- New-style water source material (mostly unused)
+ {name = "default_river_water.png", backface_culling = false},
+ },
+ alpha = 160,
+ paramtype = "light",
+ walkable = false,
+ pointable = false,
+ diggable = false,
+ buildable_to = true,
+ is_ground_content = false,
+ drop = "",
+ drowning = 1,
+ liquidtype = "source",
+ liquid_alternative_flowing = "default:river_water_flowing",
+ liquid_alternative_source = "default:river_water_source",
+ liquid_viscosity = 1,
+ liquid_renewable = false,
+ liquid_range = 2,
+ post_effect_color = {a = 103, r = 30, g = 76, b = 90},
+ groups = {water = 3, liquid = 3, puts_out_fire = 1, cools_lava = 1},
+})
+
+minetest.register_node("default:river_water_flowing", {
+ description = "Flowing River Water",
+ drawtype = "flowingliquid",
+ tiles = {"default_river_water.png"},
+ special_tiles = {
+ {name = "default_river_water.png", backface_culling = false},
+ {name = "default_river_water.png", backface_culling = true},
+ },
+ alpha = 160,
+ paramtype = "light",
+ paramtype2 = "flowingliquid",
+ walkable = false,
+ pointable = false,
+ diggable = false,
+ buildable_to = true,
+ is_ground_content = false,
+ drop = "",
+ drowning = 1,
+ liquidtype = "flowing",
+ liquid_alternative_flowing = "default:river_water_flowing",
+ liquid_alternative_source = "default:river_water_source",
+ liquid_viscosity = 1,
+ liquid_renewable = false,
+ liquid_range = 2,
+ post_effect_color = {a = 103, r = 30, g = 76, b = 90},
+ groups = {water = 3, liquid = 3, puts_out_fire = 1,
+ not_in_creative_inventory = 1, cools_lava = 1},
+})
+
+minetest.register_node("default:lava_flowing", {
+ description = "Lava (flowing)",
+ inventory_image = minetest.inventorycube("default_lava.png"),
+ drawtype = "flowingliquid",
+ tiles ={"default_lava.png"},
+ special_tiles = {
+ {
+ image="default_lava_flowing_animated.png",
+ backface_culling=false,
+ animation={type="vertical_frames", aspect_w=16, aspect_h=16, length=3.3}
+ },
+ {
+ image="default_lava_flowing_animated.png",
+ backface_culling=true,
+ animation={type="vertical_frames", aspect_w=16, aspect_h=16, length=3.3}
+ },
+ },
+ paramtype = "light",
+ light_source = LIGHT_MAX - 1,
+ walkable = false,
+ pointable = false,
+ diggable = false,
+ buildable_to = true,
+ is_ground_content = false,
+ drowning = 1,
+ liquidtype = "flowing",
+ liquid_alternative_flowing = "default:lava_flowing",
+ liquid_alternative_source = "default:lava_source",
+ liquid_viscosity = LAVA_VISC,
+ damage_per_second = 4*2,
+ post_effect_color = {a=192, r=255, g=64, b=0},
+ groups = {lava=3, liquid=2, hot=3},
+})
+
+minetest.register_node("default:lava_source", {
+ description = "Lava",
+ inventory_image = minetest.inventorycube("default_lava.png"),
+ drawtype = "liquid",
+ --tiles ={"default_lava.png"},
+ tiles = {
+ {
+ name = "default_lava_source_animated.png",
+ animation = {type="sheet_2d", frames_w=3, frames_h=2, frame_length=0.5}
+ }
+ },
+ special_tiles = {
+ -- New-style lava source material (mostly unused)
+ {name="default_lava.png", backface_culling=false},
+ },
+ paramtype = "light",
+ light_source = LIGHT_MAX - 1,
+ walkable = false,
+ pointable = false,
+ diggable = false,
+ buildable_to = true,
+ is_ground_content = false,
+ drowning = 1,
+ liquidtype = "source",
+ liquid_alternative_flowing = "default:lava_flowing",
+ liquid_alternative_source = "default:lava_source",
+ liquid_viscosity = LAVA_VISC,
+ damage_per_second = 4*2,
+ post_effect_color = {a=192, r=255, g=64, b=0},
+ groups = {lava=3, liquid=2, hot=3},
+})
+
+minetest.register_node("default:torch", {
+ description = "Torch",
+ drawtype = "torchlike",
+ tiles ={"default_torch_on_floor.png", "default_torch_on_ceiling.png", "default_torch.png"},
+ inventory_image = "default_torch_on_floor.png",
+ wield_image = "default_torch_on_floor.png",
+ paramtype = "light",
+ paramtype2 = "wallmounted",
+ sunlight_propagates = true,
+ is_ground_content = false,
+ walkable = false,
+ light_source = LIGHT_MAX-1,
+ selection_box = {
+ type = "wallmounted",
+ wall_top = {-0.1, 0.5-0.6, -0.1, 0.1, 0.5, 0.1},
+ wall_bottom = {-0.1, -0.5, -0.1, 0.1, -0.5+0.6, 0.1},
+ wall_side = {-0.5, -0.3, -0.1, -0.5+0.3, 0.3, 0.1},
+ },
+ groups = {choppy=2,dig_immediate=3,attached_node=1},
+ legacy_wallmounted = true,
+ sounds = default.node_sound_defaults(),
+})
+
+minetest.register_node("default:sign_wall", {
+ description = "Sign",
+ drawtype = "signlike",
+ tiles ={"default_sign_wall.png"},
+ inventory_image = "default_sign_wall.png",
+ wield_image = "default_sign_wall.png",
+ paramtype = "light",
+ paramtype2 = "wallmounted",
+ sunlight_propagates = true,
+ is_ground_content = false,
+ walkable = false,
+ selection_box = {
+ type = "wallmounted",
+ --wall_top = <default>
+ --wall_bottom = <default>
+ --wall_side = <default>
+ },
+ groups = {choppy=2,dig_immediate=2,attached_node=1},
+ legacy_wallmounted = true,
+ sounds = default.node_sound_defaults(),
+ on_construct = function(pos)
+ --local n = minetest.get_node(pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("formspec", "field[text;;${text}]")
+ meta:set_string("infotext", "\"\"")
+ end,
+ on_receive_fields = function(pos, formname, fields, sender)
+ --print("Sign at "..minetest.pos_to_string(pos).." got "..dump(fields))
+ local meta = minetest.get_meta(pos)
+ fields.text = fields.text or ""
+ print((sender:get_player_name() or "").." wrote \""..fields.text..
+ "\" to sign at "..minetest.pos_to_string(pos))
+ meta:set_string("text", fields.text)
+ meta:set_string("infotext", '"'..fields.text..'"')
+ end,
+})
+
+minetest.register_node("default:chest", {
+ description = "Chest",
+ tiles ={"default_chest.png^[sheet:2x2:0,0", "default_chest.png^[sheet:2x2:0,0",
+ "default_chest.png^[sheet:2x2:1,0", "default_chest.png^[sheet:2x2:1,0",
+ "default_chest.png^[sheet:2x2:1,0", "default_chest.png^[sheet:2x2:0,1"},
+ paramtype2 = "facedir",
+ groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2},
+ legacy_facedir_simple = true,
+ is_ground_content = false,
+ sounds = default.node_sound_wood_defaults(),
+ on_construct = function(pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("formspec",
+ "size[8,9]"..
+ "list[current_name;main;0,0;8,4;]"..
+ "list[current_player;main;0,5;8,4;]" ..
+ "listring[]")
+ meta:set_string("infotext", "Chest")
+ local inv = meta:get_inventory()
+ inv:set_size("main", 8*4)
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty("main")
+ end,
+})
+
+local function has_locked_chest_privilege(meta, player)
+ if player:get_player_name() ~= meta:get_string("owner") then
+ return false
+ end
+ return true
+end
+
+minetest.register_node("default:chest_locked", {
+ description = "Locked Chest",
+ tiles ={"default_chest.png^[sheet:2x2:0,0", "default_chest.png^[sheet:2x2:0,0",
+ "default_chest.png^[sheet:2x2:1,0", "default_chest.png^[sheet:2x2:1,0",
+ "default_chest.png^[sheet:2x2:1,0", "default_chest.png^[sheet:2x2:1,1"},
+ paramtype2 = "facedir",
+ groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2},
+ legacy_facedir_simple = true,
+ is_ground_content = false,
+ sounds = default.node_sound_wood_defaults(),
+ after_place_node = function(pos, placer)
+ local meta = minetest.get_meta(pos)
+ local pname =
+ placer and placer:get_player_name() or ""
+ meta:set_string("owner", pname)
+ meta:set_string("infotext", "Locked Chest (owned by "..
+ meta:get_string("owner")..")")
+ end,
+ on_construct = function(pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("formspec",
+ "size[8,9]"..
+ "list[current_name;main;0,0;8,4;]"..
+ "list[current_player;main;0,5;8,4;]" ..
+ "listring[]")
+ meta:set_string("infotext", "Locked Chest")
+ meta:set_string("owner", "")
+ local inv = meta:get_inventory()
+ inv:set_size("main", 8*4)
+ -- this is not really the intended usage but works for testing purposes:
+ meta:mark_as_private("owner")
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ return inv:is_empty("main")
+ end,
+ allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
+ local meta = minetest.get_meta(pos)
+ if not has_locked_chest_privilege(meta, player) then
+ minetest.log("action", player:get_player_name()..
+ " tried to access a locked chest belonging to "..
+ meta:get_string("owner").." at "..
+ minetest.pos_to_string(pos))
+ return 0
+ end
+ return count
+ end,
+ allow_metadata_inventory_put = function(pos, listname, index, stack, player)
+ local meta = minetest.get_meta(pos)
+ if not has_locked_chest_privilege(meta, player) then
+ minetest.log("action", player:get_player_name()..
+ " tried to access a locked chest belonging to "..
+ meta:get_string("owner").." at "..
+ minetest.pos_to_string(pos))
+ return 0
+ end
+ return stack:get_count()
+ end,
+ allow_metadata_inventory_take = function(pos, listname, index, stack, player)
+ local meta = minetest.get_meta(pos)
+ if not has_locked_chest_privilege(meta, player) then
+ minetest.log("action", player:get_player_name()..
+ " tried to access a locked chest belonging to "..
+ meta:get_string("owner").." at "..
+ minetest.pos_to_string(pos))
+ return 0
+ end
+ return stack:get_count()
+ end,
+ on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
+ minetest.log("action", player:get_player_name()..
+ " moves stuff in locked chest at "..minetest.pos_to_string(pos))
+ end,
+ on_metadata_inventory_put = function(pos, listname, index, stack, player)
+ minetest.log("action", player:get_player_name()..
+ " moves stuff to locked chest at "..minetest.pos_to_string(pos))
+ end,
+ on_metadata_inventory_take = function(pos, listname, index, stack, player)
+ minetest.log("action", player:get_player_name()..
+ " takes stuff from locked chest at "..minetest.pos_to_string(pos))
+ end,
+})
+
+default.furnace_inactive_formspec =
+ "size[8,9]"..
+ "image[2,2;1,1;default_furnace_fire_bg.png]"..
+ "list[current_name;fuel;2,3;1,1;]"..
+ "list[current_name;src;2,1;1,1;]"..
+ "list[current_name;dst;5,1;2,2;]"..
+ "list[current_player;main;0,5;8,4;]" ..
+ "listring[current_name;dst]" ..
+ "listring[current_player;main]" ..
+ "listring[current_name;src]" ..
+ "listring[current_player;main]"
+
+minetest.register_node("default:furnace", {
+ description = "Furnace",
+ tiles ={"default_furnace_side.png", "default_furnace_side.png", "default_furnace_side.png",
+ "default_furnace_side.png", "default_furnace_side.png", "default_furnace_front.png"},
+ paramtype2 = "facedir",
+ groups = {cracky=2},
+ legacy_facedir_simple = true,
+ is_ground_content = false,
+ sounds = default.node_sound_stone_defaults(),
+ on_construct = function(pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("formspec", default.furnace_inactive_formspec)
+ meta:set_string("infotext", "Furnace")
+ local inv = meta:get_inventory()
+ inv:set_size("fuel", 1)
+ inv:set_size("src", 1)
+ inv:set_size("dst", 4)
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ if not inv:is_empty("fuel") then
+ return false
+ elseif not inv:is_empty("dst") then
+ return false
+ elseif not inv:is_empty("src") then
+ return false
+ end
+ return true
+ end,
+})
+
+minetest.register_node("default:furnace_active", {
+ description = "Furnace",
+ tiles ={"default_furnace_side.png", "default_furnace_side.png", "default_furnace_side.png",
+ "default_furnace_side.png", "default_furnace_side.png", "default_furnace_front_active.png"},
+ paramtype2 = "facedir",
+ light_source = 8,
+ drop = "default:furnace",
+ groups = {cracky=2},
+ legacy_facedir_simple = true,
+ is_ground_content = false,
+ sounds = default.node_sound_stone_defaults(),
+ on_construct = function(pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("formspec", default.furnace_inactive_formspec)
+ meta:set_string("infotext", "Furnace");
+ local inv = meta:get_inventory()
+ inv:set_size("fuel", 1)
+ inv:set_size("src", 1)
+ inv:set_size("dst", 4)
+ end,
+ can_dig = function(pos,player)
+ local meta = minetest.get_meta(pos);
+ local inv = meta:get_inventory()
+ if not inv:is_empty("fuel") then
+ return false
+ elseif not inv:is_empty("dst") then
+ return false
+ elseif not inv:is_empty("src") then
+ return false
+ end
+ return true
+ end,
+})
+
+function swap_node(pos,name)
+ local node = minetest.get_node(pos)
+ if node.name == name then
+ return
+ end
+ node.name = name
+ minetest.swap_node(pos, node)
+end
+
+minetest.register_abm({
+ nodenames = {"default:furnace","default:furnace_active"},
+ interval = 1.0,
+ chance = 1,
+ action = function(pos, node, active_object_count, active_object_count_wider)
+ local meta = minetest.get_meta(pos)
+ for i, name in ipairs({
+ "fuel_totaltime",
+ "fuel_time",
+ "src_totaltime",
+ "src_time"
+ }) do
+ if meta:get_string(name) == "" then
+ meta:set_float(name, 0.0)
+ end
+ end
+
+ local inv = meta:get_inventory()
+
+ local srclist = inv:get_list("src")
+ local cooked = nil
+
+ if srclist then
+ cooked = minetest.get_craft_result({method = "cooking", width = 1, items = srclist})
+ end
+
+ local was_active = false
+
+ if meta:get_float("fuel_time") < meta:get_float("fuel_totaltime") then
+ was_active = true
+ meta:set_float("fuel_time", meta:get_float("fuel_time") + 1)
+ meta:set_float("src_time", meta:get_float("src_time") + 1)
+ if cooked and cooked.item and meta:get_float("src_time") >= cooked.time then
+ -- check if there's room for output in "dst" list
+ if inv:room_for_item("dst",cooked.item) then
+ -- Put result in "dst" list
+ inv:add_item("dst", cooked.item)
+ -- take stuff from "src" list
+ srcstack = inv:get_stack("src", 1)
+ srcstack:take_item()
+ inv:set_stack("src", 1, srcstack)
+ else
+ print("Could not insert '"..cooked.item:to_string().."'")
+ end
+ meta:set_string("src_time", 0)
+ end
+ end
+
+ if meta:get_float("fuel_time") < meta:get_float("fuel_totaltime") then
+ local percent = math.floor(meta:get_float("fuel_time") /
+ meta:get_float("fuel_totaltime") * 100)
+ meta:set_string("infotext","Furnace active: "..percent.."%")
+ swap_node(pos,"default:furnace_active")
+ meta:set_string("formspec",
+ "size[8,9]"..
+ "image[2,2;1,1;default_furnace_fire_bg.png^[lowpart:"..
+ (100-percent)..":default_furnace_fire_fg.png]"..
+ "list[current_name;fuel;2,3;1,1;]"..
+ "list[current_name;src;2,1;1,1;]"..
+ "list[current_name;dst;5,1;2,2;]"..
+ "list[current_player;main;0,5;8,4;]" ..
+ "listring[current_name;dst]" ..
+ "listring[current_player;main]" ..
+ "listring[current_name;src]" ..
+ "listring[current_player;main]")
+ return
+ end
+
+ local fuel = nil
+ local cooked = nil
+ local fuellist = inv:get_list("fuel")
+ local srclist = inv:get_list("src")
+
+ if srclist then
+ cooked = minetest.get_craft_result({method = "cooking", width = 1, items = srclist})
+ end
+ if fuellist then
+ fuel = minetest.get_craft_result({method = "fuel", width = 1, items = fuellist})
+ end
+
+ if fuel.time <= 0 then
+ meta:set_string("infotext","Furnace out of fuel")
+ swap_node(pos,"default:furnace")
+ meta:set_string("formspec", default.furnace_inactive_formspec)
+ return
+ end
+
+ if cooked.item:is_empty() then
+ if was_active then
+ meta:set_string("infotext","Furnace is empty")
+ swap_node(pos,"default:furnace")
+ meta:set_string("formspec", default.furnace_inactive_formspec)
+ end
+ return
+ end
+
+ meta:set_string("fuel_totaltime", fuel.time)
+ meta:set_string("fuel_time", 0)
+
+ local stack = inv:get_stack("fuel", 1)
+ stack:take_item()
+ inv:set_stack("fuel", 1, stack)
+ end,
+})
+
+minetest.register_node("default:cobble", {
+ description = "Cobble",
+ tiles ={"default_cobble.png"},
+ is_ground_content = false,
+ groups = {cracky=3},
+ sounds = default.node_sound_stone_defaults(),
+})
+
+minetest.register_node("default:mossycobble", {
+ description = "Mossy Cobble",
+ tiles ={"default_mossycobble.png"},
+ is_ground_content = false,
+ groups = {cracky=3},
+ sounds = default.node_sound_stone_defaults(),
+})
+
+minetest.register_node("default:steelblock", {
+ description = "Steel Block",
+ tiles ={"default_steel_block.png"},
+ is_ground_content = false,
+ groups = {snappy=1,bendy=2},
+ sounds = default.node_sound_stone_defaults(),
+})
+
+minetest.register_node("default:nyancat", {
+ description = "Nyancat",
+ tiles ={"default_nc_side.png", "default_nc_side.png", "default_nc_side.png",
+ "default_nc_side.png", "default_nc_back.png", "default_nc_front.png"},
+ inventory_image = "default_nc_front.png",
+ paramtype2 = "facedir",
+ groups = {cracky=2},
+ legacy_facedir_simple = true,
+ is_ground_content = false,
+ sounds = default.node_sound_defaults(),
+})
+
+minetest.register_node("default:nyancat_rainbow", {
+ description = "Nyancat Rainbow",
+ tiles ={"default_nc_rb.png"},
+ inventory_image = "default_nc_rb.png",
+ is_ground_content = false,
+ groups = {cracky=2},
+ sounds = default.node_sound_defaults(),
+})
+
+minetest.register_node("default:sapling", {
+ description = "Sapling",
+ drawtype = "plantlike",
+ visual_scale = 1.0,
+ tiles ={"default_sapling.png"},
+ inventory_image = "default_sapling.png",
+ wield_image = "default_sapling.png",
+ paramtype = "light",
+ walkable = false,
+ groups = {snappy=2,dig_immediate=3,attached_node=1},
+ sounds = default.node_sound_defaults(),
+})
+
+minetest.register_node("default:apple", {
+ description = "Apple",
+ drawtype = "plantlike",
+ visual_scale = 1.0,
+ tiles ={"default_apple.png"},
+ inventory_image = "default_apple.png",
+ paramtype = "light",
+ is_ground_content = false,
+ sunlight_propagates = true,
+ walkable = false,
+ groups = {fleshy=3,dig_immediate=3},
+ on_use = minetest.item_eat(4),
+ sounds = default.node_sound_defaults(),
+})
+
+--
+-- Grow tree function
+--
+
+local c_air = minetest.get_content_id("air")
+local c_ignore = minetest.get_content_id("ignore")
+local c_tree = minetest.get_content_id("default:tree")
+local c_leaves = minetest.get_content_id("default:leaves")
+local c_apple = minetest.get_content_id("default:apple")
+function default.grow_tree(data, a, pos, is_apple_tree, seed)
+ --[[
+ NOTE: Tree-placing code is currently duplicated in the engine
+ and in games that have saplings; both are deprecated but not
+ replaced yet
+ ]]--
+ local pr = PseudoRandom(seed)
+ local th = pr:next(4, 5)
+ local x, y, z = pos.x, pos.y, pos.z
+ for yy = y, y+th-1 do
+ local vi = a:index(x, yy, z)
+ if a:contains(x, yy, z) and (data[vi] == c_air or yy == y) then
+ data[vi] = c_tree
+ end
+ end
+ y = y+th-1 -- (x, y, z) is now last piece of trunk
+ local leaves_a = VoxelArea:new{MinEdge={x=-2, y=-1, z=-2}, MaxEdge={x=2, y=2, z=2}}
+ local leaves_buffer = {}
+
+ -- Force leaves near the trunk
+ local d = 1
+ for xi = -d, d do
+ for yi = -d, d do
+ for zi = -d, d do
+ leaves_buffer[leaves_a:index(xi, yi, zi)] = true
+ end
+ end
+ end
+
+ -- Add leaves randomly
+ for iii = 1, 8 do
+ local d = 1
+ local xx = pr:next(leaves_a.MinEdge.x, leaves_a.MaxEdge.x - d)
+ local yy = pr:next(leaves_a.MinEdge.y, leaves_a.MaxEdge.y - d)
+ local zz = pr:next(leaves_a.MinEdge.z, leaves_a.MaxEdge.z - d)
+
+ for xi = 0, d do
+ for yi = 0, d do
+ for zi = 0, d do
+ leaves_buffer[leaves_a:index(xx+xi, yy+yi, zz+zi)] = true
+ end
+ end
+ end
+ end
+
+ -- Add the leaves
+ for xi = leaves_a.MinEdge.x, leaves_a.MaxEdge.x do
+ for yi = leaves_a.MinEdge.y, leaves_a.MaxEdge.y do
+ for zi = leaves_a.MinEdge.z, leaves_a.MaxEdge.z do
+ if a:contains(x+xi, y+yi, z+zi) then
+ local vi = a:index(x+xi, y+yi, z+zi)
+ if data[vi] == c_air or data[vi] == c_ignore then
+ if leaves_buffer[leaves_a:index(xi, yi, zi)] then
+ if is_apple_tree and pr:next(1, 100) <= 10 then
+ data[vi] = c_apple
+ else
+ data[vi] = c_leaves
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+--
+-- ABMs
+--
+
+minetest.register_abm({
+ nodenames = {"default:sapling"},
+ interval = 10,
+ chance = 50,
+ action = function(pos, node)
+ if minetest.get_item_group(minetest.get_node(
+ {x = pos.x, y = pos.y - 1, z = pos.z}).name, "soil") == 0 then
+ return
+ end
+ print("A sapling grows into a tree at "..minetest.pos_to_string(pos))
+ local vm = minetest.get_voxel_manip()
+ local minp, maxp = vm:read_from_map({x=pos.x-16, y=pos.y, z=pos.z-16}, {x=pos.x+16, y=pos.y+16, z=pos.z+16})
+ local a = VoxelArea:new{MinEdge=minp, MaxEdge=maxp}
+ local data = vm:get_data()
+ default.grow_tree(data, a, pos, math.random(1, 4) == 1, math.random(1,100000))
+ vm:set_data(data)
+ vm:write_to_map(data)
+ vm:update_map()
+ end
+})
+
+minetest.register_abm({
+ nodenames = {"default:dirt"},
+ interval = 2,
+ chance = 200,
+ action = function(pos, node)
+ local above = {x=pos.x, y=pos.y+1, z=pos.z}
+ local name = minetest.get_node(above).name
+ local nodedef = minetest.registered_nodes[name]
+ if nodedef and (nodedef.sunlight_propagates or nodedef.paramtype == "light")
+ and nodedef.liquidtype == "none"
+ and (minetest.get_node_light(above) or 0) >= 13 then
+ if name == "default:snow" or name == "default:snowblock" then
+ minetest.set_node(pos, {name = "default:dirt_with_snow"})
+ else
+ minetest.set_node(pos, {name = "default:dirt_with_grass"})
+ end
+ end
+ end
+})
+
+minetest.register_abm({
+ nodenames = {"default:dirt_with_grass"},
+ interval = 2,
+ chance = 20,
+ action = function(pos, node)
+ local above = {x=pos.x, y=pos.y+1, z=pos.z}
+ local name = minetest.get_node(above).name
+ local nodedef = minetest.registered_nodes[name]
+ if name ~= "ignore" and nodedef
+ and not ((nodedef.sunlight_propagates or nodedef.paramtype == "light")
+ and nodedef.liquidtype == "none") then
+ minetest.set_node(pos, {name = "default:dirt"})
+ end
+ end
+})
+
+--
+-- Crafting items
+--
+
+minetest.register_craftitem("default:stick", {
+ description = "Stick",
+ inventory_image = "default_stick.png",
+})
+
+minetest.register_craftitem("default:paper", {
+ description = "Paper",
+ inventory_image = "default_paper.png",
+})
+
+minetest.register_craftitem("default:book", {
+ description = "Book",
+ inventory_image = "default_book.png",
+})
+
+minetest.register_craftitem("default:coal_lump", {
+ description = "Lump of coal",
+ inventory_image = "default_coal_lump.png",
+})
+
+minetest.register_craftitem("default:iron_lump", {
+ description = "Lump of iron",
+ inventory_image = "default_iron_lump.png",
+})
+
+minetest.register_craftitem("default:clay_lump", {
+ description = "Lump of clay",
+ inventory_image = "default_clay_lump.png",
+})
+
+minetest.register_craftitem("default:steel_ingot", {
+ description = "Steel ingot",
+ inventory_image = "default_steel_ingot.png",
+})
+
+minetest.register_craftitem("default:clay_brick", {
+ description = "Clay brick",
+ inventory_image = "default_steel_ingot.png",
+ inventory_image = "default_clay_brick.png",
+})
+
+minetest.register_craftitem("default:scorched_stuff", {
+ description = "Scorched stuff",
+ inventory_image = "default_scorched_stuff.png",
+})
+
+--
+-- Support old code
+--
+
+function default.spawn_falling_node(p, nodename)
+ spawn_falling_node(p, nodename)
+end
+
+-- Horrible crap to support old code
+-- Don't use this and never do what this does, it's completely wrong!
+-- (More specifically, the client and the C++ code doesn't get the group)
+function default.register_falling_node(nodename, texture)
+ minetest.log("error", debug.traceback())
+ minetest.log('error', "WARNING: default.register_falling_node is deprecated")
+ if minetest.registered_nodes[nodename] then
+ minetest.registered_nodes[nodename].groups.falling_node = 1
+ end
+end
+
+--
+-- Global callbacks
+--
+
+-- Global environment step function
+function on_step(dtime)
+ -- print("on_step")
+end
+minetest.register_globalstep(on_step)
+
+function on_placenode(p, node)
+ --print("on_placenode")
+end
+minetest.register_on_placenode(on_placenode)
+
+function on_dignode(p, node)
+ --print("on_dignode")
+end
+minetest.register_on_dignode(on_dignode)
+
+function on_punchnode(p, node)
+end
+minetest.register_on_punchnode(on_punchnode)
+
+--
+-- Test some things
+--
+
+local function test_get_craft_result()
+ minetest.log("info", "test_get_craft_result()")
+ -- normal
+ local input = {
+ method = "normal",
+ width = 2,
+ items = {"", "default:coal_lump", "", "default:stick"}
+ }
+ minetest.log("info", "torch crafting input: "..dump(input))
+ local output, decremented_input = minetest.get_craft_result(input)
+ minetest.log("info", "torch crafting output: "..dump(output))
+ minetest.log("info", "torch crafting decremented input: "..dump(decremented_input))
+ assert(output.item)
+ minetest.log("info", "torch crafting output.item:to_table(): "..dump(output.item:to_table()))
+ assert(output.item:get_name() == "default:torch")
+ assert(output.item:get_count() == 4)
+ -- fuel
+ local input = {
+ method = "fuel",
+ width = 1,
+ items = {"default:coal_lump"}
+ }
+ minetest.log("info", "coal fuel input: "..dump(input))
+ local output, decremented_input = minetest.get_craft_result(input)
+ minetest.log("info", "coal fuel output: "..dump(output))
+ minetest.log("info", "coal fuel decremented input: "..dump(decremented_input))
+ assert(output.time)
+ assert(output.time > 0)
+ -- cook
+ local input = {
+ method = "cooking",
+ width = 1,
+ items = {"default:cobble"}
+ }
+ minetest.log("info", "cobble cooking input: "..dump(output))
+ local output, decremented_input = minetest.get_craft_result(input)
+ minetest.log("info", "cobble cooking output: "..dump(output))
+ minetest.log("info", "cobble cooking decremented input: "..dump(decremented_input))
+ assert(output.time)
+ assert(output.time > 0)
+ assert(output.item)
+ minetest.log("info", "cobble cooking output.item:to_table(): "..dump(output.item:to_table()))
+ assert(output.item:get_name() == "default:stone")
+ assert(output.item:get_count() == 1)
+end
+test_get_craft_result()
+
+--
+-- Done, print some random stuff
+--
+
+--print("minetest.registered_entities:")
+--dump2(minetest.registered_entities)
+
+-- END
+
diff --git a/games/minimal/mods/default/mapgen.lua b/games/minimal/mods/default/mapgen.lua
new file mode 100644
index 000000000..30a865366
--- /dev/null
+++ b/games/minimal/mods/default/mapgen.lua
@@ -0,0 +1,137 @@
+--
+-- Aliases for map generator outputs
+--
+
+
+minetest.register_alias("mapgen_stone", "default:stone")
+minetest.register_alias("mapgen_dirt", "default:dirt")
+minetest.register_alias("mapgen_dirt_with_grass", "default:dirt_with_grass")
+minetest.register_alias("mapgen_sand", "default:sand")
+minetest.register_alias("mapgen_water_source", "default:water_source")
+minetest.register_alias("mapgen_river_water_source", "default:river_water_source")
+minetest.register_alias("mapgen_lava_source", "default:lava_source")
+minetest.register_alias("mapgen_gravel", "default:gravel")
+
+minetest.register_alias("mapgen_tree", "default:tree")
+minetest.register_alias("mapgen_leaves", "default:leaves")
+minetest.register_alias("mapgen_apple", "default:apple")
+minetest.register_alias("mapgen_junglegrass", "default:junglegrass")
+
+minetest.register_alias("mapgen_cobble", "default:cobble")
+minetest.register_alias("mapgen_stair_cobble", "stairs:stair_cobble")
+minetest.register_alias("mapgen_mossycobble", "default:mossycobble")
+
+
+--
+-- Ore generation
+--
+
+
+-- Blob ore first to avoid other ores inside blobs
+
+minetest.register_ore({
+ ore_type = "blob",
+ ore = "default:clay",
+ wherein = {"default:sand"},
+ clust_scarcity = 24*24*24,
+ clust_size = 7,
+ y_min = -15,
+ y_max = 0,
+ noise_threshold = 0,
+ noise_params = {
+ offset=0.35,
+ scale=0.2,
+ spread={x=5, y=5, z=5},
+ seed=-316,
+ octaves=1,
+ persist=0.5
+ },
+})
+
+minetest.register_ore({
+ ore_type = "scatter",
+ ore = "default:stone_with_coal",
+ wherein = "default:stone",
+ clust_scarcity = 8*8*8,
+ clust_num_ores = 8,
+ clust_size = 3,
+ y_min = -31000,
+ y_max = 64,
+})
+
+minetest.register_ore({
+ ore_type = "scatter",
+ ore = "default:stone_with_iron",
+ wherein = "default:stone",
+ clust_scarcity = 12*12*12,
+ clust_num_ores = 3,
+ clust_size = 2,
+ y_min = -15,
+ y_max = 2,
+})
+
+minetest.register_ore({
+ ore_type = "scatter",
+ ore = "default:stone_with_iron",
+ wherein = "default:stone",
+ clust_scarcity = 9*9*9,
+ clust_num_ores = 5,
+ clust_size = 3,
+ y_min = -63,
+ y_max = -16,
+})
+
+minetest.register_ore({
+ ore_type = "scatter",
+ ore = "default:stone_with_iron",
+ wherein = "default:stone",
+ clust_scarcity = 7*7*7,
+ clust_num_ores = 5,
+ clust_size = 3,
+ y_min = -31000,
+ y_max = -64,
+})
+
+
+--
+-- Register biomes for biome API
+--
+
+
+minetest.clear_registered_biomes()
+minetest.clear_registered_decorations()
+
+minetest.register_biome({
+ name = "default:grassland",
+ --node_dust = "",
+ node_top = "default:dirt_with_grass",
+ depth_top = 1,
+ node_filler = "default:dirt",
+ depth_filler = 1,
+ --node_stone = "",
+ --node_water_top = "",
+ --depth_water_top = ,
+ --node_water = "",
+ y_min = 5,
+ y_max = 31000,
+ heat_point = 50,
+ humidity_point = 50,
+})
+
+minetest.register_biome({
+ name = "default:grassland_ocean",
+ --node_dust = "",
+ node_top = "default:sand",
+ depth_top = 1,
+ node_filler = "default:sand",
+ depth_filler = 2,
+ --node_stone = "",
+ --node_water_top = "",
+ --depth_water_top = ,
+ --node_water = "",
+ y_min = -31000,
+ y_max = 4,
+ heat_point = 50,
+ humidity_point = 50,
+})
+
diff --git a/games/minimal/mods/default/sounds/default_grass_footstep.1.ogg b/games/minimal/mods/default/sounds/default_grass_footstep.1.ogg
new file mode 100644
index 000000000..ce625d92b
--- /dev/null
+++ b/games/minimal/mods/default/sounds/default_grass_footstep.1.ogg
Binary files differ
diff --git a/games/minimal/mods/default/textures/bubble.png b/games/minimal/mods/default/textures/bubble.png
new file mode 100644
index 000000000..3bca7e11c
--- /dev/null
+++ b/games/minimal/mods/default/textures/bubble.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/crack_anylength.png b/games/minimal/mods/default/textures/crack_anylength.png
new file mode 100644
index 000000000..d9b49f911
--- /dev/null
+++ b/games/minimal/mods/default/textures/crack_anylength.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_apple.png b/games/minimal/mods/default/textures/default_apple.png
new file mode 100644
index 000000000..4473dca28
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_apple.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_book.png b/games/minimal/mods/default/textures/default_book.png
new file mode 100644
index 000000000..ea14a372a
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_book.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_bookshelf.png b/games/minimal/mods/default/textures/default_bookshelf.png
new file mode 100644
index 000000000..f225e3449
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_bookshelf.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_brick.png b/games/minimal/mods/default/textures/default_brick.png
new file mode 100644
index 000000000..def1cf0b1
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_brick.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_cactus_side.png b/games/minimal/mods/default/textures/default_cactus_side.png
new file mode 100644
index 000000000..128a4d28a
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_cactus_side.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_cactus_top.png b/games/minimal/mods/default/textures/default_cactus_top.png
new file mode 100644
index 000000000..eda1a0bb6
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_cactus_top.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_chest.png b/games/minimal/mods/default/textures/default_chest.png
new file mode 100644
index 000000000..9746a3fd9
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_chest.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_clay.png b/games/minimal/mods/default/textures/default_clay.png
new file mode 100644
index 000000000..070b69e48
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_clay.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_clay_brick.png b/games/minimal/mods/default/textures/default_clay_brick.png
new file mode 100644
index 000000000..e25633b70
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_clay_brick.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_clay_lump.png b/games/minimal/mods/default/textures/default_clay_lump.png
new file mode 100644
index 000000000..ceed6fa75
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_clay_lump.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_cloud.png b/games/minimal/mods/default/textures/default_cloud.png
new file mode 100644
index 000000000..faf0ec13d
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_cloud.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_coal_lump.png b/games/minimal/mods/default/textures/default_coal_lump.png
new file mode 100644
index 000000000..dae47e342
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_coal_lump.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_cobble.png b/games/minimal/mods/default/textures/default_cobble.png
new file mode 100644
index 000000000..2a28d25a5
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_cobble.png
Binary files differ
diff --git a/data/mud.png b/games/minimal/mods/default/textures/default_dirt.png
index 7cb9c89a6..7cb9c89a6 100644
--- a/data/mud.png
+++ b/games/minimal/mods/default/textures/default_dirt.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_fence.png b/games/minimal/mods/default/textures/default_fence.png
new file mode 100644
index 000000000..e3510c52a
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_fence.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_furnace_fire_bg.png b/games/minimal/mods/default/textures/default_furnace_fire_bg.png
new file mode 100644
index 000000000..e3370f438
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_furnace_fire_bg.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_furnace_fire_fg.png b/games/minimal/mods/default/textures/default_furnace_fire_fg.png
new file mode 100644
index 000000000..7a126e325
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_furnace_fire_fg.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_furnace_front.png b/games/minimal/mods/default/textures/default_furnace_front.png
new file mode 100644
index 000000000..4da398c0a
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_furnace_front.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_furnace_front_active.png b/games/minimal/mods/default/textures/default_furnace_front_active.png
new file mode 100644
index 000000000..06c4ab3ef
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_furnace_front_active.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_furnace_side.png b/games/minimal/mods/default/textures/default_furnace_side.png
new file mode 100644
index 000000000..c729de9bf
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_furnace_side.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_glass.png b/games/minimal/mods/default/textures/default_glass.png
new file mode 100644
index 000000000..fd665a40c
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_glass.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_grass.png b/games/minimal/mods/default/textures/default_grass.png
new file mode 100644
index 000000000..1d76708a0
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_grass.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_grass_footsteps.png b/games/minimal/mods/default/textures/default_grass_footsteps.png
new file mode 100644
index 000000000..8349033d6
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_grass_footsteps.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_grass_side.png b/games/minimal/mods/default/textures/default_grass_side.png
new file mode 100644
index 000000000..4f4f680be
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_grass_side.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_gravel.png b/games/minimal/mods/default/textures/default_gravel.png
new file mode 100644
index 000000000..4b47e2346
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_gravel.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_iron_lump.png b/games/minimal/mods/default/textures/default_iron_lump.png
new file mode 100644
index 000000000..6b515f66d
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_iron_lump.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_junglegrass.png b/games/minimal/mods/default/textures/default_junglegrass.png
new file mode 100644
index 000000000..7066f7dd9
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_junglegrass.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_ladder.png b/games/minimal/mods/default/textures/default_ladder.png
new file mode 100644
index 000000000..46757b884
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_ladder.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_lava.png b/games/minimal/mods/default/textures/default_lava.png
new file mode 100644
index 000000000..a4cf649f1
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_lava.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_lava_flowing_animated.png b/games/minimal/mods/default/textures/default_lava_flowing_animated.png
new file mode 100644
index 000000000..0bbd13606
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_lava_flowing_animated.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_lava_source_animated.png b/games/minimal/mods/default/textures/default_lava_source_animated.png
new file mode 100644
index 000000000..54f4c0ddd
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_lava_source_animated.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_leaves.png b/games/minimal/mods/default/textures/default_leaves.png
new file mode 100644
index 000000000..00ce4471c
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_leaves.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_mese.png b/games/minimal/mods/default/textures/default_mese.png
new file mode 100644
index 000000000..123f0f4b9
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_mese.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_mineral_coal.png b/games/minimal/mods/default/textures/default_mineral_coal.png
new file mode 100644
index 000000000..0f52062af
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_mineral_coal.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_mineral_iron.png b/games/minimal/mods/default/textures/default_mineral_iron.png
new file mode 100644
index 000000000..6c894ce1c
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_mineral_iron.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_mossycobble.png b/games/minimal/mods/default/textures/default_mossycobble.png
new file mode 100644
index 000000000..5082953a6
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_mossycobble.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_nc_back.png b/games/minimal/mods/default/textures/default_nc_back.png
new file mode 100644
index 000000000..e479ace83
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_nc_back.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_nc_front.png b/games/minimal/mods/default/textures/default_nc_front.png
new file mode 100644
index 000000000..c9dd6a330
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_nc_front.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_nc_rb.png b/games/minimal/mods/default/textures/default_nc_rb.png
new file mode 100644
index 000000000..685a22ccf
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_nc_rb.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_nc_side.png b/games/minimal/mods/default/textures/default_nc_side.png
new file mode 100644
index 000000000..bc85427ae
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_nc_side.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_paper.png b/games/minimal/mods/default/textures/default_paper.png
new file mode 100644
index 000000000..3c31f77c2
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_paper.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_papyrus.png b/games/minimal/mods/default/textures/default_papyrus.png
new file mode 100644
index 000000000..3707e4081
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_papyrus.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_rail.png b/games/minimal/mods/default/textures/default_rail.png
new file mode 100644
index 000000000..777e10c00
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_rail.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_rail_crossing.png b/games/minimal/mods/default/textures/default_rail_crossing.png
new file mode 100644
index 000000000..a988c4740
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_rail_crossing.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_rail_curved.png b/games/minimal/mods/default/textures/default_rail_curved.png
new file mode 100644
index 000000000..f87e826c9
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_rail_curved.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_rail_t_junction.png b/games/minimal/mods/default/textures/default_rail_t_junction.png
new file mode 100644
index 000000000..fd25b5ba6
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_rail_t_junction.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_river_water.png b/games/minimal/mods/default/textures/default_river_water.png
new file mode 100644
index 000000000..3b55c5f66
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_river_water.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_sand.png b/games/minimal/mods/default/textures/default_sand.png
new file mode 100644
index 000000000..1a56cc737
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_sand.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_sandstone.png b/games/minimal/mods/default/textures/default_sandstone.png
new file mode 100644
index 000000000..bd9cb8692
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_sandstone.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_sapling.png b/games/minimal/mods/default/textures/default_sapling.png
new file mode 100644
index 000000000..93602322c
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_sapling.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_scorched_stuff.png b/games/minimal/mods/default/textures/default_scorched_stuff.png
new file mode 100644
index 000000000..c7efc7e58
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_scorched_stuff.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_sign_wall.png b/games/minimal/mods/default/textures/default_sign_wall.png
new file mode 100644
index 000000000..93c075a2d
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_sign_wall.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_steel_block.png b/games/minimal/mods/default/textures/default_steel_block.png
new file mode 100644
index 000000000..9c0a0e248
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_steel_block.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_steel_ingot.png b/games/minimal/mods/default/textures/default_steel_ingot.png
new file mode 100644
index 000000000..ad133bc12
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_steel_ingot.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_stick.png b/games/minimal/mods/default/textures/default_stick.png
new file mode 100644
index 000000000..055a6ac04
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_stick.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_stone.png b/games/minimal/mods/default/textures/default_stone.png
new file mode 100644
index 000000000..a835fe002
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_stone.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tnt_bottom.png b/games/minimal/mods/default/textures/default_tnt_bottom.png
new file mode 100644
index 000000000..4eda0603f
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tnt_bottom.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tnt_side.png b/games/minimal/mods/default/textures/default_tnt_side.png
new file mode 100644
index 000000000..c259726d6
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tnt_side.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tnt_top.png b/games/minimal/mods/default/textures/default_tnt_top.png
new file mode 100644
index 000000000..a031a34ad
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tnt_top.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tool_mesepick.png b/games/minimal/mods/default/textures/default_tool_mesepick.png
new file mode 100644
index 000000000..2b5e12cdb
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tool_mesepick.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tool_steelaxe.png b/games/minimal/mods/default/textures/default_tool_steelaxe.png
new file mode 100644
index 000000000..fae19dd0d
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tool_steelaxe.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tool_steelpick.png b/games/minimal/mods/default/textures/default_tool_steelpick.png
new file mode 100644
index 000000000..3a8f58643
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tool_steelpick.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tool_steelshovel.png b/games/minimal/mods/default/textures/default_tool_steelshovel.png
new file mode 100644
index 000000000..d5b1bc6bc
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tool_steelshovel.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tool_steelsword.png b/games/minimal/mods/default/textures/default_tool_steelsword.png
new file mode 100644
index 000000000..efc61a0d1
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tool_steelsword.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tool_stoneaxe.png b/games/minimal/mods/default/textures/default_tool_stoneaxe.png
new file mode 100644
index 000000000..532e16fbc
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tool_stoneaxe.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tool_stonepick.png b/games/minimal/mods/default/textures/default_tool_stonepick.png
new file mode 100644
index 000000000..d9156ee3a
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tool_stonepick.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tool_stoneshovel.png b/games/minimal/mods/default/textures/default_tool_stoneshovel.png
new file mode 100644
index 000000000..7bbd2d407
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tool_stoneshovel.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tool_stonesword.png b/games/minimal/mods/default/textures/default_tool_stonesword.png
new file mode 100644
index 000000000..533b8734f
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tool_stonesword.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tool_woodaxe.png b/games/minimal/mods/default/textures/default_tool_woodaxe.png
new file mode 100644
index 000000000..6e70f4a21
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tool_woodaxe.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tool_woodpick.png b/games/minimal/mods/default/textures/default_tool_woodpick.png
new file mode 100644
index 000000000..15c61f408
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tool_woodpick.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tool_woodshovel.png b/games/minimal/mods/default/textures/default_tool_woodshovel.png
new file mode 100644
index 000000000..e0b3608db
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tool_woodshovel.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tool_woodsword.png b/games/minimal/mods/default/textures/default_tool_woodsword.png
new file mode 100644
index 000000000..8099af139
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tool_woodsword.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_torch.png b/games/minimal/mods/default/textures/default_torch.png
new file mode 100644
index 000000000..21d98bdbb
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_torch.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_torch_on_ceiling.png b/games/minimal/mods/default/textures/default_torch_on_ceiling.png
new file mode 100644
index 000000000..dccd380b2
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_torch_on_ceiling.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_torch_on_floor.png b/games/minimal/mods/default/textures/default_torch_on_floor.png
new file mode 100644
index 000000000..c22010750
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_torch_on_floor.png
Binary files differ
diff --git a/data/tree.png b/games/minimal/mods/default/textures/default_tree.png
index 65abfc243..65abfc243 100644
--- a/data/tree.png
+++ b/games/minimal/mods/default/textures/default_tree.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_tree_top.png b/games/minimal/mods/default/textures/default_tree_top.png
new file mode 100644
index 000000000..3e6bd5262
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_tree_top.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/default_water.png b/games/minimal/mods/default/textures/default_water.png
new file mode 100644
index 000000000..3e385ae8b
--- /dev/null
+++ b/games/minimal/mods/default/textures/default_water.png
Binary files differ
diff --git a/data/wood.png b/games/minimal/mods/default/textures/default_wood.png
index 57c1d7c12..57c1d7c12 100644
--- a/data/wood.png
+++ b/games/minimal/mods/default/textures/default_wood.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/heart.png b/games/minimal/mods/default/textures/heart.png
new file mode 100644
index 000000000..c25f43b9a
--- /dev/null
+++ b/games/minimal/mods/default/textures/heart.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/player.png b/games/minimal/mods/default/textures/player.png
new file mode 100644
index 000000000..cf5d83f10
--- /dev/null
+++ b/games/minimal/mods/default/textures/player.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/player_back.png b/games/minimal/mods/default/textures/player_back.png
new file mode 100644
index 000000000..d498674eb
--- /dev/null
+++ b/games/minimal/mods/default/textures/player_back.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/treeprop.png b/games/minimal/mods/default/textures/treeprop.png
new file mode 100644
index 000000000..eb8a8e6c4
--- /dev/null
+++ b/games/minimal/mods/default/textures/treeprop.png
Binary files differ
diff --git a/games/minimal/mods/default/textures/wieldhand.png b/games/minimal/mods/default/textures/wieldhand.png
new file mode 100644
index 000000000..dbed6ad0c
--- /dev/null
+++ b/games/minimal/mods/default/textures/wieldhand.png
Binary files differ
diff --git a/games/minimal/mods/experimental/depends.txt b/games/minimal/mods/experimental/depends.txt
new file mode 100644
index 000000000..3a7daa1d7
--- /dev/null
+++ b/games/minimal/mods/experimental/depends.txt
@@ -0,0 +1,2 @@
+default
+
diff --git a/games/minimal/mods/experimental/init.lua b/games/minimal/mods/experimental/init.lua
new file mode 100644
index 000000000..6e0fb1738
--- /dev/null
+++ b/games/minimal/mods/experimental/init.lua
@@ -0,0 +1,644 @@
+--
+-- Experimental things
+--
+
+-- For testing random stuff
+
+experimental = {}
+
+function experimental.print_to_everything(msg)
+ minetest.log("action", msg)
+ minetest.chat_send_all(msg)
+end
+
+--[[
+experimental.player_visual_index = 0
+function switch_player_visual()
+ for _, obj in pairs(minetest.get_connected_players()) do
+ if experimental.player_visual_index == 0 then
+ obj:set_properties({visual="upright_sprite"})
+ else
+ obj:set_properties({visual="cube"})
+ end
+ end
+ experimental.player_visual_index = (experimental.player_visual_index + 1) % 2
+ minetest.after(1.0, switch_player_visual)
+end
+minetest.after(1.0, switch_player_visual)
+]]
+
+minetest.register_node("experimental:soundblock", {
+ tiles = {"unknown_node.png", "default_tnt_bottom.png",
+ "default_tnt_side.png", "default_tnt_side.png",
+ "default_tnt_side.png", "default_tnt_side.png"},
+ inventory_image = minetest.inventorycube("unknown_node.png",
+ "default_tnt_side.png", "default_tnt_side.png"),
+ groups = {dig_immediate=3},
+})
+
+minetest.register_alias("sb", "experimental:soundblock")
+
+minetest.register_abm({
+ nodenames = {"experimental:soundblock"},
+ interval = 1,
+ chance = 1,
+ action = function(p0, node, _, _)
+ minetest.sound_play("default_grass_footstep", {pos=p0, gain=0.5})
+ end,
+})
+
+--[[
+stepsound = -1
+function test_sound()
+ print("test_sound")
+ stepsound = minetest.sound_play("default_grass_footstep", {gain=1.0})
+ minetest.after(2.0, test_sound)
+ --minetest.after(0.1, test_sound_stop)
+end
+function test_sound_stop()
+ print("test_sound_stop")
+ minetest.sound_stop(stepsound)
+ minetest.after(2.0, test_sound)
+end
+test_sound()
+--]]
+
+function on_step(dtime)
+ -- print("experimental on_step")
+ --[[
+ objs = minetest.get_objects_inside_radius({x=0,y=0,z=0}, 10)
+ for k, obj in pairs(objs) do
+ name = obj:get_player_name()
+ if name then
+ print(name.." at "..dump(obj:getpos()))
+ print(name.." dir: "..dump(obj:get_look_dir()))
+ print(name.." pitch: "..dump(obj:get_look_pitch()))
+ print(name.." yaw: "..dump(obj:get_look_yaw()))
+ else
+ print("Some object at "..dump(obj:getpos()))
+ end
+ end
+ --]]
+ --[[
+ if experimental.t1 == nil then
+ experimental.t1 = 0
+ end
+ experimental.t1 = experimental.t1 + dtime
+ if experimental.t1 >= 2 then
+ experimental.t1 = experimental.t1 - 2
+ minetest.log("time of day is "..minetest.get_timeofday())
+ if experimental.day then
+ minetest.log("forcing day->night")
+ experimental.day = false
+ minetest.set_timeofday(0.0)
+ else
+ minetest.log("forcing night->day")
+ experimental.day = true
+ minetest.set_timeofday(0.5)
+ end
+ minetest.log("time of day is "..minetest.get_timeofday())
+ end
+ --]]
+end
+minetest.register_globalstep(on_step)
+
+--
+-- Random stuff
+--
+
+--
+-- TNT (not functional)
+--
+
+minetest.register_craft({
+ output = 'experimental:tnt',
+ recipe = {
+ {'default:wood'},
+ {'default:coal_lump'},
+ {'default:wood'}
+ }
+})
+
+minetest.register_node("experimental:tnt", {
+ tiles = {"default_tnt_top.png", "default_tnt_bottom.png",
+ "default_tnt_side.png", "default_tnt_side.png",
+ "default_tnt_side.png", "default_tnt_side.png"},
+ inventory_image = minetest.inventorycube("default_tnt_top.png",
+ "default_tnt_side.png", "default_tnt_side.png"),
+ drop = '', -- Get nothing
+ material = {
+ diggability = "not",
+ },
+})
+
+minetest.register_on_punchnode(function(p, node)
+ if node.name == "experimental:tnt" then
+ minetest.remove_node(p)
+ minetest.add_entity(p, "experimental:tnt")
+ nodeupdate(p)
+ end
+end)
+
+local TNT = {
+ -- Static definition
+ physical = true, -- Collides with things
+ -- weight = 5,
+ collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5},
+ visual = "cube",
+ textures = {"default_tnt_top.png", "default_tnt_bottom.png",
+ "default_tnt_side.png", "default_tnt_side.png",
+ "default_tnt_side.png", "default_tnt_side.png"},
+ -- Initial value for our timer
+ timer = 0,
+ -- Number of punches required to defuse
+ health = 1,
+ blinktimer = 0,
+ blinkstatus = true,
+}
+
+-- Called when a TNT object is created
+function TNT:on_activate(staticdata)
+ print("TNT:on_activate()")
+ self.object:setvelocity({x=0, y=4, z=0})
+ self.object:setacceleration({x=0, y=-10, z=0})
+ self.object:settexturemod("^[brighten")
+ self.object:set_armor_groups({immortal=1})
+end
+
+-- Called periodically
+function TNT:on_step(dtime)
+ --print("TNT:on_step()")
+ self.timer = self.timer + dtime
+ self.blinktimer = self.blinktimer + dtime
+ if self.blinktimer > 0.5 then
+ self.blinktimer = self.blinktimer - 0.5
+ if self.blinkstatus then
+ self.object:settexturemod("")
+ else
+ self.object:settexturemod("^[brighten")
+ end
+ self.blinkstatus = not self.blinkstatus
+ end
+end
+
+-- Called when object is punched
+function TNT:on_punch(hitter)
+ print("TNT:on_punch()")
+ self.health = self.health - 1
+ if self.health <= 0 then
+ self.object:remove()
+ hitter:get_inventory():add_item("main", "experimental:tnt")
+ --hitter:set_hp(hitter:get_hp() - 1)
+ end
+end
+
+-- Called when object is right-clicked
+function TNT:on_rightclick(clicker)
+ --pos = self.object:getpos()
+ --pos = {x=pos.x, y=pos.y+0.1, z=pos.z}
+ --self.object:moveto(pos, false)
+end
+
+--print("TNT dump: "..dump(TNT))
+--print("Registering TNT");
+minetest.register_entity("experimental:tnt", TNT)
+
+-- Add TNT's old name also
+minetest.register_alias("TNT", "experimental:tnt")
+
+--
+-- The dummyball!
+--
+
+minetest.register_entity("experimental:dummyball", {
+ initial_properties = {
+ hp_max = 20,
+ physical = false,
+ collisionbox = {-0.4,-0.4,-0.4, 0.4,0.4,0.4},
+ visual = "sprite",
+ visual_size = {x=1, y=1},
+ textures = {"experimental_dummyball.png"},
+ spritediv = {x=1, y=3},
+ initial_sprite_basepos = {x=0, y=0},
+ },
+
+ phase = 0,
+ phasetimer = 0,
+
+ on_activate = function(self, staticdata)
+ minetest.log("Dummyball activated!")
+ end,
+
+ on_step = function(self, dtime)
+ self.phasetimer = self.phasetimer + dtime
+ if self.phasetimer > 2.0 then
+ self.phasetimer = self.phasetimer - 2.0
+ self.phase = self.phase + 1
+ if self.phase >= 3 then
+ self.phase = 0
+ end
+ self.object:setsprite({x=0, y=self.phase})
+ phasearmor = {
+ [0]={cracky=3},
+ [1]={crumbly=3},
+ [2]={fleshy=3}
+ }
+ self.object:set_armor_groups(phasearmor[self.phase])
+ end
+ end,
+
+ on_punch = function(self, hitter)
+ end,
+})
+
+minetest.register_on_chat_message(function(name, message)
+ local cmd = "/dummyball"
+ if message:sub(0, #cmd) == cmd then
+ count = tonumber(message:sub(#cmd+1)) or 1
+ if not minetest.get_player_privs(name)["give"] then
+ minetest.chat_send_player(name, "you don't have permission to spawn (give)")
+ return true -- Handled chat message
+ end
+ if not minetest.get_player_privs(name)["interact"] then
+ minetest.chat_send_player(name, "you don't have permission to interact")
+ return true -- Handled chat message
+ end
+ if count >= 2 and not minetest.get_player_privs(name)["server"] then
+ minetest.chat_send_player(name, "you don't have " ..
+ "permission to spawn multiple " ..
+ "dummyballs (server)")
+ return true -- Handled chat message
+ end
+ local player = minetest.get_player_by_name(name)
+ if player == nil then
+ print("Unable to spawn entity, player is nil")
+ return true -- Handled chat message
+ end
+ local entityname = "experimental:dummyball"
+ local p = player:getpos()
+ p.y = p.y + 1
+ for i = 1,count do
+ minetest.add_entity(p, entityname)
+ end
+ minetest.chat_send_player(name, '"'..entityname
+ ..'" spawned '..tostring(count)..' time(s).');
+ return true -- Handled chat message
+ end
+end)
+
+--
+-- A test entity for testing animated and yaw-modulated sprites
+--
+
+minetest.register_entity("experimental:testentity", {
+ -- Static definition
+ physical = true, -- Collides with things
+ -- weight = 5,
+ collisionbox = {-0.7,-1.35,-0.7, 0.7,1.0,0.7},
+ --collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5},
+ visual = "sprite",
+ visual_size = {x=2, y=3},
+ textures = {"dungeon_master.png^[makealpha:128,0,0^[makealpha:128,128,0"},
+ spritediv = {x=6, y=5},
+ initial_sprite_basepos = {x=0, y=0},
+
+ on_activate = function(self, staticdata)
+ print("testentity.on_activate")
+ self.object:setsprite({x=0,y=0}, 1, 0, true)
+ --self.object:setsprite({x=0,y=0}, 4, 0.3, true)
+
+ -- Set gravity
+ self.object:setacceleration({x=0, y=-10, z=0})
+ -- Jump a bit upwards
+ self.object:setvelocity({x=0, y=10, z=0})
+ end,
+
+ on_punch = function(self, hitter)
+ self.object:remove()
+ hitter:add_to_inventory('craft testobject1 1')
+ end,
+})
+
+--
+-- More random stuff
+--
+
+minetest.register_on_respawnplayer(function(player)
+ print("on_respawnplayer")
+ -- player:setpos({x=0, y=30, z=0})
+ -- return true
+end)
+
+minetest.register_on_generated(function(minp, maxp)
+ --print("on_generated: minp="..dump(minp).." maxp="..dump(maxp))
+ --cp = {x=(minp.x+maxp.x)/2, y=(minp.y+maxp.y)/2, z=(minp.z+maxp.z)/2}
+ --minetest.add_node(cp, {name="sand"})
+end)
+
+-- Example setting get
+--print("setting max_users = " .. dump(minetest.setting_get("max_users")))
+--print("setting asdf = " .. dump(minetest.setting_get("asdf")))
+
+minetest.register_on_chat_message(function(name, message)
+ --[[print("on_chat_message: name="..dump(name).." message="..dump(message))
+ local cmd = "/testcommand"
+ if message:sub(0, #cmd) == cmd then
+ print(cmd.." invoked")
+ return true
+ end
+ local cmd = "/help"
+ if message:sub(0, #cmd) == cmd then
+ print("script-overridden help command")
+ minetest.chat_send_all("script-overridden help command")
+ return true
+ end]]
+end)
+
+-- Grow papyrus on TNT every 10 seconds
+--[[minetest.register_abm({
+ nodenames = {"TNT"},
+ interval = 10.0,
+ chance = 1,
+ action = function(pos, node, active_object_count, active_object_count_wider)
+ print("TNT ABM action")
+ pos.y = pos.y + 1
+ minetest.add_node(pos, {name="papyrus"})
+ end,
+})]]
+
+-- Replace texts of alls signs with "foo" every 10 seconds
+--[[minetest.register_abm({
+ nodenames = {"sign_wall"},
+ interval = 10.0,
+ chance = 1,
+ action = function(pos, node, active_object_count, active_object_count_wider)
+ print("ABM: Sign text changed")
+ local meta = minetest.get_meta(pos)
+ meta:set_text("foo")
+ end,
+})]]
+
+--[[local ncpos = nil
+local ncq = 1
+local ncstuff = {
+ {2, 1, 0, 3}, {3, 0, 1, 2}, {4, -1, 0, 1}, {5, -1, 0, 1}, {6, 0, -1, 0},
+ {7, 0, -1, 0}, {8, 1, 0, 3}, {9, 1, 0, 3}, {10, 1, 0, 3}, {11, 0, 1, 2},
+ {12, 0, 1, 2}, {13, 0, 1, 2}, {14, -1, 0, 1}, {15, -1, 0, 1}, {16, -1, 0, 1},
+ {17, -1, 0, 1}, {18, 0, -1, 0}, {19, 0, -1, 0}, {20, 0, -1, 0}, {21, 0, -1, 0},
+ {22, 1, 0, 3}, {23, 1, 0, 3}, {24, 1, 0, 3}, {25, 1, 0, 3}, {10, 0, 1, 2}
+}
+local ncold = {}
+local nctime = nil
+
+minetest.register_abm({
+ nodenames = {"dirt_with_grass"},
+ interval = 100000.0,
+ chance = 1,
+ action = function(pos, node, active_object_count, active_object_count_wider)
+ if ncpos ~= nil then
+ return
+ end
+
+ if pos.x % 16 ~= 8 or pos.z % 16 ~= 8 then
+ return
+ end
+
+ pos.y = pos.y + 1
+ n = minetest.get_node(pos)
+ print(dump(n))
+ if n.name ~= "air" then
+ return
+ end
+
+ pos.y = pos.y + 2
+ ncpos = pos
+ nctime = os.clock()
+ minetest.add_node(ncpos, {name="nyancat"})
+ end
+})
+
+minetest.register_abm({
+ nodenames = {"nyancat"},
+ interval = 1.0,
+ chance = 1,
+ action = function(pos, node, active_object_count, active_object_count_wider)
+ if ncpos == nil then
+ return
+ end
+ if pos.x == ncpos.x and pos.y == ncpos.y and pos.z == ncpos.z then
+ clock = os.clock()
+ if clock - nctime < 0.1 then
+ return
+ end
+ nctime = clock
+
+ s0 = ncstuff[ncq]
+ ncq = s0[1]
+ s1 = ncstuff[ncq]
+ p0 = pos
+ p1 = {x = p0.x + s0[2], y = p0.y, z = p0.z + s0[3]}
+ p2 = {x = p1.x + s1[2], y = p1.y, z = p1.z + s1[3]}
+ table.insert(ncold, 1, p0)
+ while #ncold >= 10 do
+ minetest.add_node(ncold[#ncold], {name="air"})
+ table.remove(ncold, #ncold)
+ end
+ minetest.add_node(p0, {name="nyancat_rainbow"})
+ minetest.add_node(p1, {name="nyancat", param1=s0[4]})
+ minetest.add_node(p2, {name="air"})
+ ncpos = p1
+ end
+ end,
+})--]]
+
+minetest.register_node("experimental:tester_node_1", {
+ description = "Tester Node 1 (construct/destruct/timer)",
+ tiles = {"wieldhand.png"},
+ groups = {oddly_breakable_by_hand=2},
+ sounds = default.node_sound_wood_defaults(),
+ -- This was known to cause a bug in minetest.item_place_node() when used
+ -- via minetest.place_node(), causing a placer with no position
+ paramtype2 = "facedir",
+
+ on_construct = function(pos)
+ experimental.print_to_everything("experimental:tester_node_1:on_construct("..minetest.pos_to_string(pos)..")")
+ local meta = minetest.get_meta(pos)
+ meta:set_string("mine", "test")
+ local timer = minetest.get_node_timer(pos)
+ timer:start(4, 3)
+ end,
+
+ after_place_node = function(pos, placer)
+ experimental.print_to_everything("experimental:tester_node_1:after_place_node("..minetest.pos_to_string(pos)..")")
+ local meta = minetest.get_meta(pos)
+ if meta:get_string("mine") == "test" then
+ experimental.print_to_everything("correct metadata found")
+ else
+ experimental.print_to_everything("incorrect metadata found")
+ end
+ end,
+
+ on_destruct = function(pos)
+ experimental.print_to_everything("experimental:tester_node_1:on_destruct("..minetest.pos_to_string(pos)..")")
+ end,
+
+ after_destruct = function(pos)
+ experimental.print_to_everything("experimental:tester_node_1:after_destruct("..minetest.pos_to_string(pos)..")")
+ end,
+
+ after_dig_node = function(pos, oldnode, oldmetadata, digger)
+ experimental.print_to_everything("experimental:tester_node_1:after_dig_node("..minetest.pos_to_string(pos)..")")
+ end,
+
+ on_timer = function(pos, elapsed)
+ experimental.print_to_everything("on_timer(): elapsed="..dump(elapsed))
+ return true
+ end,
+})
+
+minetest.register_craftitem("experimental:tester_tool_1", {
+ description = "Tester Tool 1",
+ inventory_image = "experimental_tester_tool_1.png",
+ on_use = function(itemstack, user, pointed_thing)
+ --print(dump(pointed_thing))
+ if pointed_thing.type == "node" then
+ local node = minetest.get_node(pointed_thing.under)
+ if node.name == "experimental:tester_node_1" or node.name == "default:chest" then
+ local p = pointed_thing.under
+ minetest.log("action", "Tester tool used at "..minetest.pos_to_string(p))
+ if node.name == "experimental:tester_node_1" then
+ minetest.dig_node(p)
+ else
+ minetest.get_meta(p):mark_as_private({"infotext", "formspec"})
+ minetest.chat_send_player(user:get_player_name(), "Verify that chest is unusable now.")
+ end
+ else
+ local p = pointed_thing.above
+ minetest.log("action", "Tester tool used at "..minetest.pos_to_string(p))
+ minetest.place_node(p, {name="experimental:tester_node_1"})
+ end
+ end
+ end,
+})
+
+minetest.register_craft({
+ output = 'experimental:tester_tool_1',
+ recipe = {
+ {'group:crumbly'},
+ {'group:crumbly'},
+ }
+})
+
+minetest.register_craftitem("experimental:tester_tool_2", {
+ description = "Tester Tool 2",
+ inventory_image = "experimental_tester_tool_1.png^[invert:g",
+ on_use = function(itemstack, user, pointed_thing)
+ local pos = minetest.get_pointed_thing_position(pointed_thing, true)
+ if pos == nil then return end
+ pos = vector.add(pos, {x=0, y=0.5, z=0})
+ local tex, anim
+ if math.random(0, 1) == 0 then
+ tex = "default_lava_source_animated.png"
+ anim = {type="sheet_2d", frames_w=3, frames_h=2, frame_length=0.5}
+ else
+ tex = "default_lava_flowing_animated.png"
+ anim = {type="vertical_frames", aspect_w=16, aspect_h=16, length=3.3}
+ end
+
+ minetest.add_particle({
+ pos = pos,
+ velocity = {x=0, y=0, z=0},
+ acceleration = {x=0, y=0.04, z=0},
+ expirationtime = 6,
+ collisiondetection = true,
+ texture = tex,
+ animation = anim,
+ size = 4,
+ glow = math.random(0, 5),
+ })
+ end,
+})
+
+minetest.register_craft({
+ output = 'experimental:tester_tool_2',
+ recipe = {
+ {'group:crumbly','group:crumbly'},
+ }
+})
+
+--[[minetest.register_on_joinplayer(function(player)
+ minetest.after(3, function()
+ player:set_inventory_formspec("size[8,7.5]"..
+ "image[1,0.6;1,2;player.png]"..
+ "list[current_player;main;0,3.5;8,4;]"..
+ "list[current_player;craft;3,0;3,3;]"..
+ "list[current_player;craftpreview;7,1;1,1;]")
+ end)
+end)]]
+
+-- Create a detached inventory
+local inv = minetest.create_detached_inventory("test_inventory", {
+ allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
+ experimental.print_to_everything("allow move asked")
+ return count -- Allow all
+ end,
+ allow_put = function(inv, listname, index, stack, player)
+ experimental.print_to_everything("allow put asked")
+ return 1 -- Allow only 1
+ end,
+ allow_take = function(inv, listname, index, stack, player)
+ experimental.print_to_everything("allow take asked")
+ return 4 -- Allow 4 at max
+ end,
+ on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
+ experimental.print_to_everything(player:get_player_name().." moved items")
+ end,
+ on_put = function(inv, listname, index, stack, player)
+ experimental.print_to_everything(player:get_player_name().." put items")
+ end,
+ on_take = function(inv, listname, index, stack, player)
+ experimental.print_to_everything(player:get_player_name().." took items")
+ end,
+})
+inv:set_size("main", 4*6)
+inv:add_item("main", "experimental:tester_tool_1")
+inv:add_item("main", "experimental:tnt 5")
+
+minetest.register_chatcommand("test1", {
+ params = "",
+ description = "Test 1: Modify player's inventory view",
+ func = function(name, param)
+ local player = minetest.get_player_by_name(name)
+ if not player then
+ return
+ end
+ player:set_inventory_formspec(
+ "size[13,7.5]"..
+ "image[6,0.6;1,2;player.png]"..
+ "list[current_player;main;5,3.5;8,4;]"..
+ "list[current_player;craft;8,0;3,3;]"..
+ "list[current_player;craftpreview;12,1;1,1;]"..
+ "list[detached:test_inventory;main;0,0;4,6;0]"..
+ "button[0.5,7;2,1;button1;Button 1]"..
+ "button_exit[2.5,7;2,1;button2;Exit Button]"
+ )
+ minetest.chat_send_player(name, "Done.");
+ end,
+})
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ experimental.print_to_everything("Inventory fields 1: player="..player:get_player_name()..", fields="..dump(fields))
+end)
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ experimental.print_to_everything("Inventory fields 2: player="..player:get_player_name()..", fields="..dump(fields))
+ return true -- Disable the first callback
+end)
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ experimental.print_to_everything("Inventory fields 3: player="..player:get_player_name()..", fields="..dump(fields))
+end)
+
+minetest.log("experimental modname="..dump(minetest.get_current_modname()))
+minetest.log("experimental modpath="..dump(minetest.get_modpath("experimental")))
+minetest.log("experimental worldpath="..dump(minetest.get_worldpath()))
+
+-- END
diff --git a/games/minimal/mods/experimental/textures/experimental_dummyball.png b/games/minimal/mods/experimental/textures/experimental_dummyball.png
new file mode 100644
index 000000000..256414f46
--- /dev/null
+++ b/games/minimal/mods/experimental/textures/experimental_dummyball.png
Binary files differ
diff --git a/games/minimal/mods/experimental/textures/experimental_tester_tool_1.png b/games/minimal/mods/experimental/textures/experimental_tester_tool_1.png
new file mode 100644
index 000000000..587923c86
--- /dev/null
+++ b/games/minimal/mods/experimental/textures/experimental_tester_tool_1.png
Binary files differ
diff --git a/games/minimal/mods/give_initial_stuff/depends.txt b/games/minimal/mods/give_initial_stuff/depends.txt
new file mode 100644
index 000000000..3a7daa1d7
--- /dev/null
+++ b/games/minimal/mods/give_initial_stuff/depends.txt
@@ -0,0 +1,2 @@
+default
+
diff --git a/games/minimal/mods/give_initial_stuff/init.lua b/games/minimal/mods/give_initial_stuff/init.lua
new file mode 100644
index 000000000..29b835c7d
--- /dev/null
+++ b/games/minimal/mods/give_initial_stuff/init.lua
@@ -0,0 +1,16 @@
+minetest.register_on_newplayer(function(player)
+ print("[minimal] giving initial stuff to player")
+ player:get_inventory():add_item('main', 'default:pick_stone')
+ player:get_inventory():add_item('main', 'default:torch 99')
+ player:get_inventory():add_item('main', 'default:cobble 99')
+ player:get_inventory():add_item('main', 'default:wood 99')
+ player:get_inventory():add_item('main', 'default:axe_steel')
+ player:get_inventory():add_item('main', 'default:shovel_steel')
+ player:get_inventory():add_item('main', 'default:pick_wood')
+ player:get_inventory():add_item('main', 'default:pick_steel')
+ player:get_inventory():add_item('main', 'default:pick_mese')
+ player:get_inventory():add_item('main', 'default:mese 99')
+ player:get_inventory():add_item('main', 'default:water_source 99')
+ player:get_inventory():add_item('main', 'experimental:tester_tool_1')
+end)
+
diff --git a/games/minimal/mods/legacy/depends.txt b/games/minimal/mods/legacy/depends.txt
new file mode 100644
index 000000000..3a7daa1d7
--- /dev/null
+++ b/games/minimal/mods/legacy/depends.txt
@@ -0,0 +1,2 @@
+default
+
diff --git a/games/minimal/mods/legacy/init.lua b/games/minimal/mods/legacy/init.lua
new file mode 100644
index 000000000..98ad69be0
--- /dev/null
+++ b/games/minimal/mods/legacy/init.lua
@@ -0,0 +1,128 @@
+-- legacy (Minetest 0.4 mod)
+-- Provides as much backwards-compatibility as feasible
+
+--
+-- Aliases to support loading 0.3 and old 0.4 worlds and inventories
+--
+
+minetest.register_alias("stone", "default:stone")
+minetest.register_alias("stone_with_coal", "default:stone_with_coal")
+minetest.register_alias("stone_with_iron", "default:stone_with_iron")
+minetest.register_alias("dirt_with_grass", "default:dirt_with_grass")
+minetest.register_alias("dirt_with_grass_footsteps", "default:dirt_with_grass_footsteps")
+minetest.register_alias("dirt", "default:dirt")
+minetest.register_alias("sand", "default:sand")
+minetest.register_alias("gravel", "default:gravel")
+minetest.register_alias("sandstone", "default:sandstone")
+minetest.register_alias("clay", "default:clay")
+minetest.register_alias("brick", "default:brick")
+minetest.register_alias("tree", "default:tree")
+minetest.register_alias("jungletree", "default:jungletree")
+minetest.register_alias("junglegrass", "default:junglegrass")
+minetest.register_alias("leaves", "default:leaves")
+minetest.register_alias("cactus", "default:cactus")
+minetest.register_alias("papyrus", "default:papyrus")
+minetest.register_alias("bookshelf", "default:bookshelf")
+minetest.register_alias("glass", "default:glass")
+minetest.register_alias("wooden_fence", "default:fence_wood")
+minetest.register_alias("rail", "default:rail")
+minetest.register_alias("ladder", "default:ladder")
+minetest.register_alias("wood", "default:wood")
+minetest.register_alias("mese", "default:mese")
+minetest.register_alias("cloud", "default:cloud")
+minetest.register_alias("water_flowing", "default:water_flowing")
+minetest.register_alias("water_source", "default:water_source")
+minetest.register_alias("lava_flowing", "default:lava_flowing")
+minetest.register_alias("lava_source", "default:lava_source")
+minetest.register_alias("torch", "default:torch")
+minetest.register_alias("sign_wall", "default:sign_wall")
+minetest.register_alias("furnace", "default:furnace")
+minetest.register_alias("chest", "default:chest")
+minetest.register_alias("locked_chest", "default:chest_locked")
+minetest.register_alias("cobble", "default:cobble")
+minetest.register_alias("mossycobble", "default:mossycobble")
+minetest.register_alias("steelblock", "default:steelblock")
+minetest.register_alias("nyancat", "default:nyancat")
+minetest.register_alias("nyancat_rainbow", "default:nyancat_rainbow")
+minetest.register_alias("sapling", "default:sapling")
+minetest.register_alias("apple", "default:apple")
+
+minetest.register_alias("WPick", "default:pick_wood")
+minetest.register_alias("STPick", "default:pick_stone")
+minetest.register_alias("SteelPick", "default:pick_steel")
+minetest.register_alias("MesePick", "default:pick_mese")
+minetest.register_alias("WShovel", "default:shovel_wood")
+minetest.register_alias("STShovel", "default:shovel_stone")
+minetest.register_alias("SteelShovel", "default:shovel_steel")
+minetest.register_alias("WAxe", "default:axe_wood")
+minetest.register_alias("STAxe", "default:axe_stone")
+minetest.register_alias("SteelAxe", "default:axe_steel")
+minetest.register_alias("WSword", "default:sword_wood")
+minetest.register_alias("STSword", "default:sword_stone")
+minetest.register_alias("SteelSword", "default:sword_steel")
+
+minetest.register_alias("Stick", "default:stick")
+minetest.register_alias("paper", "default:paper")
+minetest.register_alias("book", "default:book")
+minetest.register_alias("lump_of_coal", "default:coal_lump")
+minetest.register_alias("lump_of_iron", "default:iron_lump")
+minetest.register_alias("lump_of_clay", "default:clay_lump")
+minetest.register_alias("steel_ingot", "default:steel_ingot")
+minetest.register_alias("clay_brick", "default:clay_brick")
+minetest.register_alias("scorched_stuff", "default:scorched_stuff")
+
+--
+-- Old items
+--
+
+minetest.register_craftitem(":rat", {
+ description = "Rat",
+ inventory_image = "rat.png",
+ on_drop = function(item, dropper, pos)
+ item:take_item()
+ return item
+ end,
+ on_place = function(item, dropped, pointed)
+ pos = minetest.get_pointed_thing_position(pointed, true)
+ if pos ~= nil then
+ item:take_item()
+ return item
+ end
+ end
+})
+
+minetest.register_craftitem(":cooked_rat", {
+ description = "Cooked rat",
+ inventory_image = "cooked_rat.png",
+ on_use = minetest.item_eat(6),
+})
+
+minetest.register_craftitem(":firefly", {
+ description = "Firefly",
+ inventory_image = "firefly.png",
+ on_drop = function(item, dropper, pos)
+ item:take_item()
+ return item
+ end,
+ on_place = function(item, dropped, pointed)
+ pos = minetest.get_pointed_thing_position(pointed, true)
+ if pos ~= nil then
+ item:take_item()
+ return item
+ end
+ end
+})
+
+minetest.register_craft({
+ type = "cooking",
+ output = "cooked_rat",
+ recipe = "rat",
+})
+
+minetest.register_craft({
+ type = "cooking",
+ output = "scorched_stuff",
+ recipe = "cooked_rat",
+})
+
+-- END
diff --git a/games/minimal/mods/legacy/textures/apple_iron.png b/games/minimal/mods/legacy/textures/apple_iron.png
new file mode 100644
index 000000000..db5945856
--- /dev/null
+++ b/games/minimal/mods/legacy/textures/apple_iron.png
Binary files differ
diff --git a/games/minimal/mods/legacy/textures/cooked_rat.png b/games/minimal/mods/legacy/textures/cooked_rat.png
new file mode 100644
index 000000000..776dc4ee0
--- /dev/null
+++ b/games/minimal/mods/legacy/textures/cooked_rat.png
Binary files differ
diff --git a/games/minimal/mods/legacy/textures/dungeon_master.png b/games/minimal/mods/legacy/textures/dungeon_master.png
new file mode 100644
index 000000000..d52d8ccd7
--- /dev/null
+++ b/games/minimal/mods/legacy/textures/dungeon_master.png
Binary files differ
diff --git a/games/minimal/mods/legacy/textures/fireball.png b/games/minimal/mods/legacy/textures/fireball.png
new file mode 100644
index 000000000..124469ce4
--- /dev/null
+++ b/games/minimal/mods/legacy/textures/fireball.png
Binary files differ
diff --git a/games/minimal/mods/legacy/textures/firefly.png b/games/minimal/mods/legacy/textures/firefly.png
new file mode 100644
index 000000000..ea95a6a84
--- /dev/null
+++ b/games/minimal/mods/legacy/textures/firefly.png
Binary files differ
diff --git a/games/minimal/mods/legacy/textures/oerkki1.png b/games/minimal/mods/legacy/textures/oerkki1.png
new file mode 100644
index 000000000..061709c80
--- /dev/null
+++ b/games/minimal/mods/legacy/textures/oerkki1.png
Binary files differ
diff --git a/games/minimal/mods/legacy/textures/oerkki1_damaged.png b/games/minimal/mods/legacy/textures/oerkki1_damaged.png
new file mode 100644
index 000000000..b2a30337e
--- /dev/null
+++ b/games/minimal/mods/legacy/textures/oerkki1_damaged.png
Binary files differ
diff --git a/games/minimal/mods/legacy/textures/rat.png b/games/minimal/mods/legacy/textures/rat.png
new file mode 100644
index 000000000..04cf9b865
--- /dev/null
+++ b/games/minimal/mods/legacy/textures/rat.png
Binary files differ
diff --git a/games/minimal/mods/stairs/depends.txt b/games/minimal/mods/stairs/depends.txt
new file mode 100644
index 000000000..4ad96d515
--- /dev/null
+++ b/games/minimal/mods/stairs/depends.txt
@@ -0,0 +1 @@
+default
diff --git a/games/minimal/mods/stairs/init.lua b/games/minimal/mods/stairs/init.lua
new file mode 100644
index 000000000..c3bbc561e
--- /dev/null
+++ b/games/minimal/mods/stairs/init.lua
@@ -0,0 +1,93 @@
+stairs = {}
+
+-- Node will be called stairs:stair_<subname>
+function stairs.register_stair(subname, recipeitem, groups, images, description)
+ minetest.register_node("stairs:stair_" .. subname, {
+ description = description,
+ drawtype = "nodebox",
+ tiles = images,
+ paramtype = "light",
+ paramtype2 = "facedir",
+ is_ground_content = true,
+ groups = groups,
+ node_box = {
+ type = "fixed",
+ fixed = {
+ {-0.5, -0.5, -0.5, 0.5, 0, 0.5},
+ {-0.5, 0, 0, 0.5, 0.5, 0.5},
+ },
+ },
+ })
+
+ minetest.register_craft({
+ output = 'stairs:stair_' .. subname .. ' 4',
+ recipe = {
+ {recipeitem, "", ""},
+ {recipeitem, recipeitem, ""},
+ {recipeitem, recipeitem, recipeitem},
+ },
+ })
+end
+
+-- Node will be called stairs:slab_<subname>
+function stairs.register_slab(subname, recipeitem, groups, images, description)
+ minetest.register_node("stairs:slab_" .. subname, {
+ description = description,
+ drawtype = "nodebox",
+ tiles = images,
+ paramtype = "light",
+ is_ground_content = true,
+ groups = groups,
+ node_box = {
+ type = "fixed",
+ fixed = {-0.5, -0.5, -0.5, 0.5, 0, 0.5},
+ },
+ selection_box = {
+ type = "fixed",
+ fixed = {-0.5, -0.5, -0.5, 0.5, 0, 0.5},
+ },
+ })
+
+ minetest.register_craft({
+ output = 'stairs:slab_' .. subname .. ' 3',
+ recipe = {
+ {recipeitem, recipeitem, recipeitem},
+ },
+ })
+end
+
+-- Nodes will be called stairs:{stair,slab}_<subname>
+function stairs.register_stair_and_slab(subname, recipeitem, groups, images, desc_stair, desc_slab)
+ stairs.register_stair(subname, recipeitem, groups, images, desc_stair)
+ stairs.register_slab(subname, recipeitem, groups, images, desc_slab)
+end
+
+stairs.register_stair_and_slab("wood", "default:wood",
+ {snappy=2,choppy=2,oddly_breakable_by_hand=2},
+ {"default_wood.png"},
+ "Wooden stair",
+ "Wooden slab")
+
+stairs.register_stair_and_slab("stone", "default:stone",
+ {cracky=3},
+ {"default_stone.png"},
+ "Stone stair",
+ "Stone slab")
+
+stairs.register_stair_and_slab("cobble", "default:cobble",
+ {cracky=3},
+ {"default_cobble.png"},
+ "Cobble stair",
+ "Cobble slab")
+
+stairs.register_stair_and_slab("brick", "default:brick",
+ {cracky=3},
+ {"default_brick.png"},
+ "Brick stair",
+ "Brick slab")
+
+stairs.register_stair_and_slab("sandstone", "default:sandstone",
+ {crumbly=2,cracky=2},
+ {"default_sandstone.png"},
+ "Sandstone stair",
+ "Sandstone slab")
diff --git a/games/minimal/mods/test/init.lua b/games/minimal/mods/test/init.lua
new file mode 100644
index 000000000..051b47906
--- /dev/null
+++ b/games/minimal/mods/test/init.lua
@@ -0,0 +1,11 @@
+--
+-- Minimal Development Test
+-- Mod: test
+--
+
+-- Try out PseudoRandom
+pseudo = PseudoRandom(13)
+assert(pseudo:next() == 22290)
+assert(pseudo:next() == 13854)
+
+
diff --git a/genmap.py b/genmap.py
deleted file mode 100755
index 8949888a4..000000000
--- a/genmap.py
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/python
-
-import struct
-import random
-
-def getrand():
- i = random.randrange(0,2)
- if i==0:
- return 0
- return 254
-
-"""
-Map format:
-map/sectors/XXXXZZZZ/YYYY
-
-XXXX,YYYY,ZZZZ = coordinates in hexadecimal
-
-fffe = -2
-ffff = -1
-0000 = 0
-0001 = 1
-"""
-
-f = open("map/sectors/00000000/ffff", "wb")
-
-# version
-f.write(struct.pack('B', 2))
-# is_underground
-f.write(struct.pack('B', 0))
-
-for i in range(0,16*16*16):
- # Material content
- f.write(struct.pack('B', getrand()))
- # Brightness
- f.write(struct.pack('B', 15))
-
-f.close()
-
diff --git a/lib/gmp/CMakeLists.txt b/lib/gmp/CMakeLists.txt
new file mode 100644
index 000000000..96ae8191d
--- /dev/null
+++ b/lib/gmp/CMakeLists.txt
@@ -0,0 +1,7 @@
+if(MSVC)
+ set(CMAKE_C_FLAGS_RELEASE "/MT /O2 /Ob2 /D NDEBUG")
+endif()
+
+add_library(gmp mini-gmp.c)
+target_link_libraries(gmp)
+
diff --git a/lib/gmp/mini-gmp.c b/lib/gmp/mini-gmp.c
new file mode 100644
index 000000000..f3b43fbe8
--- /dev/null
+++ b/lib/gmp/mini-gmp.c
@@ -0,0 +1,4130 @@
+/* mini-gmp, a minimalistic implementation of a GNU GMP subset.
+
+ Contributed to the GNU project by Niels Möller
+
+Copyright 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1999, 2000, 2001,
+2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
+Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 3 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with the GNU MP Library. If not, see http://www.gnu.org/licenses/. */
+
+/* NOTE: All functions in this file which are not declared in
+ mini-gmp.h are internal, and are not intended to be compatible
+ neither with GMP nor with future versions of mini-gmp. */
+
+/* Much of the material copied from GMP files, including: gmp-impl.h,
+ longlong.h, mpn/generic/add_n.c, mpn/generic/addmul_1.c,
+ mpn/generic/lshift.c, mpn/generic/mul_1.c,
+ mpn/generic/mul_basecase.c, mpn/generic/rshift.c,
+ mpn/generic/sbpi1_div_qr.c, mpn/generic/sub_n.c,
+ mpn/generic/submul_1.c. */
+
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mini-gmp.h"
+
+
+/* Macros */
+#define GMP_LIMB_BITS (sizeof(mp_limb_t) * CHAR_BIT)
+
+#define GMP_LIMB_MAX (~ (mp_limb_t) 0)
+#define GMP_LIMB_HIGHBIT ((mp_limb_t) 1 << (GMP_LIMB_BITS - 1))
+
+#define GMP_HLIMB_BIT ((mp_limb_t) 1 << (GMP_LIMB_BITS / 2))
+#define GMP_LLIMB_MASK (GMP_HLIMB_BIT - 1)
+
+#define GMP_ULONG_BITS (sizeof(unsigned long) * CHAR_BIT)
+#define GMP_ULONG_HIGHBIT ((unsigned long) 1 << (GMP_ULONG_BITS - 1))
+
+#define GMP_ABS(x) ((x) >= 0 ? (x) : -(x))
+#define GMP_NEG_CAST(T,x) (-((T)((x) + 1) - 1))
+
+#define GMP_MIN(a, b) ((a) < (b) ? (a) : (b))
+#define GMP_MAX(a, b) ((a) > (b) ? (a) : (b))
+
+#define gmp_assert_nocarry(x) do { \
+ mp_limb_t __cy = x; \
+ assert (__cy == 0); \
+ } while (0)
+
+#define gmp_clz(count, x) do { \
+ mp_limb_t __clz_x = (x); \
+ unsigned __clz_c; \
+ for (__clz_c = 0; \
+ (__clz_x & ((mp_limb_t) 0xff << (GMP_LIMB_BITS - 8))) == 0; \
+ __clz_c += 8) \
+ __clz_x <<= 8; \
+ for (; (__clz_x & GMP_LIMB_HIGHBIT) == 0; __clz_c++) \
+ __clz_x <<= 1; \
+ (count) = __clz_c; \
+ } while (0)
+
+#define gmp_ctz(count, x) do { \
+ mp_limb_t __ctz_x = (x); \
+ unsigned __ctz_c = 0; \
+ gmp_clz (__ctz_c, __ctz_x & - __ctz_x); \
+ (count) = GMP_LIMB_BITS - 1 - __ctz_c; \
+ } while (0)
+
+#define gmp_add_ssaaaa(sh, sl, ah, al, bh, bl) \
+ do { \
+ mp_limb_t __x; \
+ __x = (al) + (bl); \
+ (sh) = (ah) + (bh) + (__x < (al)); \
+ (sl) = __x; \
+ } while (0)
+
+#define gmp_sub_ddmmss(sh, sl, ah, al, bh, bl) \
+ do { \
+ mp_limb_t __x; \
+ __x = (al) - (bl); \
+ (sh) = (ah) - (bh) - ((al) < (bl)); \
+ (sl) = __x; \
+ } while (0)
+
+#define gmp_umul_ppmm(w1, w0, u, v) \
+ do { \
+ mp_limb_t __x0, __x1, __x2, __x3; \
+ unsigned __ul, __vl, __uh, __vh; \
+ mp_limb_t __u = (u), __v = (v); \
+ \
+ __ul = __u & GMP_LLIMB_MASK; \
+ __uh = __u >> (GMP_LIMB_BITS / 2); \
+ __vl = __v & GMP_LLIMB_MASK; \
+ __vh = __v >> (GMP_LIMB_BITS / 2); \
+ \
+ __x0 = (mp_limb_t) __ul * __vl; \
+ __x1 = (mp_limb_t) __ul * __vh; \
+ __x2 = (mp_limb_t) __uh * __vl; \
+ __x3 = (mp_limb_t) __uh * __vh; \
+ \
+ __x1 += __x0 >> (GMP_LIMB_BITS / 2);/* this can't give carry */ \
+ __x1 += __x2; /* but this indeed can */ \
+ if (__x1 < __x2) /* did we get it? */ \
+ __x3 += GMP_HLIMB_BIT; /* yes, add it in the proper pos. */ \
+ \
+ (w1) = __x3 + (__x1 >> (GMP_LIMB_BITS / 2)); \
+ (w0) = (__x1 << (GMP_LIMB_BITS / 2)) + (__x0 & GMP_LLIMB_MASK); \
+ } while (0)
+
+#define gmp_udiv_qrnnd_preinv(q, r, nh, nl, d, di) \
+ do { \
+ mp_limb_t _qh, _ql, _r, _mask; \
+ gmp_umul_ppmm (_qh, _ql, (nh), (di)); \
+ gmp_add_ssaaaa (_qh, _ql, _qh, _ql, (nh) + 1, (nl)); \
+ _r = (nl) - _qh * (d); \
+ _mask = -(mp_limb_t) (_r > _ql); /* both > and >= are OK */ \
+ _qh += _mask; \
+ _r += _mask & (d); \
+ if (_r >= (d)) \
+ { \
+ _r -= (d); \
+ _qh++; \
+ } \
+ \
+ (r) = _r; \
+ (q) = _qh; \
+ } while (0)
+
+#define gmp_udiv_qr_3by2(q, r1, r0, n2, n1, n0, d1, d0, dinv) \
+ do { \
+ mp_limb_t _q0, _t1, _t0, _mask; \
+ gmp_umul_ppmm ((q), _q0, (n2), (dinv)); \
+ gmp_add_ssaaaa ((q), _q0, (q), _q0, (n2), (n1)); \
+ \
+ /* Compute the two most significant limbs of n - q'd */ \
+ (r1) = (n1) - (d1) * (q); \
+ gmp_sub_ddmmss ((r1), (r0), (r1), (n0), (d1), (d0)); \
+ gmp_umul_ppmm (_t1, _t0, (d0), (q)); \
+ gmp_sub_ddmmss ((r1), (r0), (r1), (r0), _t1, _t0); \
+ (q)++; \
+ \
+ /* Conditionally adjust q and the remainders */ \
+ _mask = - (mp_limb_t) ((r1) >= _q0); \
+ (q) += _mask; \
+ gmp_add_ssaaaa ((r1), (r0), (r1), (r0), _mask & (d1), _mask & (d0)); \
+ if ((r1) >= (d1)) \
+ { \
+ if ((r1) > (d1) || (r0) >= (d0)) \
+ { \
+ (q)++; \
+ gmp_sub_ddmmss ((r1), (r0), (r1), (r0), (d1), (d0)); \
+ } \
+ } \
+ } while (0)
+
+/* Swap macros. */
+#define MP_LIMB_T_SWAP(x, y) \
+ do { \
+ mp_limb_t __mp_limb_t_swap__tmp = (x); \
+ (x) = (y); \
+ (y) = __mp_limb_t_swap__tmp; \
+ } while (0)
+#define MP_SIZE_T_SWAP(x, y) \
+ do { \
+ mp_size_t __mp_size_t_swap__tmp = (x); \
+ (x) = (y); \
+ (y) = __mp_size_t_swap__tmp; \
+ } while (0)
+#define MP_BITCNT_T_SWAP(x,y) \
+ do { \
+ mp_bitcnt_t __mp_bitcnt_t_swap__tmp = (x); \
+ (x) = (y); \
+ (y) = __mp_bitcnt_t_swap__tmp; \
+ } while (0)
+#define MP_PTR_SWAP(x, y) \
+ do { \
+ mp_ptr __mp_ptr_swap__tmp = (x); \
+ (x) = (y); \
+ (y) = __mp_ptr_swap__tmp; \
+ } while (0)
+#define MP_SRCPTR_SWAP(x, y) \
+ do { \
+ mp_srcptr __mp_srcptr_swap__tmp = (x); \
+ (x) = (y); \
+ (y) = __mp_srcptr_swap__tmp; \
+ } while (0)
+
+#define MPN_PTR_SWAP(xp,xs, yp,ys) \
+ do { \
+ MP_PTR_SWAP (xp, yp); \
+ MP_SIZE_T_SWAP (xs, ys); \
+ } while(0)
+#define MPN_SRCPTR_SWAP(xp,xs, yp,ys) \
+ do { \
+ MP_SRCPTR_SWAP (xp, yp); \
+ MP_SIZE_T_SWAP (xs, ys); \
+ } while(0)
+
+#define MPZ_PTR_SWAP(x, y) \
+ do { \
+ mpz_ptr __mpz_ptr_swap__tmp = (x); \
+ (x) = (y); \
+ (y) = __mpz_ptr_swap__tmp; \
+ } while (0)
+#define MPZ_SRCPTR_SWAP(x, y) \
+ do { \
+ mpz_srcptr __mpz_srcptr_swap__tmp = (x); \
+ (x) = (y); \
+ (y) = __mpz_srcptr_swap__tmp; \
+ } while (0)
+
+
+/* Memory allocation and other helper functions. */
+static void
+gmp_die (const char *msg)
+{
+ fprintf (stderr, "%s\n", msg);
+ abort();
+}
+
+static void *
+gmp_default_alloc (size_t size)
+{
+ void *p;
+
+ assert (size > 0);
+
+ p = malloc (size);
+ if (!p)
+ gmp_die("gmp_default_alloc: Virtual memory exhausted.");
+
+ return p;
+}
+
+static void *
+gmp_default_realloc (void *old, size_t old_size, size_t new_size)
+{
+ mp_ptr p;
+
+ p = realloc (old, new_size);
+
+ if (!p)
+ gmp_die("gmp_default_realoc: Virtual memory exhausted.");
+
+ return p;
+}
+
+static void
+gmp_default_free (void *p, size_t size)
+{
+ free (p);
+}
+
+static void * (*gmp_allocate_func) (size_t) = gmp_default_alloc;
+static void * (*gmp_reallocate_func) (void *, size_t, size_t) = gmp_default_realloc;
+static void (*gmp_free_func) (void *, size_t) = gmp_default_free;
+
+void
+mp_get_memory_functions (void *(**alloc_func) (size_t),
+ void *(**realloc_func) (void *, size_t, size_t),
+ void (**free_func) (void *, size_t))
+{
+ if (alloc_func)
+ *alloc_func = gmp_allocate_func;
+
+ if (realloc_func)
+ *realloc_func = gmp_reallocate_func;
+
+ if (free_func)
+ *free_func = gmp_free_func;
+}
+
+void
+mp_set_memory_functions (void *(*alloc_func) (size_t),
+ void *(*realloc_func) (void *, size_t, size_t),
+ void (*free_func) (void *, size_t))
+{
+ if (!alloc_func)
+ alloc_func = gmp_default_alloc;
+ if (!realloc_func)
+ realloc_func = gmp_default_realloc;
+ if (!free_func)
+ free_func = gmp_default_free;
+
+ gmp_allocate_func = alloc_func;
+ gmp_reallocate_func = realloc_func;
+ gmp_free_func = free_func;
+}
+
+#define gmp_xalloc(size) ((*gmp_allocate_func)((size)))
+#define gmp_free(p) ((*gmp_free_func) ((p), 0))
+
+static mp_ptr
+gmp_xalloc_limbs (mp_size_t size)
+{
+ return gmp_xalloc (size * sizeof (mp_limb_t));
+}
+
+static mp_ptr
+gmp_xrealloc_limbs (mp_ptr old, mp_size_t size)
+{
+ assert (size > 0);
+ return (*gmp_reallocate_func) (old, 0, size * sizeof (mp_limb_t));
+}
+
+
+/* MPN interface */
+
+void
+mpn_copyi (mp_ptr d, mp_srcptr s, mp_size_t n)
+{
+ mp_size_t i;
+ for (i = 0; i < n; i++)
+ d[i] = s[i];
+}
+
+void
+mpn_copyd (mp_ptr d, mp_srcptr s, mp_size_t n)
+{
+ while (n-- > 0)
+ d[n] = s[n];
+}
+
+int
+mpn_cmp (mp_srcptr ap, mp_srcptr bp, mp_size_t n)
+{
+ for (; n > 0; n--)
+ {
+ if (ap[n-1] < bp[n-1])
+ return -1;
+ else if (ap[n-1] > bp[n-1])
+ return 1;
+ }
+ return 0;
+}
+
+static int
+mpn_cmp4 (mp_srcptr ap, mp_size_t an, mp_srcptr bp, mp_size_t bn)
+{
+ if (an > bn)
+ return 1;
+ else if (an < bn)
+ return -1;
+ else
+ return mpn_cmp (ap, bp, an);
+}
+
+static mp_size_t
+mpn_normalized_size (mp_srcptr xp, mp_size_t n)
+{
+ for (; n > 0 && xp[n-1] == 0; n--)
+ ;
+ return n;
+}
+
+#define mpn_zero_p(xp, n) (mpn_normalized_size ((xp), (n)) == 0)
+
+mp_limb_t
+mpn_add_1 (mp_ptr rp, mp_srcptr ap, mp_size_t n, mp_limb_t b)
+{
+ mp_size_t i;
+
+ assert (n > 0);
+
+ for (i = 0; i < n; i++)
+ {
+ mp_limb_t r = ap[i] + b;
+ /* Carry out */
+ b = (r < b);
+ rp[i] = r;
+ }
+ return b;
+}
+
+mp_limb_t
+mpn_add_n (mp_ptr rp, mp_srcptr ap, mp_srcptr bp, mp_size_t n)
+{
+ mp_size_t i;
+ mp_limb_t cy;
+
+ for (i = 0, cy = 0; i < n; i++)
+ {
+ mp_limb_t a, b, r;
+ a = ap[i]; b = bp[i];
+ r = a + cy;
+ cy = (r < cy);
+ r += b;
+ cy += (r < b);
+ rp[i] = r;
+ }
+ return cy;
+}
+
+mp_limb_t
+mpn_add (mp_ptr rp, mp_srcptr ap, mp_size_t an, mp_srcptr bp, mp_size_t bn)
+{
+ mp_limb_t cy;
+
+ assert (an >= bn);
+
+ cy = mpn_add_n (rp, ap, bp, bn);
+ if (an > bn)
+ cy = mpn_add_1 (rp + bn, ap + bn, an - bn, cy);
+ return cy;
+}
+
+mp_limb_t
+mpn_sub_1 (mp_ptr rp, mp_srcptr ap, mp_size_t n, mp_limb_t b)
+{
+ mp_size_t i;
+
+ assert (n > 0);
+
+ for (i = 0; i < n; i++)
+ {
+ mp_limb_t a = ap[i];
+ /* Carry out */
+ mp_limb_t cy = a < b;;
+ rp[i] = a - b;
+ b = cy;
+ }
+ return b;
+}
+
+mp_limb_t
+mpn_sub_n (mp_ptr rp, mp_srcptr ap, mp_srcptr bp, mp_size_t n)
+{
+ mp_size_t i;
+ mp_limb_t cy;
+
+ for (i = 0, cy = 0; i < n; i++)
+ {
+ mp_limb_t a, b;
+ a = ap[i]; b = bp[i];
+ b += cy;
+ cy = (b < cy);
+ cy += (a < b);
+ rp[i] = a - b;
+ }
+ return cy;
+}
+
+mp_limb_t
+mpn_sub (mp_ptr rp, mp_srcptr ap, mp_size_t an, mp_srcptr bp, mp_size_t bn)
+{
+ mp_limb_t cy;
+
+ assert (an >= bn);
+
+ cy = mpn_sub_n (rp, ap, bp, bn);
+ if (an > bn)
+ cy = mpn_sub_1 (rp + bn, ap + bn, an - bn, cy);
+ return cy;
+}
+
+mp_limb_t
+mpn_mul_1 (mp_ptr rp, mp_srcptr up, mp_size_t n, mp_limb_t vl)
+{
+ mp_limb_t ul, cl, hpl, lpl;
+
+ assert (n >= 1);
+
+ cl = 0;
+ do
+ {
+ ul = *up++;
+ gmp_umul_ppmm (hpl, lpl, ul, vl);
+
+ lpl += cl;
+ cl = (lpl < cl) + hpl;
+
+ *rp++ = lpl;
+ }
+ while (--n != 0);
+
+ return cl;
+}
+
+mp_limb_t
+mpn_addmul_1 (mp_ptr rp, mp_srcptr up, mp_size_t n, mp_limb_t vl)
+{
+ mp_limb_t ul, cl, hpl, lpl, rl;
+
+ assert (n >= 1);
+
+ cl = 0;
+ do
+ {
+ ul = *up++;
+ gmp_umul_ppmm (hpl, lpl, ul, vl);
+
+ lpl += cl;
+ cl = (lpl < cl) + hpl;
+
+ rl = *rp;
+ lpl = rl + lpl;
+ cl += lpl < rl;
+ *rp++ = lpl;
+ }
+ while (--n != 0);
+
+ return cl;
+}
+
+mp_limb_t
+mpn_submul_1 (mp_ptr rp, mp_srcptr up, mp_size_t n, mp_limb_t vl)
+{
+ mp_limb_t ul, cl, hpl, lpl, rl;
+
+ assert (n >= 1);
+
+ cl = 0;
+ do
+ {
+ ul = *up++;
+ gmp_umul_ppmm (hpl, lpl, ul, vl);
+
+ lpl += cl;
+ cl = (lpl < cl) + hpl;
+
+ rl = *rp;
+ lpl = rl - lpl;
+ cl += lpl > rl;
+ *rp++ = lpl;
+ }
+ while (--n != 0);
+
+ return cl;
+}
+
+mp_limb_t
+mpn_mul (mp_ptr rp, mp_srcptr up, mp_size_t un, mp_srcptr vp, mp_size_t vn)
+{
+ assert (un >= vn);
+ assert (vn >= 1);
+
+ /* We first multiply by the low order limb. This result can be
+ stored, not added, to rp. We also avoid a loop for zeroing this
+ way. */
+
+ rp[un] = mpn_mul_1 (rp, up, un, vp[0]);
+ rp += 1, vp += 1, vn -= 1;
+
+ /* Now accumulate the product of up[] and the next higher limb from
+ vp[]. */
+
+ while (vn >= 1)
+ {
+ rp[un] = mpn_addmul_1 (rp, up, un, vp[0]);
+ rp += 1, vp += 1, vn -= 1;
+ }
+ return rp[un - 1];
+}
+
+void
+mpn_mul_n (mp_ptr rp, mp_srcptr ap, mp_srcptr bp, mp_size_t n)
+{
+ mpn_mul (rp, ap, n, bp, n);
+}
+
+void
+mpn_sqr (mp_ptr rp, mp_srcptr ap, mp_size_t n)
+{
+ mpn_mul (rp, ap, n, ap, n);
+}
+
+mp_limb_t
+mpn_lshift (mp_ptr rp, mp_srcptr up, mp_size_t n, unsigned int cnt)
+{
+ mp_limb_t high_limb, low_limb;
+ unsigned int tnc;
+ mp_size_t i;
+ mp_limb_t retval;
+
+ assert (n >= 1);
+ assert (cnt >= 1);
+ assert (cnt < GMP_LIMB_BITS);
+
+ up += n;
+ rp += n;
+
+ tnc = GMP_LIMB_BITS - cnt;
+ low_limb = *--up;
+ retval = low_limb >> tnc;
+ high_limb = (low_limb << cnt);
+
+ for (i = n - 1; i != 0; i--)
+ {
+ low_limb = *--up;
+ *--rp = high_limb | (low_limb >> tnc);
+ high_limb = (low_limb << cnt);
+ }
+ *--rp = high_limb;
+
+ return retval;
+}
+
+mp_limb_t
+mpn_rshift (mp_ptr rp, mp_srcptr up, mp_size_t n, unsigned int cnt)
+{
+ mp_limb_t high_limb, low_limb;
+ unsigned int tnc;
+ mp_size_t i;
+ mp_limb_t retval;
+
+ assert (n >= 1);
+ assert (cnt >= 1);
+ assert (cnt < GMP_LIMB_BITS);
+
+ tnc = GMP_LIMB_BITS - cnt;
+ high_limb = *up++;
+ retval = (high_limb << tnc);
+ low_limb = high_limb >> cnt;
+
+ for (i = n - 1; i != 0; i--)
+ {
+ high_limb = *up++;
+ *rp++ = low_limb | (high_limb << tnc);
+ low_limb = high_limb >> cnt;
+ }
+ *rp = low_limb;
+
+ return retval;
+}
+
+
+/* MPN division interface. */
+mp_limb_t
+mpn_invert_3by2 (mp_limb_t u1, mp_limb_t u0)
+{
+ mp_limb_t r, p, m;
+ unsigned ul, uh;
+ unsigned ql, qh;
+
+ /* First, do a 2/1 inverse. */
+ /* The inverse m is defined as floor( (B^2 - 1 - u1)/u1 ), so that 0 <
+ * B^2 - (B + m) u1 <= u1 */
+ assert (u1 >= GMP_LIMB_HIGHBIT);
+
+ ul = u1 & GMP_LLIMB_MASK;
+ uh = u1 >> (GMP_LIMB_BITS / 2);
+
+ qh = ~u1 / uh;
+ r = ((~u1 - (mp_limb_t) qh * uh) << (GMP_LIMB_BITS / 2)) | GMP_LLIMB_MASK;
+
+ p = (mp_limb_t) qh * ul;
+ /* Adjustment steps taken from udiv_qrnnd_c */
+ if (r < p)
+ {
+ qh--;
+ r += u1;
+ if (r >= u1) /* i.e. we didn't get carry when adding to r */
+ if (r < p)
+ {
+ qh--;
+ r += u1;
+ }
+ }
+ r -= p;
+
+ /* Do a 3/2 division (with half limb size) */
+ p = (r >> (GMP_LIMB_BITS / 2)) * qh + r;
+ ql = (p >> (GMP_LIMB_BITS / 2)) + 1;
+
+ /* By the 3/2 method, we don't need the high half limb. */
+ r = (r << (GMP_LIMB_BITS / 2)) + GMP_LLIMB_MASK - ql * u1;
+
+ if (r >= (p << (GMP_LIMB_BITS / 2)))
+ {
+ ql--;
+ r += u1;
+ }
+ m = ((mp_limb_t) qh << (GMP_LIMB_BITS / 2)) + ql;
+ if (r >= u1)
+ {
+ m++;
+ r -= u1;
+ }
+
+ if (u0 > 0)
+ {
+ mp_limb_t th, tl;
+ r = ~r;
+ r += u0;
+ if (r < u0)
+ {
+ m--;
+ if (r >= u1)
+ {
+ m--;
+ r -= u1;
+ }
+ r -= u1;
+ }
+ gmp_umul_ppmm (th, tl, u0, m);
+ r += th;
+ if (r < th)
+ {
+ m--;
+ if (r > u1 || (r == u1 && tl > u0))
+ m--;
+ }
+ }
+
+ return m;
+}
+
+struct gmp_div_inverse
+{
+ /* Normalization shift count. */
+ unsigned shift;
+ /* Normalized divisor (d0 unused for mpn_div_qr_1) */
+ mp_limb_t d1, d0;
+ /* Inverse, for 2/1 or 3/2. */
+ mp_limb_t di;
+};
+
+static void
+mpn_div_qr_1_invert (struct gmp_div_inverse *inv, mp_limb_t d)
+{
+ unsigned shift;
+
+ assert (d > 0);
+ gmp_clz (shift, d);
+ inv->shift = shift;
+ inv->d1 = d << shift;
+ inv->di = mpn_invert_limb (inv->d1);
+}
+
+static void
+mpn_div_qr_2_invert (struct gmp_div_inverse *inv,
+ mp_limb_t d1, mp_limb_t d0)
+{
+ unsigned shift;
+
+ assert (d1 > 0);
+ gmp_clz (shift, d1);
+ inv->shift = shift;
+ if (shift > 0)
+ {
+ d1 = (d1 << shift) | (d0 >> (GMP_LIMB_BITS - shift));
+ d0 <<= shift;
+ }
+ inv->d1 = d1;
+ inv->d0 = d0;
+ inv->di = mpn_invert_3by2 (d1, d0);
+}
+
+static void
+mpn_div_qr_invert (struct gmp_div_inverse *inv,
+ mp_srcptr dp, mp_size_t dn)
+{
+ assert (dn > 0);
+
+ if (dn == 1)
+ mpn_div_qr_1_invert (inv, dp[0]);
+ else if (dn == 2)
+ mpn_div_qr_2_invert (inv, dp[1], dp[0]);
+ else
+ {
+ unsigned shift;
+ mp_limb_t d1, d0;
+
+ d1 = dp[dn-1];
+ d0 = dp[dn-2];
+ assert (d1 > 0);
+ gmp_clz (shift, d1);
+ inv->shift = shift;
+ if (shift > 0)
+ {
+ d1 = (d1 << shift) | (d0 >> (GMP_LIMB_BITS - shift));
+ d0 = (d0 << shift) | (dp[dn-3] >> (GMP_LIMB_BITS - shift));
+ }
+ inv->d1 = d1;
+ inv->d0 = d0;
+ inv->di = mpn_invert_3by2 (d1, d0);
+ }
+}
+
+/* Not matching current public gmp interface, rather corresponding to
+ the sbpi1_div_* functions. */
+static mp_limb_t
+mpn_div_qr_1_preinv (mp_ptr qp, mp_srcptr np, mp_size_t nn,
+ const struct gmp_div_inverse *inv)
+{
+ mp_limb_t d, di;
+ mp_limb_t r;
+ mp_ptr tp = NULL;
+
+ if (inv->shift > 0)
+ {
+ tp = gmp_xalloc_limbs (nn);
+ r = mpn_lshift (tp, np, nn, inv->shift);
+ np = tp;
+ }
+ else
+ r = 0;
+
+ d = inv->d1;
+ di = inv->di;
+ while (nn-- > 0)
+ {
+ mp_limb_t q;
+
+ gmp_udiv_qrnnd_preinv (q, r, r, np[nn], d, di);
+ if (qp)
+ qp[nn] = q;
+ }
+ if (inv->shift > 0)
+ gmp_free (tp);
+
+ return r >> inv->shift;
+}
+
+static mp_limb_t
+mpn_div_qr_1 (mp_ptr qp, mp_srcptr np, mp_size_t nn, mp_limb_t d)
+{
+ assert (d > 0);
+
+ /* Special case for powers of two. */
+ if (d > 1 && (d & (d-1)) == 0)
+ {
+ unsigned shift;
+ mp_limb_t r = np[0] & (d-1);
+ gmp_ctz (shift, d);
+ if (qp)
+ mpn_rshift (qp, np, nn, shift);
+
+ return r;
+ }
+ else
+ {
+ struct gmp_div_inverse inv;
+ mpn_div_qr_1_invert (&inv, d);
+ return mpn_div_qr_1_preinv (qp, np, nn, &inv);
+ }
+}
+
+static void
+mpn_div_qr_2_preinv (mp_ptr qp, mp_ptr rp, mp_srcptr np, mp_size_t nn,
+ const struct gmp_div_inverse *inv)
+{
+ unsigned shift;
+ mp_size_t i;
+ mp_limb_t d1, d0, di, r1, r0;
+ mp_ptr tp;
+
+ assert (nn >= 2);
+ shift = inv->shift;
+ d1 = inv->d1;
+ d0 = inv->d0;
+ di = inv->di;
+
+ if (shift > 0)
+ {
+ tp = gmp_xalloc_limbs (nn);
+ r1 = mpn_lshift (tp, np, nn, shift);
+ np = tp;
+ }
+ else
+ r1 = 0;
+
+ r0 = np[nn - 1];
+
+ for (i = nn - 2; i >= 0; i--)
+ {
+ mp_limb_t n0, q;
+ n0 = np[i];
+ gmp_udiv_qr_3by2 (q, r1, r0, r1, r0, n0, d1, d0, di);
+
+ if (qp)
+ qp[i] = q;
+ }
+
+ if (shift > 0)
+ {
+ assert ((r0 << (GMP_LIMB_BITS - shift)) == 0);
+ r0 = (r0 >> shift) | (r1 << (GMP_LIMB_BITS - shift));
+ r1 >>= shift;
+
+ gmp_free (tp);
+ }
+
+ rp[1] = r1;
+ rp[0] = r0;
+}
+
+#if 0
+static void
+mpn_div_qr_2 (mp_ptr qp, mp_ptr rp, mp_srcptr np, mp_size_t nn,
+ mp_limb_t d1, mp_limb_t d0)
+{
+ struct gmp_div_inverse inv;
+ assert (nn >= 2);
+
+ mpn_div_qr_2_invert (&inv, d1, d0);
+ mpn_div_qr_2_preinv (qp, rp, np, nn, &inv);
+}
+#endif
+
+static void
+mpn_div_qr_pi1 (mp_ptr qp,
+ mp_ptr np, mp_size_t nn, mp_limb_t n1,
+ mp_srcptr dp, mp_size_t dn,
+ mp_limb_t dinv)
+{
+ mp_size_t i;
+
+ mp_limb_t d1, d0;
+ mp_limb_t cy, cy1;
+ mp_limb_t q;
+
+ assert (dn > 2);
+ assert (nn >= dn);
+
+ d1 = dp[dn - 1];
+ d0 = dp[dn - 2];
+
+ assert ((d1 & GMP_LIMB_HIGHBIT) != 0);
+ /* Iteration variable is the index of the q limb.
+ *
+ * We divide <n1, np[dn-1+i], np[dn-2+i], np[dn-3+i],..., np[i]>
+ * by <d1, d0, dp[dn-3], ..., dp[0] >
+ */
+
+ for (i = nn - dn; i >= 0; i--)
+ {
+ mp_limb_t n0 = np[dn-1+i];
+
+ if (n1 == d1 && n0 == d0)
+ {
+ q = GMP_LIMB_MAX;
+ mpn_submul_1 (np+i, dp, dn, q);
+ n1 = np[dn-1+i]; /* update n1, last loop's value will now be invalid */
+ }
+ else
+ {
+ gmp_udiv_qr_3by2 (q, n1, n0, n1, n0, np[dn-2+i], d1, d0, dinv);
+
+ cy = mpn_submul_1 (np + i, dp, dn-2, q);
+
+ cy1 = n0 < cy;
+ n0 = n0 - cy;
+ cy = n1 < cy1;
+ n1 = n1 - cy1;
+ np[dn-2+i] = n0;
+
+ if (cy != 0)
+ {
+ n1 += d1 + mpn_add_n (np + i, np + i, dp, dn - 1);
+ q--;
+ }
+ }
+
+ if (qp)
+ qp[i] = q;
+ }
+
+ np[dn - 1] = n1;
+}
+
+static void
+mpn_div_qr_preinv (mp_ptr qp, mp_ptr np, mp_size_t nn,
+ mp_srcptr dp, mp_size_t dn,
+ const struct gmp_div_inverse *inv)
+{
+ assert (dn > 0);
+ assert (nn >= dn);
+
+ if (dn == 1)
+ np[0] = mpn_div_qr_1_preinv (qp, np, nn, inv);
+ else if (dn == 2)
+ mpn_div_qr_2_preinv (qp, np, np, nn, inv);
+ else
+ {
+ mp_limb_t nh;
+ unsigned shift;
+
+ assert (inv->d1 == dp[dn-1]);
+ assert (inv->d0 == dp[dn-2]);
+ assert ((inv->d1 & GMP_LIMB_HIGHBIT) != 0);
+
+ shift = inv->shift;
+ if (shift > 0)
+ nh = mpn_lshift (np, np, nn, shift);
+ else
+ nh = 0;
+
+ mpn_div_qr_pi1 (qp, np, nn, nh, dp, dn, inv->di);
+
+ if (shift > 0)
+ gmp_assert_nocarry (mpn_rshift (np, np, dn, shift));
+ }
+}
+
+static void
+mpn_div_qr (mp_ptr qp, mp_ptr np, mp_size_t nn, mp_srcptr dp, mp_size_t dn)
+{
+ struct gmp_div_inverse inv;
+ mp_ptr tp = NULL;
+
+ assert (dn > 0);
+ assert (nn >= dn);
+
+ mpn_div_qr_invert (&inv, dp, dn);
+ if (dn > 2 && inv.shift > 0)
+ {
+ tp = gmp_xalloc_limbs (dn);
+ gmp_assert_nocarry (mpn_lshift (tp, dp, dn, inv.shift));
+ dp = tp;
+ }
+ mpn_div_qr_preinv (qp, np, nn, dp, dn, &inv);
+ if (tp)
+ gmp_free (tp);
+}
+
+
+/* MPN base conversion. */
+static unsigned
+mpn_base_power_of_two_p (unsigned b)
+{
+ switch (b)
+ {
+ case 2: return 1;
+ case 4: return 2;
+ case 8: return 3;
+ case 16: return 4;
+ case 32: return 5;
+ case 64: return 6;
+ case 128: return 7;
+ case 256: return 8;
+ default: return 0;
+ }
+}
+
+struct mpn_base_info
+{
+ /* bb is the largest power of the base which fits in one limb, and
+ exp is the corresponding exponent. */
+ unsigned exp;
+ mp_limb_t bb;
+};
+
+static void
+mpn_get_base_info (struct mpn_base_info *info, mp_limb_t b)
+{
+ mp_limb_t m;
+ mp_limb_t p;
+ unsigned exp;
+
+ m = GMP_LIMB_MAX / b;
+ for (exp = 1, p = b; p <= m; exp++)
+ p *= b;
+
+ info->exp = exp;
+ info->bb = p;
+}
+
+static mp_bitcnt_t
+mpn_limb_size_in_base_2 (mp_limb_t u)
+{
+ unsigned shift;
+
+ assert (u > 0);
+ gmp_clz (shift, u);
+ return GMP_LIMB_BITS - shift;
+}
+
+static size_t
+mpn_get_str_bits (unsigned char *sp, unsigned bits, mp_srcptr up, mp_size_t un)
+{
+ unsigned char mask;
+ size_t sn, j;
+ mp_size_t i;
+ int shift;
+
+ sn = ((un - 1) * GMP_LIMB_BITS + mpn_limb_size_in_base_2 (up[un-1])
+ + bits - 1) / bits;
+
+ mask = (1U << bits) - 1;
+
+ for (i = 0, j = sn, shift = 0; j-- > 0;)
+ {
+ unsigned char digit = up[i] >> shift;
+
+ shift += bits;
+
+ if (shift >= GMP_LIMB_BITS && ++i < un)
+ {
+ shift -= GMP_LIMB_BITS;
+ digit |= up[i] << (bits - shift);
+ }
+ sp[j] = digit & mask;
+ }
+ return sn;
+}
+
+/* We generate digits from the least significant end, and reverse at
+ the end. */
+static size_t
+mpn_limb_get_str (unsigned char *sp, mp_limb_t w,
+ const struct gmp_div_inverse *binv)
+{
+ mp_size_t i;
+ for (i = 0; w > 0; i++)
+ {
+ mp_limb_t h, l, r;
+
+ h = w >> (GMP_LIMB_BITS - binv->shift);
+ l = w << binv->shift;
+
+ gmp_udiv_qrnnd_preinv (w, r, h, l, binv->d1, binv->di);
+ assert ( (r << (GMP_LIMB_BITS - binv->shift)) == 0);
+ r >>= binv->shift;
+
+ sp[i] = r;
+ }
+ return i;
+}
+
+static size_t
+mpn_get_str_other (unsigned char *sp,
+ int base, const struct mpn_base_info *info,
+ mp_ptr up, mp_size_t un)
+{
+ struct gmp_div_inverse binv;
+ size_t sn;
+ size_t i;
+
+ mpn_div_qr_1_invert (&binv, base);
+
+ sn = 0;
+
+ if (un > 1)
+ {
+ struct gmp_div_inverse bbinv;
+ mpn_div_qr_1_invert (&bbinv, info->bb);
+
+ do
+ {
+ mp_limb_t w;
+ size_t done;
+ w = mpn_div_qr_1_preinv (up, up, un, &bbinv);
+ un -= (up[un-1] == 0);
+ done = mpn_limb_get_str (sp + sn, w, &binv);
+
+ for (sn += done; done < info->exp; done++)
+ sp[sn++] = 0;
+ }
+ while (un > 1);
+ }
+ sn += mpn_limb_get_str (sp + sn, up[0], &binv);
+
+ /* Reverse order */
+ for (i = 0; 2*i + 1 < sn; i++)
+ {
+ unsigned char t = sp[i];
+ sp[i] = sp[sn - i - 1];
+ sp[sn - i - 1] = t;
+ }
+
+ return sn;
+}
+
+size_t
+mpn_get_str (unsigned char *sp, int base, mp_ptr up, mp_size_t un)
+{
+ unsigned bits;
+
+ assert (un > 0);
+ assert (up[un-1] > 0);
+
+ bits = mpn_base_power_of_two_p (base);
+ if (bits)
+ return mpn_get_str_bits (sp, bits, up, un);
+ else
+ {
+ struct mpn_base_info info;
+
+ mpn_get_base_info (&info, base);
+ return mpn_get_str_other (sp, base, &info, up, un);
+ }
+}
+
+static mp_size_t
+mpn_set_str_bits (mp_ptr rp, const unsigned char *sp, size_t sn,
+ unsigned bits)
+{
+ mp_size_t rn;
+ size_t j;
+ unsigned shift;
+
+ for (j = sn, rn = 0, shift = 0; j-- > 0; )
+ {
+ if (shift == 0)
+ {
+ rp[rn++] = sp[j];
+ shift += bits;
+ }
+ else
+ {
+ rp[rn-1] |= (mp_limb_t) sp[j] << shift;
+ shift += bits;
+ if (shift >= GMP_LIMB_BITS)
+ {
+ shift -= GMP_LIMB_BITS;
+ if (shift > 0)
+ rp[rn++] = (mp_limb_t) sp[j] >> (bits - shift);
+ }
+ }
+ }
+ rn = mpn_normalized_size (rp, rn);
+ return rn;
+}
+
+static mp_size_t
+mpn_set_str_other (mp_ptr rp, const unsigned char *sp, size_t sn,
+ mp_limb_t b, const struct mpn_base_info *info)
+{
+ mp_size_t rn;
+ mp_limb_t w;
+ unsigned first;
+ unsigned k;
+ size_t j;
+
+ first = 1 + (sn - 1) % info->exp;
+
+ j = 0;
+ w = sp[j++];
+ for (k = 1; k < first; k++)
+ w = w * b + sp[j++];
+
+ rp[0] = w;
+
+ for (rn = (w > 0); j < sn;)
+ {
+ mp_limb_t cy;
+
+ w = sp[j++];
+ for (k = 1; k < info->exp; k++)
+ w = w * b + sp[j++];
+
+ cy = mpn_mul_1 (rp, rp, rn, info->bb);
+ cy += mpn_add_1 (rp, rp, rn, w);
+ if (cy > 0)
+ rp[rn++] = cy;
+ }
+ assert (j == sn);
+
+ return rn;
+}
+
+mp_size_t
+mpn_set_str (mp_ptr rp, const unsigned char *sp, size_t sn, int base)
+{
+ unsigned bits;
+
+ if (sn == 0)
+ return 0;
+
+ bits = mpn_base_power_of_two_p (base);
+ if (bits)
+ return mpn_set_str_bits (rp, sp, sn, bits);
+ else
+ {
+ struct mpn_base_info info;
+
+ mpn_get_base_info (&info, base);
+ return mpn_set_str_other (rp, sp, sn, base, &info);
+ }
+}
+
+
+/* MPZ interface */
+void
+mpz_init (mpz_t r)
+{
+ r->_mp_alloc = 1;
+ r->_mp_size = 0;
+ r->_mp_d = gmp_xalloc_limbs (1);
+}
+
+/* The utility of this function is a bit limited, since many functions
+ assings the result variable using mpz_swap. */
+void
+mpz_init2 (mpz_t r, mp_bitcnt_t bits)
+{
+ mp_size_t rn;
+
+ bits -= (bits != 0); /* Round down, except if 0 */
+ rn = 1 + bits / GMP_LIMB_BITS;
+
+ r->_mp_alloc = rn;
+ r->_mp_size = 0;
+ r->_mp_d = gmp_xalloc_limbs (rn);
+}
+
+void
+mpz_clear (mpz_t r)
+{
+ gmp_free (r->_mp_d);
+}
+
+static void *
+mpz_realloc (mpz_t r, mp_size_t size)
+{
+ size = GMP_MAX (size, 1);
+
+ r->_mp_d = gmp_xrealloc_limbs (r->_mp_d, size);
+ r->_mp_alloc = size;
+
+ if (GMP_ABS (r->_mp_size) > size)
+ r->_mp_size = 0;
+
+ return r->_mp_d;
+}
+
+/* Realloc for an mpz_t WHAT if it has less than NEEDED limbs. */
+#define MPZ_REALLOC(z,n) ((n) > (z)->_mp_alloc \
+ ? mpz_realloc(z,n) \
+ : (z)->_mp_d)
+
+/* MPZ assignment and basic conversions. */
+void
+mpz_set_si (mpz_t r, signed long int x)
+{
+ if (x >= 0)
+ mpz_set_ui (r, x);
+ else /* (x < 0) */
+ {
+ r->_mp_size = -1;
+ r->_mp_d[0] = GMP_NEG_CAST (unsigned long int, x);
+ }
+}
+
+void
+mpz_set_ui (mpz_t r, unsigned long int x)
+{
+ if (x > 0)
+ {
+ r->_mp_size = 1;
+ r->_mp_d[0] = x;
+ }
+ else
+ r->_mp_size = 0;
+}
+
+void
+mpz_set (mpz_t r, const mpz_t x)
+{
+ /* Allow the NOP r == x */
+ if (r != x)
+ {
+ mp_size_t n;
+ mp_ptr rp;
+
+ n = GMP_ABS (x->_mp_size);
+ rp = MPZ_REALLOC (r, n);
+
+ mpn_copyi (rp, x->_mp_d, n);
+ r->_mp_size = x->_mp_size;
+ }
+}
+
+void
+mpz_init_set_si (mpz_t r, signed long int x)
+{
+ mpz_init (r);
+ mpz_set_si (r, x);
+}
+
+void
+mpz_init_set_ui (mpz_t r, unsigned long int x)
+{
+ mpz_init (r);
+ mpz_set_ui (r, x);
+}
+
+void
+mpz_init_set (mpz_t r, const mpz_t x)
+{
+ mpz_init (r);
+ mpz_set (r, x);
+}
+
+int
+mpz_fits_slong_p (const mpz_t u)
+{
+ mp_size_t us = u->_mp_size;
+
+ if (us == 0)
+ return 1;
+ else if (us == 1)
+ return u->_mp_d[0] < GMP_LIMB_HIGHBIT;
+ else if (us == -1)
+ return u->_mp_d[0] <= GMP_LIMB_HIGHBIT;
+ else
+ return 0;
+}
+
+int
+mpz_fits_ulong_p (const mpz_t u)
+{
+ mp_size_t us = u->_mp_size;
+
+ return us == 0 || us == 1;
+}
+
+long int
+mpz_get_si (const mpz_t u)
+{
+ mp_size_t us = u->_mp_size;
+
+ if (us > 0)
+ return (long) (u->_mp_d[0] & ~GMP_LIMB_HIGHBIT);
+ else if (us < 0)
+ return (long) (- u->_mp_d[0] | GMP_LIMB_HIGHBIT);
+ else
+ return 0;
+}
+
+unsigned long int
+mpz_get_ui (const mpz_t u)
+{
+ return u->_mp_size == 0 ? 0 : u->_mp_d[0];
+}
+
+size_t
+mpz_size (const mpz_t u)
+{
+ return GMP_ABS (u->_mp_size);
+}
+
+mp_limb_t
+mpz_getlimbn (const mpz_t u, mp_size_t n)
+{
+ if (n >= 0 && n < GMP_ABS (u->_mp_size))
+ return u->_mp_d[n];
+ else
+ return 0;
+}
+
+
+/* Conversions and comparison to double. */
+void
+mpz_set_d (mpz_t r, double x)
+{
+ int sign;
+ mp_ptr rp;
+ mp_size_t rn, i;
+ double B;
+ double Bi;
+ mp_limb_t f;
+
+ /* x != x is true when x is a NaN, and x == x * 0.5 is true when x is
+ zero or infinity. */
+ if (x == 0.0 || x != x || x == x * 0.5)
+ {
+ r->_mp_size = 0;
+ return;
+ }
+
+ if (x < 0.0)
+ {
+ x = - x;
+ sign = 1;
+ }
+ else
+ sign = 0;
+
+ if (x < 1.0)
+ {
+ r->_mp_size = 0;
+ return;
+ }
+ B = 2.0 * (double) GMP_LIMB_HIGHBIT;
+ Bi = 1.0 / B;
+ for (rn = 1; x >= B; rn++)
+ x *= Bi;
+
+ rp = MPZ_REALLOC (r, rn);
+
+ f = (mp_limb_t) x;
+ x -= f;
+ assert (x < 1.0);
+ rp[rn-1] = f;
+ for (i = rn-1; i-- > 0; )
+ {
+ x = B * x;
+ f = (mp_limb_t) x;
+ x -= f;
+ assert (x < 1.0);
+ rp[i] = f;
+ }
+
+ r->_mp_size = sign ? - rn : rn;
+}
+
+void
+mpz_init_set_d (mpz_t r, double x)
+{
+ mpz_init (r);
+ mpz_set_d (r, x);
+}
+
+double
+mpz_get_d (const mpz_t u)
+{
+ mp_size_t un;
+ double x;
+ double B = 2.0 * (double) GMP_LIMB_HIGHBIT;
+
+ un = GMP_ABS (u->_mp_size);
+
+ if (un == 0)
+ return 0.0;
+
+ x = u->_mp_d[--un];
+ while (un > 0)
+ x = B*x + u->_mp_d[--un];
+
+ if (u->_mp_size < 0)
+ x = -x;
+
+ return x;
+}
+
+int
+mpz_cmpabs_d (const mpz_t x, double d)
+{
+ mp_size_t xn;
+ double B, Bi;
+ mp_size_t i;
+
+ xn = x->_mp_size;
+ d = GMP_ABS (d);
+
+ if (xn != 0)
+ {
+ xn = GMP_ABS (xn);
+
+ B = 2.0 * (double) GMP_LIMB_HIGHBIT;
+ Bi = 1.0 / B;
+
+ /* Scale d so it can be compared with the top limb. */
+ for (i = 1; i < xn; i++)
+ d *= Bi;
+
+ if (d >= B)
+ return -1;
+
+ /* Compare floor(d) to top limb, subtract and cancel when equal. */
+ for (i = xn; i-- > 0;)
+ {
+ mp_limb_t f, xl;
+
+ f = (mp_limb_t) d;
+ xl = x->_mp_d[i];
+ if (xl > f)
+ return 1;
+ else if (xl < f)
+ return -1;
+ d = B * (d - f);
+ }
+ }
+ return - (d > 0.0);
+}
+
+int
+mpz_cmp_d (const mpz_t x, double d)
+{
+ if (x->_mp_size < 0)
+ {
+ if (d >= 0.0)
+ return -1;
+ else
+ return -mpz_cmpabs_d (x, d);
+ }
+ else
+ {
+ if (d < 0.0)
+ return 1;
+ else
+ return mpz_cmpabs_d (x, d);
+ }
+}
+
+
+/* MPZ comparisons and the like. */
+int
+mpz_sgn (const mpz_t u)
+{
+ mp_size_t usize = u->_mp_size;
+
+ if (usize > 0)
+ return 1;
+ else if (usize < 0)
+ return -1;
+ else
+ return 0;
+}
+
+int
+mpz_cmp_si (const mpz_t u, long v)
+{
+ mp_size_t usize = u->_mp_size;
+
+ if (usize < -1)
+ return -1;
+ else if (v >= 0)
+ return mpz_cmp_ui (u, v);
+ else if (usize >= 0)
+ return 1;
+ else /* usize == -1 */
+ {
+ mp_limb_t ul = u->_mp_d[0];
+ if ((mp_limb_t)GMP_NEG_CAST (unsigned long int, v) < ul)
+ return -1;
+ else if ( (mp_limb_t)GMP_NEG_CAST (unsigned long int, v) > ul)
+ return 1;
+ }
+ return 0;
+}
+
+int
+mpz_cmp_ui (const mpz_t u, unsigned long v)
+{
+ mp_size_t usize = u->_mp_size;
+
+ if (usize > 1)
+ return 1;
+ else if (usize < 0)
+ return -1;
+ else
+ {
+ mp_limb_t ul = (usize > 0) ? u->_mp_d[0] : 0;
+ if (ul > v)
+ return 1;
+ else if (ul < v)
+ return -1;
+ }
+ return 0;
+}
+
+int
+mpz_cmp (const mpz_t a, const mpz_t b)
+{
+ mp_size_t asize = a->_mp_size;
+ mp_size_t bsize = b->_mp_size;
+
+ if (asize > bsize)
+ return 1;
+ else if (asize < bsize)
+ return -1;
+ else if (asize > 0)
+ return mpn_cmp (a->_mp_d, b->_mp_d, asize);
+ else if (asize < 0)
+ return -mpn_cmp (a->_mp_d, b->_mp_d, -asize);
+ else
+ return 0;
+}
+
+int
+mpz_cmpabs_ui (const mpz_t u, unsigned long v)
+{
+ mp_size_t un = GMP_ABS (u->_mp_size);
+ mp_limb_t ul;
+
+ if (un > 1)
+ return 1;
+
+ ul = (un == 1) ? u->_mp_d[0] : 0;
+
+ if (ul > v)
+ return 1;
+ else if (ul < v)
+ return -1;
+ else
+ return 0;
+}
+
+int
+mpz_cmpabs (const mpz_t u, const mpz_t v)
+{
+ return mpn_cmp4 (u->_mp_d, GMP_ABS (u->_mp_size),
+ v->_mp_d, GMP_ABS (v->_mp_size));
+}
+
+void
+mpz_abs (mpz_t r, const mpz_t u)
+{
+ if (r != u)
+ mpz_set (r, u);
+
+ r->_mp_size = GMP_ABS (r->_mp_size);
+}
+
+void
+mpz_neg (mpz_t r, const mpz_t u)
+{
+ if (r != u)
+ mpz_set (r, u);
+
+ r->_mp_size = -r->_mp_size;
+}
+
+void
+mpz_swap (mpz_t u, mpz_t v)
+{
+ MP_SIZE_T_SWAP (u->_mp_size, v->_mp_size);
+ MP_SIZE_T_SWAP (u->_mp_alloc, v->_mp_alloc);
+ MP_PTR_SWAP (u->_mp_d, v->_mp_d);
+}
+
+
+/* MPZ addition and subtraction */
+
+/* Adds to the absolute value. Returns new size, but doesn't store it. */
+static mp_size_t
+mpz_abs_add_ui (mpz_t r, const mpz_t a, unsigned long b)
+{
+ mp_size_t an;
+ mp_ptr rp;
+ mp_limb_t cy;
+
+ an = GMP_ABS (a->_mp_size);
+ if (an == 0)
+ {
+ r->_mp_d[0] = b;
+ return b > 0;
+ }
+
+ rp = MPZ_REALLOC (r, an + 1);
+
+ cy = mpn_add_1 (rp, a->_mp_d, an, b);
+ rp[an] = cy;
+ an += (cy > 0);
+
+ return an;
+}
+
+/* Subtract from the absolute value. Returns new size, (or -1 on underflow),
+ but doesn't store it. */
+static mp_size_t
+mpz_abs_sub_ui (mpz_t r, const mpz_t a, unsigned long b)
+{
+ mp_size_t an = GMP_ABS (a->_mp_size);
+ mp_ptr rp = MPZ_REALLOC (r, an);
+
+ if (an == 0)
+ {
+ rp[0] = b;
+ return -(b > 0);
+ }
+ else if (an == 1 && a->_mp_d[0] < b)
+ {
+ rp[0] = b - a->_mp_d[0];
+ return -1;
+ }
+ else
+ {
+ gmp_assert_nocarry (mpn_sub_1 (rp, a->_mp_d, an, b));
+ return mpn_normalized_size (rp, an);
+ }
+}
+
+void
+mpz_add_ui (mpz_t r, const mpz_t a, unsigned long b)
+{
+ if (a->_mp_size >= 0)
+ r->_mp_size = mpz_abs_add_ui (r, a, b);
+ else
+ r->_mp_size = -mpz_abs_sub_ui (r, a, b);
+}
+
+void
+mpz_sub_ui (mpz_t r, const mpz_t a, unsigned long b)
+{
+ if (a->_mp_size < 0)
+ r->_mp_size = -mpz_abs_add_ui (r, a, b);
+ else
+ r->_mp_size = mpz_abs_sub_ui (r, a, b);
+}
+
+void
+mpz_ui_sub (mpz_t r, unsigned long a, const mpz_t b)
+{
+ if (b->_mp_size < 0)
+ r->_mp_size = mpz_abs_add_ui (r, b, a);
+ else
+ r->_mp_size = -mpz_abs_sub_ui (r, b, a);
+}
+
+static mp_size_t
+mpz_abs_add (mpz_t r, const mpz_t a, const mpz_t b)
+{
+ mp_size_t an = GMP_ABS (a->_mp_size);
+ mp_size_t bn = GMP_ABS (b->_mp_size);
+ mp_size_t rn;
+ mp_ptr rp;
+ mp_limb_t cy;
+
+ rn = GMP_MAX (an, bn);
+ rp = MPZ_REALLOC (r, rn + 1);
+ if (an >= bn)
+ cy = mpn_add (rp, a->_mp_d, an, b->_mp_d, bn);
+ else
+ cy = mpn_add (rp, b->_mp_d, bn, a->_mp_d, an);
+
+ rp[rn] = cy;
+
+ return rn + (cy > 0);
+}
+
+static mp_size_t
+mpz_abs_sub (mpz_t r, const mpz_t a, const mpz_t b)
+{
+ mp_size_t an = GMP_ABS (a->_mp_size);
+ mp_size_t bn = GMP_ABS (b->_mp_size);
+ int cmp;
+ mp_ptr rp;
+
+ cmp = mpn_cmp4 (a->_mp_d, an, b->_mp_d, bn);
+ if (cmp > 0)
+ {
+ rp = MPZ_REALLOC (r, an);
+ gmp_assert_nocarry (mpn_sub (rp, a->_mp_d, an, b->_mp_d, bn));
+ return mpn_normalized_size (rp, an);
+ }
+ else if (cmp < 0)
+ {
+ rp = MPZ_REALLOC (r, bn);
+ gmp_assert_nocarry (mpn_sub (rp, b->_mp_d, bn, a->_mp_d, an));
+ return -mpn_normalized_size (rp, bn);
+ }
+ else
+ return 0;
+}
+
+void
+mpz_add (mpz_t r, const mpz_t a, const mpz_t b)
+{
+ mp_size_t rn;
+
+ if ( (a->_mp_size ^ b->_mp_size) >= 0)
+ rn = mpz_abs_add (r, a, b);
+ else
+ rn = mpz_abs_sub (r, a, b);
+
+ r->_mp_size = a->_mp_size >= 0 ? rn : - rn;
+}
+
+void
+mpz_sub (mpz_t r, const mpz_t a, const mpz_t b)
+{
+ mp_size_t rn;
+
+ if ( (a->_mp_size ^ b->_mp_size) >= 0)
+ rn = mpz_abs_sub (r, a, b);
+ else
+ rn = mpz_abs_add (r, a, b);
+
+ r->_mp_size = a->_mp_size >= 0 ? rn : - rn;
+}
+
+
+/* MPZ multiplication */
+void
+mpz_mul_si (mpz_t r, const mpz_t u, long int v)
+{
+ if (v < 0)
+ {
+ mpz_mul_ui (r, u, GMP_NEG_CAST (unsigned long int, v));
+ mpz_neg (r, r);
+ }
+ else
+ mpz_mul_ui (r, u, (unsigned long int) v);
+}
+
+void
+mpz_mul_ui (mpz_t r, const mpz_t u, unsigned long int v)
+{
+ mp_size_t un;
+ mpz_t t;
+ mp_ptr tp;
+ mp_limb_t cy;
+
+ un = GMP_ABS (u->_mp_size);
+
+ if (un == 0 || v == 0)
+ {
+ r->_mp_size = 0;
+ return;
+ }
+
+ mpz_init2 (t, (un + 1) * GMP_LIMB_BITS);
+
+ tp = t->_mp_d;
+ cy = mpn_mul_1 (tp, u->_mp_d, un, v);
+ tp[un] = cy;
+
+ t->_mp_size = un + (cy > 0);
+ if (u->_mp_size < 0)
+ t->_mp_size = - t->_mp_size;
+
+ mpz_swap (r, t);
+ mpz_clear (t);
+}
+
+void
+mpz_mul (mpz_t r, const mpz_t u, const mpz_t v)
+{
+ int sign;
+ mp_size_t un, vn, rn;
+ mpz_t t;
+ mp_ptr tp;
+
+ un = GMP_ABS (u->_mp_size);
+ vn = GMP_ABS (v->_mp_size);
+
+ if (un == 0 || vn == 0)
+ {
+ r->_mp_size = 0;
+ return;
+ }
+
+ sign = (u->_mp_size ^ v->_mp_size) < 0;
+
+ mpz_init2 (t, (un + vn) * GMP_LIMB_BITS);
+
+ tp = t->_mp_d;
+ if (un >= vn)
+ mpn_mul (tp, u->_mp_d, un, v->_mp_d, vn);
+ else
+ mpn_mul (tp, v->_mp_d, vn, u->_mp_d, un);
+
+ rn = un + vn;
+ rn -= tp[rn-1] == 0;
+
+ t->_mp_size = sign ? - rn : rn;
+ mpz_swap (r, t);
+ mpz_clear (t);
+}
+
+void
+mpz_mul_2exp (mpz_t r, const mpz_t u, mp_bitcnt_t bits)
+{
+ mp_size_t un, rn;
+ mp_size_t limbs;
+ unsigned shift;
+ mp_ptr rp;
+
+ un = GMP_ABS (u->_mp_size);
+ if (un == 0)
+ {
+ r->_mp_size = 0;
+ return;
+ }
+
+ limbs = bits / GMP_LIMB_BITS;
+ shift = bits % GMP_LIMB_BITS;
+
+ rn = un + limbs + (shift > 0);
+ rp = MPZ_REALLOC (r, rn);
+ if (shift > 0)
+ {
+ mp_limb_t cy = mpn_lshift (rp + limbs, u->_mp_d, un, shift);
+ rp[rn-1] = cy;
+ rn -= (cy == 0);
+ }
+ else
+ mpn_copyd (rp + limbs, u->_mp_d, un);
+
+ while (limbs > 0)
+ rp[--limbs] = 0;
+
+ r->_mp_size = (u->_mp_size < 0) ? - rn : rn;
+}
+
+
+/* MPZ division */
+enum mpz_div_round_mode { GMP_DIV_FLOOR, GMP_DIV_CEIL, GMP_DIV_TRUNC };
+
+/* Allows q or r to be zero. Returns 1 iff remainder is non-zero. */
+static int
+mpz_div_qr (mpz_t q, mpz_t r,
+ const mpz_t n, const mpz_t d, enum mpz_div_round_mode mode)
+{
+ mp_size_t ns, ds, nn, dn, qs;
+ ns = n->_mp_size;
+ ds = d->_mp_size;
+
+ if (ds == 0)
+ gmp_die("mpz_div_qr: Divide by zero.");
+
+ if (ns == 0)
+ {
+ if (q)
+ q->_mp_size = 0;
+ if (r)
+ r->_mp_size = 0;
+ return 0;
+ }
+
+ nn = GMP_ABS (ns);
+ dn = GMP_ABS (ds);
+
+ qs = ds ^ ns;
+
+ if (nn < dn)
+ {
+ if (mode == GMP_DIV_CEIL && qs >= 0)
+ {
+ /* q = 1, r = n - d */
+ if (r)
+ mpz_sub (r, n, d);
+ if (q)
+ mpz_set_ui (q, 1);
+ }
+ else if (mode == GMP_DIV_FLOOR && qs < 0)
+ {
+ /* q = -1, r = n + d */
+ if (r)
+ mpz_add (r, n, d);
+ if (q)
+ mpz_set_si (q, -1);
+ }
+ else
+ {
+ /* q = 0, r = d */
+ if (r)
+ mpz_set (r, n);
+ if (q)
+ q->_mp_size = 0;
+ }
+ return 1;
+ }
+ else
+ {
+ mp_ptr np, qp;
+ mp_size_t qn, rn;
+ mpz_t tq, tr;
+
+ mpz_init (tr);
+ mpz_set (tr, n);
+ np = tr->_mp_d;
+
+ qn = nn - dn + 1;
+
+ if (q)
+ {
+ mpz_init2 (tq, qn * GMP_LIMB_BITS);
+ qp = tq->_mp_d;
+ }
+ else
+ qp = NULL;
+
+ mpn_div_qr (qp, np, nn, d->_mp_d, dn);
+
+ if (qp)
+ {
+ qn -= (qp[qn-1] == 0);
+
+ tq->_mp_size = qs < 0 ? -qn : qn;
+ }
+ rn = mpn_normalized_size (np, dn);
+ tr->_mp_size = ns < 0 ? - rn : rn;
+
+ if (mode == GMP_DIV_FLOOR && qs < 0 && rn != 0)
+ {
+ if (q)
+ mpz_sub_ui (tq, tq, 1);
+ if (r)
+ mpz_add (tr, tr, d);
+ }
+ else if (mode == GMP_DIV_CEIL && qs >= 0 && rn != 0)
+ {
+ if (q)
+ mpz_add_ui (tq, tq, 1);
+ if (r)
+ mpz_sub (tr, tr, d);
+ }
+
+ if (q)
+ {
+ mpz_swap (tq, q);
+ mpz_clear (tq);
+ }
+ if (r)
+ mpz_swap (tr, r);
+
+ mpz_clear (tr);
+
+ return rn != 0;
+ }
+}
+
+void
+mpz_cdiv_qr (mpz_t q, mpz_t r, const mpz_t n, const mpz_t d)
+{
+ mpz_div_qr (q, r, n, d, GMP_DIV_CEIL);
+}
+
+void
+mpz_fdiv_qr (mpz_t q, mpz_t r, const mpz_t n, const mpz_t d)
+{
+ mpz_div_qr (q, r, n, d, GMP_DIV_FLOOR);
+}
+
+void
+mpz_tdiv_qr (mpz_t q, mpz_t r, const mpz_t n, const mpz_t d)
+{
+ mpz_div_qr (q, r, n, d, GMP_DIV_TRUNC);
+}
+
+void
+mpz_cdiv_q (mpz_t q, const mpz_t n, const mpz_t d)
+{
+ mpz_div_qr (q, NULL, n, d, GMP_DIV_CEIL);
+}
+
+void
+mpz_fdiv_q (mpz_t q, const mpz_t n, const mpz_t d)
+{
+ mpz_div_qr (q, NULL, n, d, GMP_DIV_FLOOR);
+}
+
+void
+mpz_tdiv_q (mpz_t q, const mpz_t n, const mpz_t d)
+{
+ mpz_div_qr (q, NULL, n, d, GMP_DIV_TRUNC);
+}
+
+void
+mpz_cdiv_r (mpz_t r, const mpz_t n, const mpz_t d)
+{
+ mpz_div_qr (NULL, r, n, d, GMP_DIV_CEIL);
+}
+
+void
+mpz_fdiv_r (mpz_t r, const mpz_t n, const mpz_t d)
+{
+ mpz_div_qr (NULL, r, n, d, GMP_DIV_FLOOR);
+}
+
+void
+mpz_tdiv_r (mpz_t r, const mpz_t n, const mpz_t d)
+{
+ mpz_div_qr (NULL, r, n, d, GMP_DIV_TRUNC);
+}
+
+void
+mpz_mod (mpz_t r, const mpz_t n, const mpz_t d)
+{
+ if (d->_mp_size >= 0)
+ mpz_div_qr (NULL, r, n, d, GMP_DIV_FLOOR);
+ else
+ mpz_div_qr (NULL, r, n, d, GMP_DIV_CEIL);
+}
+
+static void
+mpz_div_q_2exp (mpz_t q, const mpz_t u, mp_bitcnt_t bit_index,
+ enum mpz_div_round_mode mode)
+{
+ mp_size_t un, qn;
+ mp_size_t limb_cnt;
+ mp_ptr qp;
+ mp_limb_t adjust;
+
+ un = u->_mp_size;
+ if (un == 0)
+ {
+ q->_mp_size = 0;
+ return;
+ }
+ limb_cnt = bit_index / GMP_LIMB_BITS;
+ qn = GMP_ABS (un) - limb_cnt;
+ bit_index %= GMP_LIMB_BITS;
+
+ if (mode == ((un > 0) ? GMP_DIV_CEIL : GMP_DIV_FLOOR)) /* un != 0 here. */
+ /* Note: Below, the final indexing at limb_cnt is valid because at
+ that point we have qn > 0. */
+ adjust = (qn <= 0
+ || !mpn_zero_p (u->_mp_d, limb_cnt)
+ || (u->_mp_d[limb_cnt]
+ & (((mp_limb_t) 1 << bit_index) - 1)));
+ else
+ adjust = 0;
+
+ if (qn <= 0)
+ qn = 0;
+
+ else
+ {
+ qp = MPZ_REALLOC (q, qn);
+
+ if (bit_index != 0)
+ {
+ mpn_rshift (qp, u->_mp_d + limb_cnt, qn, bit_index);
+ qn -= qp[qn - 1] == 0;
+ }
+ else
+ {
+ mpn_copyi (qp, u->_mp_d + limb_cnt, qn);
+ }
+ }
+
+ q->_mp_size = qn;
+
+ mpz_add_ui (q, q, adjust);
+ if (un < 0)
+ mpz_neg (q, q);
+}
+
+static void
+mpz_div_r_2exp (mpz_t r, const mpz_t u, mp_bitcnt_t bit_index,
+ enum mpz_div_round_mode mode)
+{
+ mp_size_t us, un, rn;
+ mp_ptr rp;
+ mp_limb_t mask;
+
+ us = u->_mp_size;
+ if (us == 0 || bit_index == 0)
+ {
+ r->_mp_size = 0;
+ return;
+ }
+ rn = (bit_index + GMP_LIMB_BITS - 1) / GMP_LIMB_BITS;
+ assert (rn > 0);
+
+ rp = MPZ_REALLOC (r, rn);
+ un = GMP_ABS (us);
+
+ mask = GMP_LIMB_MAX >> (rn * GMP_LIMB_BITS - bit_index);
+
+ if (rn > un)
+ {
+ /* Quotient (with truncation) is zero, and remainder is
+ non-zero */
+ if (mode == ((us > 0) ? GMP_DIV_CEIL : GMP_DIV_FLOOR)) /* us != 0 here. */
+ {
+ /* Have to negate and sign extend. */
+ mp_size_t i;
+ mp_limb_t cy;
+
+ for (cy = 1, i = 0; i < un; i++)
+ {
+ mp_limb_t s = ~u->_mp_d[i] + cy;
+ cy = s < cy;
+ rp[i] = s;
+ }
+ assert (cy == 0);
+ for (; i < rn - 1; i++)
+ rp[i] = GMP_LIMB_MAX;
+
+ rp[rn-1] = mask;
+ us = -us;
+ }
+ else
+ {
+ /* Just copy */
+ if (r != u)
+ mpn_copyi (rp, u->_mp_d, un);
+
+ rn = un;
+ }
+ }
+ else
+ {
+ if (r != u)
+ mpn_copyi (rp, u->_mp_d, rn - 1);
+
+ rp[rn-1] = u->_mp_d[rn-1] & mask;
+
+ if (mode == ((us > 0) ? GMP_DIV_CEIL : GMP_DIV_FLOOR)) /* us != 0 here. */
+ {
+ /* If r != 0, compute 2^{bit_count} - r. */
+ mp_size_t i;
+
+ for (i = 0; i < rn && rp[i] == 0; i++)
+ ;
+ if (i < rn)
+ {
+ /* r > 0, need to flip sign. */
+ rp[i] = ~rp[i] + 1;
+ for (i++; i < rn; i++)
+ rp[i] = ~rp[i];
+
+ rp[rn-1] &= mask;
+
+ /* us is not used for anything else, so we can modify it
+ here to indicate flipped sign. */
+ us = -us;
+ }
+ }
+ }
+ rn = mpn_normalized_size (rp, rn);
+ r->_mp_size = us < 0 ? -rn : rn;
+}
+
+void
+mpz_cdiv_q_2exp (mpz_t r, const mpz_t u, mp_bitcnt_t cnt)
+{
+ mpz_div_q_2exp (r, u, cnt, GMP_DIV_CEIL);
+}
+
+void
+mpz_fdiv_q_2exp (mpz_t r, const mpz_t u, mp_bitcnt_t cnt)
+{
+ mpz_div_q_2exp (r, u, cnt, GMP_DIV_FLOOR);
+}
+
+void
+mpz_tdiv_q_2exp (mpz_t r, const mpz_t u, mp_bitcnt_t cnt)
+{
+ mpz_div_q_2exp (r, u, cnt, GMP_DIV_TRUNC);
+}
+
+void
+mpz_cdiv_r_2exp (mpz_t r, const mpz_t u, mp_bitcnt_t cnt)
+{
+ mpz_div_r_2exp (r, u, cnt, GMP_DIV_CEIL);
+}
+
+void
+mpz_fdiv_r_2exp (mpz_t r, const mpz_t u, mp_bitcnt_t cnt)
+{
+ mpz_div_r_2exp (r, u, cnt, GMP_DIV_FLOOR);
+}
+
+void
+mpz_tdiv_r_2exp (mpz_t r, const mpz_t u, mp_bitcnt_t cnt)
+{
+ mpz_div_r_2exp (r, u, cnt, GMP_DIV_TRUNC);
+}
+
+void
+mpz_divexact (mpz_t q, const mpz_t n, const mpz_t d)
+{
+ gmp_assert_nocarry (mpz_div_qr (q, NULL, n, d, GMP_DIV_TRUNC));
+}
+
+int
+mpz_divisible_p (const mpz_t n, const mpz_t d)
+{
+ return mpz_div_qr (NULL, NULL, n, d, GMP_DIV_TRUNC) == 0;
+}
+
+static unsigned long
+mpz_div_qr_ui (mpz_t q, mpz_t r,
+ const mpz_t n, unsigned long d, enum mpz_div_round_mode mode)
+{
+ mp_size_t ns, qn;
+ mp_ptr qp;
+ mp_limb_t rl;
+ mp_size_t rs;
+
+ ns = n->_mp_size;
+ if (ns == 0)
+ {
+ if (q)
+ q->_mp_size = 0;
+ if (r)
+ r->_mp_size = 0;
+ return 0;
+ }
+
+ qn = GMP_ABS (ns);
+ if (q)
+ qp = MPZ_REALLOC (q, qn);
+ else
+ qp = NULL;
+
+ rl = mpn_div_qr_1 (qp, n->_mp_d, qn, d);
+ assert (rl < d);
+
+ rs = rl > 0;
+ rs = (ns < 0) ? -rs : rs;
+
+ if (rl > 0 && ( (mode == GMP_DIV_FLOOR && ns < 0)
+ || (mode == GMP_DIV_CEIL && ns >= 0)))
+ {
+ if (q)
+ gmp_assert_nocarry (mpn_add_1 (qp, qp, qn, 1));
+ rl = d - rl;
+ rs = -rs;
+ }
+
+ if (r)
+ {
+ r->_mp_d[0] = rl;
+ r->_mp_size = rs;
+ }
+ if (q)
+ {
+ qn -= (qp[qn-1] == 0);
+ assert (qn == 0 || qp[qn-1] > 0);
+
+ q->_mp_size = (ns < 0) ? - qn : qn;
+ }
+
+ return rl;
+}
+
+unsigned long
+mpz_cdiv_qr_ui (mpz_t q, mpz_t r, const mpz_t n, unsigned long d)
+{
+ return mpz_div_qr_ui (q, r, n, d, GMP_DIV_CEIL);
+}
+
+unsigned long
+mpz_fdiv_qr_ui (mpz_t q, mpz_t r, const mpz_t n, unsigned long d)
+{
+ return mpz_div_qr_ui (q, r, n, d, GMP_DIV_FLOOR);
+}
+
+unsigned long
+mpz_tdiv_qr_ui (mpz_t q, mpz_t r, const mpz_t n, unsigned long d)
+{
+ return mpz_div_qr_ui (q, r, n, d, GMP_DIV_TRUNC);
+}
+
+unsigned long
+mpz_cdiv_q_ui (mpz_t q, const mpz_t n, unsigned long d)
+{
+ return mpz_div_qr_ui (q, NULL, n, d, GMP_DIV_CEIL);
+}
+
+unsigned long
+mpz_fdiv_q_ui (mpz_t q, const mpz_t n, unsigned long d)
+{
+ return mpz_div_qr_ui (q, NULL, n, d, GMP_DIV_FLOOR);
+}
+
+unsigned long
+mpz_tdiv_q_ui (mpz_t q, const mpz_t n, unsigned long d)
+{
+ return mpz_div_qr_ui (q, NULL, n, d, GMP_DIV_TRUNC);
+}
+
+unsigned long
+mpz_cdiv_r_ui (mpz_t r, const mpz_t n, unsigned long d)
+{
+ return mpz_div_qr_ui (NULL, r, n, d, GMP_DIV_CEIL);
+}
+unsigned long
+mpz_fdiv_r_ui (mpz_t r, const mpz_t n, unsigned long d)
+{
+ return mpz_div_qr_ui (NULL, r, n, d, GMP_DIV_FLOOR);
+}
+unsigned long
+mpz_tdiv_r_ui (mpz_t r, const mpz_t n, unsigned long d)
+{
+ return mpz_div_qr_ui (NULL, r, n, d, GMP_DIV_TRUNC);
+}
+
+unsigned long
+mpz_cdiv_ui (const mpz_t n, unsigned long d)
+{
+ return mpz_div_qr_ui (NULL, NULL, n, d, GMP_DIV_CEIL);
+}
+
+unsigned long
+mpz_fdiv_ui (const mpz_t n, unsigned long d)
+{
+ return mpz_div_qr_ui (NULL, NULL, n, d, GMP_DIV_FLOOR);
+}
+
+unsigned long
+mpz_tdiv_ui (const mpz_t n, unsigned long d)
+{
+ return mpz_div_qr_ui (NULL, NULL, n, d, GMP_DIV_TRUNC);
+}
+
+unsigned long
+mpz_mod_ui (mpz_t r, const mpz_t n, unsigned long d)
+{
+ return mpz_div_qr_ui (NULL, r, n, d, GMP_DIV_FLOOR);
+}
+
+void
+mpz_divexact_ui (mpz_t q, const mpz_t n, unsigned long d)
+{
+ gmp_assert_nocarry (mpz_div_qr_ui (q, NULL, n, d, GMP_DIV_TRUNC));
+}
+
+int
+mpz_divisible_ui_p (const mpz_t n, unsigned long d)
+{
+ return mpz_div_qr_ui (NULL, NULL, n, d, GMP_DIV_TRUNC) == 0;
+}
+
+
+/* GCD */
+static mp_limb_t
+mpn_gcd_11 (mp_limb_t u, mp_limb_t v)
+{
+ unsigned shift;
+
+ assert ( (u | v) > 0);
+
+ if (u == 0)
+ return v;
+ else if (v == 0)
+ return u;
+
+ gmp_ctz (shift, u | v);
+
+ u >>= shift;
+ v >>= shift;
+
+ if ( (u & 1) == 0)
+ MP_LIMB_T_SWAP (u, v);
+
+ while ( (v & 1) == 0)
+ v >>= 1;
+
+ while (u != v)
+ {
+ if (u > v)
+ {
+ u -= v;
+ do
+ u >>= 1;
+ while ( (u & 1) == 0);
+ }
+ else
+ {
+ v -= u;
+ do
+ v >>= 1;
+ while ( (v & 1) == 0);
+ }
+ }
+ return u << shift;
+}
+
+unsigned long
+mpz_gcd_ui (mpz_t g, const mpz_t u, unsigned long v)
+{
+ mp_size_t un;
+
+ if (v == 0)
+ {
+ if (g)
+ mpz_abs (g, u);
+ }
+ else
+ {
+ un = GMP_ABS (u->_mp_size);
+ if (un != 0)
+ v = mpn_gcd_11 (mpn_div_qr_1 (NULL, u->_mp_d, un, v), v);
+
+ if (g)
+ mpz_set_ui (g, v);
+ }
+
+ return v;
+}
+
+static mp_bitcnt_t
+mpz_make_odd (mpz_t r, const mpz_t u)
+{
+ mp_size_t un, rn, i;
+ mp_ptr rp;
+ unsigned shift;
+
+ un = GMP_ABS (u->_mp_size);
+ assert (un > 0);
+
+ for (i = 0; u->_mp_d[i] == 0; i++)
+ ;
+
+ gmp_ctz (shift, u->_mp_d[i]);
+
+ rn = un - i;
+ rp = MPZ_REALLOC (r, rn);
+ if (shift > 0)
+ {
+ mpn_rshift (rp, u->_mp_d + i, rn, shift);
+ rn -= (rp[rn-1] == 0);
+ }
+ else
+ mpn_copyi (rp, u->_mp_d + i, rn);
+
+ r->_mp_size = rn;
+ return i * GMP_LIMB_BITS + shift;
+}
+
+void
+mpz_gcd (mpz_t g, const mpz_t u, const mpz_t v)
+{
+ mpz_t tu, tv;
+ mp_bitcnt_t uz, vz, gz;
+
+ if (u->_mp_size == 0)
+ {
+ mpz_abs (g, v);
+ return;
+ }
+ if (v->_mp_size == 0)
+ {
+ mpz_abs (g, u);
+ return;
+ }
+
+ mpz_init (tu);
+ mpz_init (tv);
+
+ uz = mpz_make_odd (tu, u);
+ vz = mpz_make_odd (tv, v);
+ gz = GMP_MIN (uz, vz);
+
+ if (tu->_mp_size < tv->_mp_size)
+ mpz_swap (tu, tv);
+
+ mpz_tdiv_r (tu, tu, tv);
+ if (tu->_mp_size == 0)
+ {
+ mpz_swap (g, tv);
+ }
+ else
+ for (;;)
+ {
+ int c;
+
+ mpz_make_odd (tu, tu);
+ c = mpz_cmp (tu, tv);
+ if (c == 0)
+ {
+ mpz_swap (g, tu);
+ break;
+ }
+ if (c < 0)
+ mpz_swap (tu, tv);
+
+ if (tv->_mp_size == 1)
+ {
+ mp_limb_t vl = tv->_mp_d[0];
+ mp_limb_t ul = mpz_tdiv_ui (tu, vl);
+ mpz_set_ui (g, mpn_gcd_11 (ul, vl));
+ break;
+ }
+ mpz_sub (tu, tu, tv);
+ }
+ mpz_clear (tu);
+ mpz_clear (tv);
+ mpz_mul_2exp (g, g, gz);
+}
+
+void
+mpz_gcdext (mpz_t g, mpz_t s, mpz_t t, const mpz_t u, const mpz_t v)
+{
+ mpz_t tu, tv, s0, s1, t0, t1;
+ mp_bitcnt_t uz, vz, gz;
+ mp_bitcnt_t power;
+
+ if (u->_mp_size == 0)
+ {
+ /* g = 0 u + sgn(v) v */
+ signed long sign = mpz_sgn (v);
+ mpz_abs (g, v);
+ if (s)
+ mpz_set_ui (s, 0);
+ if (t)
+ mpz_set_si (t, sign);
+ return;
+ }
+
+ if (v->_mp_size == 0)
+ {
+ /* g = sgn(u) u + 0 v */
+ signed long sign = mpz_sgn (u);
+ mpz_abs (g, u);
+ if (s)
+ mpz_set_si (s, sign);
+ if (t)
+ mpz_set_ui (t, 0);
+ return;
+ }
+
+ mpz_init (tu);
+ mpz_init (tv);
+ mpz_init (s0);
+ mpz_init (s1);
+ mpz_init (t0);
+ mpz_init (t1);
+
+ uz = mpz_make_odd (tu, u);
+ vz = mpz_make_odd (tv, v);
+ gz = GMP_MIN (uz, vz);
+
+ uz -= gz;
+ vz -= gz;
+
+ /* Cofactors corresponding to odd gcd. gz handled later. */
+ if (tu->_mp_size < tv->_mp_size)
+ {
+ mpz_swap (tu, tv);
+ MPZ_SRCPTR_SWAP (u, v);
+ MPZ_PTR_SWAP (s, t);
+ MP_BITCNT_T_SWAP (uz, vz);
+ }
+
+ /* Maintain
+ *
+ * u = t0 tu + t1 tv
+ * v = s0 tu + s1 tv
+ *
+ * where u and v denote the inputs with common factors of two
+ * eliminated, and det (s0, t0; s1, t1) = 2^p. Then
+ *
+ * 2^p tu = s1 u - t1 v
+ * 2^p tv = -s0 u + t0 v
+ */
+
+ /* After initial division, tu = q tv + tu', we have
+ *
+ * u = 2^uz (tu' + q tv)
+ * v = 2^vz tv
+ *
+ * or
+ *
+ * t0 = 2^uz, t1 = 2^uz q
+ * s0 = 0, s1 = 2^vz
+ */
+
+ mpz_setbit (t0, uz);
+ mpz_tdiv_qr (t1, tu, tu, tv);
+ mpz_mul_2exp (t1, t1, uz);
+
+ mpz_setbit (s1, vz);
+ power = uz + vz;
+
+ if (tu->_mp_size > 0)
+ {
+ mp_bitcnt_t shift;
+ shift = mpz_make_odd (tu, tu);
+ mpz_mul_2exp (t0, t0, shift);
+ mpz_mul_2exp (s0, s0, shift);
+ power += shift;
+
+ for (;;)
+ {
+ int c;
+ c = mpz_cmp (tu, tv);
+ if (c == 0)
+ break;
+
+ if (c < 0)
+ {
+ /* tv = tv' + tu
+ *
+ * u = t0 tu + t1 (tv' + tu) = (t0 + t1) tu + t1 tv'
+ * v = s0 tu + s1 (tv' + tu) = (s0 + s1) tu + s1 tv' */
+
+ mpz_sub (tv, tv, tu);
+ mpz_add (t0, t0, t1);
+ mpz_add (s0, s0, s1);
+
+ shift = mpz_make_odd (tv, tv);
+ mpz_mul_2exp (t1, t1, shift);
+ mpz_mul_2exp (s1, s1, shift);
+ }
+ else
+ {
+ mpz_sub (tu, tu, tv);
+ mpz_add (t1, t0, t1);
+ mpz_add (s1, s0, s1);
+
+ shift = mpz_make_odd (tu, tu);
+ mpz_mul_2exp (t0, t0, shift);
+ mpz_mul_2exp (s0, s0, shift);
+ }
+ power += shift;
+ }
+ }
+
+ /* Now tv = odd part of gcd, and -s0 and t0 are corresponding
+ cofactors. */
+
+ mpz_mul_2exp (tv, tv, gz);
+ mpz_neg (s0, s0);
+
+ /* 2^p g = s0 u + t0 v. Eliminate one factor of two at a time. To
+ adjust cofactors, we need u / g and v / g */
+
+ mpz_divexact (s1, v, tv);
+ mpz_abs (s1, s1);
+ mpz_divexact (t1, u, tv);
+ mpz_abs (t1, t1);
+
+ while (power-- > 0)
+ {
+ /* s0 u + t0 v = (s0 - v/g) u - (t0 + u/g) v */
+ if (mpz_odd_p (s0) || mpz_odd_p (t0))
+ {
+ mpz_sub (s0, s0, s1);
+ mpz_add (t0, t0, t1);
+ }
+ mpz_divexact_ui (s0, s0, 2);
+ mpz_divexact_ui (t0, t0, 2);
+ }
+
+ /* Arrange so that |s| < |u| / 2g */
+ mpz_add (s1, s0, s1);
+ if (mpz_cmpabs (s0, s1) > 0)
+ {
+ mpz_swap (s0, s1);
+ mpz_sub (t0, t0, t1);
+ }
+ if (u->_mp_size < 0)
+ mpz_neg (s0, s0);
+ if (v->_mp_size < 0)
+ mpz_neg (t0, t0);
+
+ mpz_swap (g, tv);
+ if (s)
+ mpz_swap (s, s0);
+ if (t)
+ mpz_swap (t, t0);
+
+ mpz_clear (tu);
+ mpz_clear (tv);
+ mpz_clear (s0);
+ mpz_clear (s1);
+ mpz_clear (t0);
+ mpz_clear (t1);
+}
+
+void
+mpz_lcm (mpz_t r, const mpz_t u, const mpz_t v)
+{
+ mpz_t g;
+
+ if (u->_mp_size == 0 || v->_mp_size == 0)
+ {
+ r->_mp_size = 0;
+ return;
+ }
+
+ mpz_init (g);
+
+ mpz_gcd (g, u, v);
+ mpz_divexact (g, u, g);
+ mpz_mul (r, g, v);
+
+ mpz_clear (g);
+ mpz_abs (r, r);
+}
+
+void
+mpz_lcm_ui (mpz_t r, const mpz_t u, unsigned long v)
+{
+ if (v == 0 || u->_mp_size == 0)
+ {
+ r->_mp_size = 0;
+ return;
+ }
+
+ v /= mpz_gcd_ui (NULL, u, v);
+ mpz_mul_ui (r, u, v);
+
+ mpz_abs (r, r);
+}
+
+int
+mpz_invert (mpz_t r, const mpz_t u, const mpz_t m)
+{
+ mpz_t g, tr;
+ int invertible;
+
+ if (u->_mp_size == 0 || mpz_cmpabs_ui (m, 1) <= 0)
+ return 0;
+
+ mpz_init (g);
+ mpz_init (tr);
+
+ mpz_gcdext (g, tr, NULL, u, m);
+ invertible = (mpz_cmp_ui (g, 1) == 0);
+
+ if (invertible)
+ {
+ if (tr->_mp_size < 0)
+ {
+ if (m->_mp_size >= 0)
+ mpz_add (tr, tr, m);
+ else
+ mpz_sub (tr, tr, m);
+ }
+ mpz_swap (r, tr);
+ }
+
+ mpz_clear (g);
+ mpz_clear (tr);
+ return invertible;
+}
+
+
+/* Higher level operations (sqrt, pow and root) */
+
+void
+mpz_pow_ui (mpz_t r, const mpz_t b, unsigned long e)
+{
+ unsigned long bit;
+ mpz_t tr;
+ mpz_init_set_ui (tr, 1);
+
+ for (bit = GMP_ULONG_HIGHBIT; bit > 0; bit >>= 1)
+ {
+ mpz_mul (tr, tr, tr);
+ if (e & bit)
+ mpz_mul (tr, tr, b);
+ }
+ mpz_swap (r, tr);
+ mpz_clear (tr);
+}
+
+void
+mpz_ui_pow_ui (mpz_t r, unsigned long blimb, unsigned long e)
+{
+ mpz_t b;
+ mpz_init_set_ui (b, blimb);
+ mpz_pow_ui (r, b, e);
+ mpz_clear (b);
+}
+
+void
+mpz_powm (mpz_t r, const mpz_t b, const mpz_t e, const mpz_t m)
+{
+ mpz_t tr;
+ mpz_t base;
+ mp_size_t en, mn;
+ mp_srcptr mp;
+ struct gmp_div_inverse minv;
+ unsigned shift;
+ mp_ptr tp = NULL;
+
+ en = GMP_ABS (e->_mp_size);
+ mn = GMP_ABS (m->_mp_size);
+ if (mn == 0)
+ gmp_die ("mpz_powm: Zero modulo.");
+
+ if (en == 0)
+ {
+ mpz_set_ui (r, 1);
+ return;
+ }
+
+ mp = m->_mp_d;
+ mpn_div_qr_invert (&minv, mp, mn);
+ shift = minv.shift;
+
+ if (shift > 0)
+ {
+ /* To avoid shifts, we do all our reductions, except the final
+ one, using a *normalized* m. */
+ minv.shift = 0;
+
+ tp = gmp_xalloc_limbs (mn);
+ gmp_assert_nocarry (mpn_lshift (tp, mp, mn, shift));
+ mp = tp;
+ }
+
+ mpz_init (base);
+
+ if (e->_mp_size < 0)
+ {
+ if (!mpz_invert (base, b, m))
+ gmp_die ("mpz_powm: Negative exponent and non-invertibe base.");
+ }
+ else
+ {
+ mp_size_t bn;
+ mpz_abs (base, b);
+
+ bn = base->_mp_size;
+ if (bn >= mn)
+ {
+ mpn_div_qr_preinv (NULL, base->_mp_d, base->_mp_size, mp, mn, &minv);
+ bn = mn;
+ }
+
+ /* We have reduced the absolute value. Now take care of the
+ sign. Note that we get zero represented non-canonically as
+ m. */
+ if (b->_mp_size < 0)
+ {
+ mp_ptr bp = MPZ_REALLOC (base, mn);
+ gmp_assert_nocarry (mpn_sub (bp, mp, mn, bp, bn));
+ bn = mn;
+ }
+ base->_mp_size = mpn_normalized_size (base->_mp_d, bn);
+ }
+ mpz_init_set_ui (tr, 1);
+
+ while (en-- > 0)
+ {
+ mp_limb_t w = e->_mp_d[en];
+ mp_limb_t bit;
+
+ for (bit = GMP_LIMB_HIGHBIT; bit > 0; bit >>= 1)
+ {
+ mpz_mul (tr, tr, tr);
+ if (w & bit)
+ mpz_mul (tr, tr, base);
+ if (tr->_mp_size > mn)
+ {
+ mpn_div_qr_preinv (NULL, tr->_mp_d, tr->_mp_size, mp, mn, &minv);
+ tr->_mp_size = mpn_normalized_size (tr->_mp_d, mn);
+ }
+ }
+ }
+
+ /* Final reduction */
+ if (tr->_mp_size >= mn)
+ {
+ minv.shift = shift;
+ mpn_div_qr_preinv (NULL, tr->_mp_d, tr->_mp_size, mp, mn, &minv);
+ tr->_mp_size = mpn_normalized_size (tr->_mp_d, mn);
+ }
+ if (tp)
+ gmp_free (tp);
+
+ mpz_swap (r, tr);
+ mpz_clear (tr);
+ mpz_clear (base);
+}
+
+void
+mpz_powm_ui (mpz_t r, const mpz_t b, unsigned long elimb, const mpz_t m)
+{
+ mpz_t e;
+ mpz_init_set_ui (e, elimb);
+ mpz_powm (r, b, e, m);
+ mpz_clear (e);
+}
+
+/* x=trunc(y^(1/z)), r=y-x^z */
+void
+mpz_rootrem (mpz_t x, mpz_t r, const mpz_t y, unsigned long z)
+{
+ int sgn;
+ mpz_t t, u;
+
+ sgn = y->_mp_size < 0;
+ if (sgn && (z & 1) == 0)
+ gmp_die ("mpz_rootrem: Negative argument, with even root.");
+ if (z == 0)
+ gmp_die ("mpz_rootrem: Zeroth root.");
+
+ if (mpz_cmpabs_ui (y, 1) <= 0) {
+ mpz_set (x, y);
+ if (r)
+ r->_mp_size = 0;
+ return;
+ }
+
+ mpz_init (t);
+ mpz_init (u);
+ mpz_setbit (t, mpz_sizeinbase (y, 2) / z + 1);
+
+ if (z == 2) /* simplify sqrt loop: z-1 == 1 */
+ do {
+ mpz_swap (u, t); /* u = x */
+ mpz_tdiv_q (t, y, u); /* t = y/x */
+ mpz_add (t, t, u); /* t = y/x + x */
+ mpz_tdiv_q_2exp (t, t, 1); /* x'= (y/x + x)/2 */
+ } while (mpz_cmpabs (t, u) < 0); /* |x'| < |x| */
+ else /* z != 2 */ {
+ mpz_t v;
+
+ mpz_init (v);
+ if (sgn)
+ mpz_neg (t, t);
+
+ do {
+ mpz_swap (u, t); /* u = x */
+ mpz_pow_ui (t, u, z - 1); /* t = x^(z-1) */
+ mpz_tdiv_q (t, y, t); /* t = y/x^(z-1) */
+ mpz_mul_ui (v, u, z - 1); /* v = x*(z-1) */
+ mpz_add (t, t, v); /* t = y/x^(z-1) + x*(z-1) */
+ mpz_tdiv_q_ui (t, t, z); /* x'=(y/x^(z-1) + x*(z-1))/z */
+ } while (mpz_cmpabs (t, u) < 0); /* |x'| < |x| */
+
+ mpz_clear (v);
+ }
+
+ if (r) {
+ mpz_pow_ui (t, u, z);
+ mpz_sub (r, y, t);
+ }
+ mpz_swap (x, u);
+ mpz_clear (u);
+ mpz_clear (t);
+}
+
+int
+mpz_root (mpz_t x, const mpz_t y, unsigned long z)
+{
+ int res;
+ mpz_t r;
+
+ mpz_init (r);
+ mpz_rootrem (x, r, y, z);
+ res = r->_mp_size == 0;
+ mpz_clear (r);
+
+ return res;
+}
+
+/* Compute s = floor(sqrt(u)) and r = u - s^2. Allows r == NULL */
+void
+mpz_sqrtrem (mpz_t s, mpz_t r, const mpz_t u)
+{
+ mpz_rootrem (s, r, u, 2);
+}
+
+void
+mpz_sqrt (mpz_t s, const mpz_t u)
+{
+ mpz_rootrem (s, NULL, u, 2);
+}
+
+
+/* Combinatorics */
+
+void
+mpz_fac_ui (mpz_t x, unsigned long n)
+{
+ if (n < 2) {
+ mpz_set_ui (x, 1);
+ return;
+ }
+ mpz_set_ui (x, n);
+ for (;--n > 1;)
+ mpz_mul_ui (x, x, n);
+}
+
+void
+mpz_bin_uiui (mpz_t r, unsigned long n, unsigned long k)
+{
+ mpz_t t;
+
+ if (k > n) {
+ r->_mp_size = 0;
+ return;
+ }
+ mpz_fac_ui (r, n);
+ mpz_init (t);
+ mpz_fac_ui (t, k);
+ mpz_divexact (r, r, t);
+ mpz_fac_ui (t, n - k);
+ mpz_divexact (r, r, t);
+ mpz_clear (t);
+}
+
+
+/* Logical operations and bit manipulation. */
+
+/* Numbers are treated as if represented in two's complement (and
+ infinitely sign extended). For a negative values we get the two's
+ complement from -x = ~x + 1, where ~ is bitwise complementt.
+ Negation transforms
+
+ xxxx10...0
+
+ into
+
+ yyyy10...0
+
+ where yyyy is the bitwise complement of xxxx. So least significant
+ bits, up to and including the first one bit, are unchanged, and
+ the more significant bits are all complemented.
+
+ To change a bit from zero to one in a negative number, subtract the
+ corresponding power of two from the absolute value. This can never
+ underflow. To change a bit from one to zero, add the corresponding
+ power of two, and this might overflow. E.g., if x = -001111, the
+ two's complement is 110001. Clearing the least significant bit, we
+ get two's complement 110000, and -010000. */
+
+int
+mpz_tstbit (const mpz_t d, mp_bitcnt_t bit_index)
+{
+ mp_size_t limb_index;
+ unsigned shift;
+ mp_size_t ds;
+ mp_size_t dn;
+ mp_limb_t w;
+ int bit;
+
+ ds = d->_mp_size;
+ dn = GMP_ABS (ds);
+ limb_index = bit_index / GMP_LIMB_BITS;
+ if (limb_index >= dn)
+ return ds < 0;
+
+ shift = bit_index % GMP_LIMB_BITS;
+ w = d->_mp_d[limb_index];
+ bit = (w >> shift) & 1;
+
+ if (ds < 0)
+ {
+ /* d < 0. Check if any of the bits below is set: If so, our bit
+ must be complemented. */
+ if (shift > 0 && (w << (GMP_LIMB_BITS - shift)) > 0)
+ return bit ^ 1;
+ while (limb_index-- > 0)
+ if (d->_mp_d[limb_index] > 0)
+ return bit ^ 1;
+ }
+ return bit;
+}
+
+static void
+mpz_abs_add_bit (mpz_t d, mp_bitcnt_t bit_index)
+{
+ mp_size_t dn, limb_index;
+ mp_limb_t bit;
+ mp_ptr dp;
+
+ dn = GMP_ABS (d->_mp_size);
+
+ limb_index = bit_index / GMP_LIMB_BITS;
+ bit = (mp_limb_t) 1 << (bit_index % GMP_LIMB_BITS);
+
+ if (limb_index >= dn)
+ {
+ mp_size_t i;
+ /* The bit should be set outside of the end of the number.
+ We have to increase the size of the number. */
+ dp = MPZ_REALLOC (d, limb_index + 1);
+
+ dp[limb_index] = bit;
+ for (i = dn; i < limb_index; i++)
+ dp[i] = 0;
+ dn = limb_index + 1;
+ }
+ else
+ {
+ mp_limb_t cy;
+
+ dp = d->_mp_d;
+
+ cy = mpn_add_1 (dp + limb_index, dp + limb_index, dn - limb_index, bit);
+ if (cy > 0)
+ {
+ dp = MPZ_REALLOC (d, dn + 1);
+ dp[dn++] = cy;
+ }
+ }
+
+ d->_mp_size = (d->_mp_size < 0) ? - dn : dn;
+}
+
+static void
+mpz_abs_sub_bit (mpz_t d, mp_bitcnt_t bit_index)
+{
+ mp_size_t dn, limb_index;
+ mp_ptr dp;
+ mp_limb_t bit;
+
+ dn = GMP_ABS (d->_mp_size);
+ dp = d->_mp_d;
+
+ limb_index = bit_index / GMP_LIMB_BITS;
+ bit = (mp_limb_t) 1 << (bit_index % GMP_LIMB_BITS);
+
+ assert (limb_index < dn);
+
+ gmp_assert_nocarry (mpn_sub_1 (dp + limb_index, dp + limb_index,
+ dn - limb_index, bit));
+ dn -= (dp[dn-1] == 0);
+ d->_mp_size = (d->_mp_size < 0) ? - dn : dn;
+}
+
+void
+mpz_setbit (mpz_t d, mp_bitcnt_t bit_index)
+{
+ if (!mpz_tstbit (d, bit_index))
+ {
+ if (d->_mp_size >= 0)
+ mpz_abs_add_bit (d, bit_index);
+ else
+ mpz_abs_sub_bit (d, bit_index);
+ }
+}
+
+void
+mpz_clrbit (mpz_t d, mp_bitcnt_t bit_index)
+{
+ if (mpz_tstbit (d, bit_index))
+ {
+ if (d->_mp_size >= 0)
+ mpz_abs_sub_bit (d, bit_index);
+ else
+ mpz_abs_add_bit (d, bit_index);
+ }
+}
+
+void
+mpz_combit (mpz_t d, mp_bitcnt_t bit_index)
+{
+ if (mpz_tstbit (d, bit_index) ^ (d->_mp_size < 0))
+ mpz_abs_sub_bit (d, bit_index);
+ else
+ mpz_abs_add_bit (d, bit_index);
+}
+
+void
+mpz_com (mpz_t r, const mpz_t u)
+{
+ mpz_neg (r, u);
+ mpz_sub_ui (r, r, 1);
+}
+
+void
+mpz_and (mpz_t r, const mpz_t u, const mpz_t v)
+{
+ mp_size_t un, vn, rn, i;
+ mp_ptr up, vp, rp;
+
+ mp_limb_t ux, vx, rx;
+ mp_limb_t uc, vc, rc;
+ mp_limb_t ul, vl, rl;
+
+ un = GMP_ABS (u->_mp_size);
+ vn = GMP_ABS (v->_mp_size);
+ if (un < vn)
+ {
+ MPZ_SRCPTR_SWAP (u, v);
+ MP_SIZE_T_SWAP (un, vn);
+ }
+ if (vn == 0)
+ {
+ r->_mp_size = 0;
+ return;
+ }
+
+ uc = u->_mp_size < 0;
+ vc = v->_mp_size < 0;
+ rc = uc & vc;
+
+ ux = -uc;
+ vx = -vc;
+ rx = -rc;
+
+ /* If the smaller input is positive, higher limbs don't matter. */
+ rn = vx ? un : vn;
+
+ rp = MPZ_REALLOC (r, rn + rc);
+
+ up = u->_mp_d;
+ vp = v->_mp_d;
+
+ for (i = 0; i < vn; i++)
+ {
+ ul = (up[i] ^ ux) + uc;
+ uc = ul < uc;
+
+ vl = (vp[i] ^ vx) + vc;
+ vc = vl < vc;
+
+ rl = ( (ul & vl) ^ rx) + rc;
+ rc = rl < rc;
+ rp[i] = rl;
+ }
+ assert (vc == 0);
+
+ for (; i < rn; i++)
+ {
+ ul = (up[i] ^ ux) + uc;
+ uc = ul < uc;
+
+ rl = ( (ul & vx) ^ rx) + rc;
+ rc = rl < rc;
+ rp[i] = rl;
+ }
+ if (rc)
+ rp[rn++] = rc;
+ else
+ rn = mpn_normalized_size (rp, rn);
+
+ r->_mp_size = rx ? -rn : rn;
+}
+
+void
+mpz_ior (mpz_t r, const mpz_t u, const mpz_t v)
+{
+ mp_size_t un, vn, rn, i;
+ mp_ptr up, vp, rp;
+
+ mp_limb_t ux, vx, rx;
+ mp_limb_t uc, vc, rc;
+ mp_limb_t ul, vl, rl;
+
+ un = GMP_ABS (u->_mp_size);
+ vn = GMP_ABS (v->_mp_size);
+ if (un < vn)
+ {
+ MPZ_SRCPTR_SWAP (u, v);
+ MP_SIZE_T_SWAP (un, vn);
+ }
+ if (vn == 0)
+ {
+ mpz_set (r, u);
+ return;
+ }
+
+ uc = u->_mp_size < 0;
+ vc = v->_mp_size < 0;
+ rc = uc | vc;
+
+ ux = -uc;
+ vx = -vc;
+ rx = -rc;
+
+ /* If the smaller input is negative, by sign extension higher limbs
+ don't matter. */
+ rn = vx ? vn : un;
+
+ rp = MPZ_REALLOC (r, rn + rc);
+
+ up = u->_mp_d;
+ vp = v->_mp_d;
+
+ for (i = 0; i < vn; i++)
+ {
+ ul = (up[i] ^ ux) + uc;
+ uc = ul < uc;
+
+ vl = (vp[i] ^ vx) + vc;
+ vc = vl < vc;
+
+ rl = ( (ul | vl) ^ rx) + rc;
+ rc = rl < rc;
+ rp[i] = rl;
+ }
+ assert (vc == 0);
+
+ for (; i < rn; i++)
+ {
+ ul = (up[i] ^ ux) + uc;
+ uc = ul < uc;
+
+ rl = ( (ul | vx) ^ rx) + rc;
+ rc = rl < rc;
+ rp[i] = rl;
+ }
+ if (rc)
+ rp[rn++] = rc;
+ else
+ rn = mpn_normalized_size (rp, rn);
+
+ r->_mp_size = rx ? -rn : rn;
+}
+
+void
+mpz_xor (mpz_t r, const mpz_t u, const mpz_t v)
+{
+ mp_size_t un, vn, i;
+ mp_ptr up, vp, rp;
+
+ mp_limb_t ux, vx, rx;
+ mp_limb_t uc, vc, rc;
+ mp_limb_t ul, vl, rl;
+
+ un = GMP_ABS (u->_mp_size);
+ vn = GMP_ABS (v->_mp_size);
+ if (un < vn)
+ {
+ MPZ_SRCPTR_SWAP (u, v);
+ MP_SIZE_T_SWAP (un, vn);
+ }
+ if (vn == 0)
+ {
+ mpz_set (r, u);
+ return;
+ }
+
+ uc = u->_mp_size < 0;
+ vc = v->_mp_size < 0;
+ rc = uc ^ vc;
+
+ ux = -uc;
+ vx = -vc;
+ rx = -rc;
+
+ rp = MPZ_REALLOC (r, un + rc);
+
+ up = u->_mp_d;
+ vp = v->_mp_d;
+
+ for (i = 0; i < vn; i++)
+ {
+ ul = (up[i] ^ ux) + uc;
+ uc = ul < uc;
+
+ vl = (vp[i] ^ vx) + vc;
+ vc = vl < vc;
+
+ rl = (ul ^ vl ^ rx) + rc;
+ rc = rl < rc;
+ rp[i] = rl;
+ }
+ assert (vc == 0);
+
+ for (; i < un; i++)
+ {
+ ul = (up[i] ^ ux) + uc;
+ uc = ul < uc;
+
+ rl = (ul ^ ux) + rc;
+ rc = rl < rc;
+ rp[i] = rl;
+ }
+ if (rc)
+ rp[un++] = rc;
+ else
+ un = mpn_normalized_size (rp, un);
+
+ r->_mp_size = rx ? -un : un;
+}
+
+static unsigned
+gmp_popcount_limb (mp_limb_t x)
+{
+ unsigned c;
+
+ /* Do 16 bits at a time, to avoid limb-sized constants. */
+ for (c = 0; x > 0; x >>= 16)
+ {
+ unsigned w = ((x >> 1) & 0x5555) + (x & 0x5555);
+ w = ((w >> 2) & 0x3333) + (w & 0x3333);
+ w = ((w >> 4) & 0x0f0f) + (w & 0x0f0f);
+ w = (w >> 8) + (w & 0x00ff);
+ c += w;
+ }
+ return c;
+}
+
+mp_bitcnt_t
+mpz_popcount (const mpz_t u)
+{
+ mp_size_t un, i;
+ mp_bitcnt_t c;
+
+ un = u->_mp_size;
+
+ if (un < 0)
+ return ~(mp_bitcnt_t) 0;
+
+ for (c = 0, i = 0; i < un; i++)
+ c += gmp_popcount_limb (u->_mp_d[i]);
+
+ return c;
+}
+
+mp_bitcnt_t
+mpz_hamdist (const mpz_t u, const mpz_t v)
+{
+ mp_size_t un, vn, i;
+ mp_limb_t uc, vc, ul, vl, comp;
+ mp_srcptr up, vp;
+ mp_bitcnt_t c;
+
+ un = u->_mp_size;
+ vn = v->_mp_size;
+
+ if ( (un ^ vn) < 0)
+ return ~(mp_bitcnt_t) 0;
+
+ if (un < 0)
+ {
+ assert (vn < 0);
+ un = -un;
+ vn = -vn;
+ uc = vc = 1;
+ comp = - (mp_limb_t) 1;
+ }
+ else
+ uc = vc = comp = 0;
+
+ up = u->_mp_d;
+ vp = v->_mp_d;
+
+ if (un < vn)
+ MPN_SRCPTR_SWAP (up, un, vp, vn);
+
+ for (i = 0, c = 0; i < vn; i++)
+ {
+ ul = (up[i] ^ comp) + uc;
+ uc = ul < uc;
+
+ vl = (vp[i] ^ comp) + vc;
+ vc = vl < vc;
+
+ c += gmp_popcount_limb (ul ^ vl);
+ }
+ assert (vc == 0);
+
+ for (; i < un; i++)
+ {
+ ul = (up[i] ^ comp) + uc;
+ uc = ul < uc;
+
+ c += gmp_popcount_limb (ul ^ comp);
+ }
+
+ return c;
+}
+
+mp_bitcnt_t
+mpz_scan1 (const mpz_t u, mp_bitcnt_t starting_bit)
+{
+ mp_ptr up;
+ mp_size_t us, un, i;
+ mp_limb_t limb, ux, uc;
+ unsigned cnt;
+
+ up = u->_mp_d;
+ us = u->_mp_size;
+ un = GMP_ABS (us);
+ i = starting_bit / GMP_LIMB_BITS;
+
+ /* Past the end there's no 1 bits for u>=0, or an immediate 1 bit
+ for u<0. Notice this test picks up any u==0 too. */
+ if (i >= un)
+ return (us >= 0 ? ~(mp_bitcnt_t) 0 : starting_bit);
+
+ if (us < 0)
+ {
+ ux = GMP_LIMB_MAX;
+ uc = mpn_zero_p (up, i);
+ }
+ else
+ ux = uc = 0;
+
+ limb = (ux ^ up[i]) + uc;
+ uc = limb < uc;
+
+ /* Mask to 0 all bits before starting_bit, thus ignoring them. */
+ limb &= (GMP_LIMB_MAX << (starting_bit % GMP_LIMB_BITS));
+
+ while (limb == 0)
+ {
+ i++;
+ if (i == un)
+ {
+ assert (uc == 0);
+ /* For the u > 0 case, this can happen only for the first
+ masked limb. For the u < 0 case, it happens when the
+ highest limbs of the absolute value are all ones. */
+ return (us >= 0 ? ~(mp_bitcnt_t) 0 : un * GMP_LIMB_BITS);
+ }
+ limb = (ux ^ up[i]) + uc;
+ uc = limb < uc;
+ }
+ gmp_ctz (cnt, limb);
+ return (mp_bitcnt_t) i * GMP_LIMB_BITS + cnt;
+}
+
+mp_bitcnt_t
+mpz_scan0 (const mpz_t u, mp_bitcnt_t starting_bit)
+{
+ mp_ptr up;
+ mp_size_t us, un, i;
+ mp_limb_t limb, ux, uc;
+ unsigned cnt;
+
+ up = u->_mp_d;
+ us = u->_mp_size;
+ un = GMP_ABS (us);
+ i = starting_bit / GMP_LIMB_BITS;
+
+ /* When past end, there's an immediate 0 bit for u>=0, or no 0 bits for
+ u<0. Notice this test picks up all cases of u==0 too. */
+ if (i >= un)
+ return (us >= 0 ? starting_bit : ~(mp_bitcnt_t) 0);
+
+ if (us < 0)
+ {
+ ux = GMP_LIMB_MAX;
+ uc = mpn_zero_p (up, i);
+ }
+ else
+ ux = uc = 0;
+
+ limb = (ux ^ up[i]) + uc;
+ uc = limb < uc;
+
+ /* Mask to 1 all bits before starting_bit, thus ignoring them. */
+ limb |= ((mp_limb_t) 1 << (starting_bit % GMP_LIMB_BITS)) - 1;
+
+ while (limb == GMP_LIMB_MAX)
+ {
+ i++;
+ if (i == un)
+ {
+ assert (uc == 0);
+ return (us >= 0 ? un * GMP_LIMB_BITS : ~(mp_bitcnt_t) 0);
+ }
+ limb = (ux ^ up[i]) + uc;
+ uc = limb < uc;
+ }
+ gmp_ctz (cnt, ~limb);
+ return (mp_bitcnt_t) i * GMP_LIMB_BITS + cnt;
+}
+
+
+/* MPZ base conversion. */
+
+size_t
+mpz_sizeinbase (const mpz_t u, int base)
+{
+ mp_size_t un;
+ mp_srcptr up;
+ mp_ptr tp;
+ mp_bitcnt_t bits;
+ struct gmp_div_inverse bi;
+ size_t ndigits;
+
+ assert (base >= 2);
+ assert (base <= 36);
+
+ un = GMP_ABS (u->_mp_size);
+ if (un == 0)
+ return 1;
+
+ up = u->_mp_d;
+
+ bits = (un - 1) * GMP_LIMB_BITS + mpn_limb_size_in_base_2 (up[un-1]);
+ switch (base)
+ {
+ case 2:
+ return bits;
+ case 4:
+ return (bits + 1) / 2;
+ case 8:
+ return (bits + 2) / 3;
+ case 16:
+ return (bits + 3) / 4;
+ case 32:
+ return (bits + 4) / 5;
+ /* FIXME: Do something more clever for the common case of base
+ 10. */
+ }
+
+ tp = gmp_xalloc_limbs (un);
+ mpn_copyi (tp, up, un);
+ mpn_div_qr_1_invert (&bi, base);
+
+ for (ndigits = 0; un > 0; ndigits++)
+ {
+ mpn_div_qr_1_preinv (tp, tp, un, &bi);
+ un -= (tp[un-1] == 0);
+ }
+ gmp_free (tp);
+ return ndigits;
+}
+
+char *
+mpz_get_str (char *sp, int base, const mpz_t u)
+{
+ unsigned bits;
+ const char *digits;
+ mp_size_t un;
+ size_t i, sn;
+
+ if (base >= 0)
+ {
+ digits = "0123456789abcdefghijklmnopqrstuvwxyz";
+ }
+ else
+ {
+ base = -base;
+ digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ }
+ if (base <= 1)
+ base = 10;
+ if (base > 36)
+ return NULL;
+
+ sn = 1 + mpz_sizeinbase (u, base);
+ if (!sp)
+ sp = gmp_xalloc (1 + sn);
+
+ un = GMP_ABS (u->_mp_size);
+
+ if (un == 0)
+ {
+ sp[0] = '0';
+ sp[1] = '\0';
+ return sp;
+ }
+
+ i = 0;
+
+ if (u->_mp_size < 0)
+ sp[i++] = '-';
+
+ bits = mpn_base_power_of_two_p (base);
+
+ if (bits)
+ /* Not modified in this case. */
+ sn = i + mpn_get_str_bits ((unsigned char *) sp + i, bits, u->_mp_d, un);
+ else
+ {
+ struct mpn_base_info info;
+ mp_ptr tp;
+
+ mpn_get_base_info (&info, base);
+ tp = gmp_xalloc_limbs (un);
+ mpn_copyi (tp, u->_mp_d, un);
+
+ sn = i + mpn_get_str_other ((unsigned char *) sp + i, base, &info, tp, un);
+ gmp_free (tp);
+ }
+
+ for (; i < sn; i++)
+ sp[i] = digits[(unsigned char) sp[i]];
+
+ sp[sn] = '\0';
+ return sp;
+}
+
+int
+mpz_set_str (mpz_t r, const char *sp, int base)
+{
+ unsigned bits;
+ mp_size_t rn, alloc;
+ mp_ptr rp;
+ size_t sn;
+ size_t dn;
+ int sign;
+ unsigned char *dp;
+
+ assert (base == 0 || (base >= 2 && base <= 36));
+
+ while (isspace( (unsigned char) *sp))
+ sp++;
+
+ if (*sp == '-')
+ {
+ sign = 1;
+ sp++;
+ }
+ else
+ sign = 0;
+
+ if (base == 0)
+ {
+ if (*sp == '0')
+ {
+ sp++;
+ if (*sp == 'x' || *sp == 'X')
+ {
+ base = 16;
+ sp++;
+ }
+ else if (*sp == 'b' || *sp == 'B')
+ {
+ base = 2;
+ sp++;
+ }
+ else
+ base = 8;
+ }
+ else
+ base = 10;
+ }
+
+ sn = strlen (sp);
+ dp = gmp_xalloc (sn + (sn == 0));
+
+ for (dn = 0; *sp; sp++)
+ {
+ unsigned digit;
+
+ if (isspace ((unsigned char) *sp))
+ continue;
+ if (*sp >= '0' && *sp <= '9')
+ digit = *sp - '0';
+ else if (*sp >= 'a' && *sp <= 'z')
+ digit = *sp - 'a' + 10;
+ else if (*sp >= 'A' && *sp <= 'Z')
+ digit = *sp - 'A' + 10;
+ else
+ digit = base; /* fail */
+
+ if (digit >= base)
+ {
+ gmp_free (dp);
+ r->_mp_size = 0;
+ return -1;
+ }
+
+ dp[dn++] = digit;
+ }
+
+ bits = mpn_base_power_of_two_p (base);
+
+ if (bits > 0)
+ {
+ alloc = (sn * bits + GMP_LIMB_BITS - 1) / GMP_LIMB_BITS;
+ rp = MPZ_REALLOC (r, alloc);
+ rn = mpn_set_str_bits (rp, dp, dn, bits);
+ }
+ else
+ {
+ struct mpn_base_info info;
+ mpn_get_base_info (&info, base);
+ alloc = (sn + info.exp - 1) / info.exp;
+ rp = MPZ_REALLOC (r, alloc);
+ rn = mpn_set_str_other (rp, dp, dn, base, &info);
+ }
+ assert (rn <= alloc);
+ gmp_free (dp);
+
+ r->_mp_size = sign ? - rn : rn;
+
+ return 0;
+}
+
+int
+mpz_init_set_str (mpz_t r, const char *sp, int base)
+{
+ mpz_init (r);
+ return mpz_set_str (r, sp, base);
+}
+
+size_t
+mpz_out_str (FILE *stream, int base, const mpz_t x)
+{
+ char *str;
+ size_t len;
+
+ str = mpz_get_str (NULL, base, x);
+ len = strlen (str);
+ len = fwrite (str, 1, len, stream);
+ gmp_free (str);
+ return len;
+}
+
+
+static int
+gmp_detect_endian (void)
+{
+ static const int i = 1;
+ const unsigned char *p = (const unsigned char *) &i;
+ if (*p == 1)
+ /* Little endian */
+ return -1;
+ else
+ /* Big endian */
+ return 1;
+}
+
+/* Import and export. Does not support nails. */
+void
+mpz_import (mpz_t r, size_t count, int order, size_t size, int endian,
+ size_t nails, const void *src)
+{
+ const unsigned char *p;
+ ptrdiff_t word_step;
+ mp_ptr rp;
+ mp_size_t rn;
+
+ /* The current (partial) limb. */
+ mp_limb_t limb;
+ /* The number of bytes already copied to this limb (starting from
+ the low end). */
+ size_t bytes;
+ /* The index where the limb should be stored, when completed. */
+ mp_size_t i;
+
+ if (nails != 0)
+ gmp_die ("mpz_import: Nails not supported.");
+
+ assert (order == 1 || order == -1);
+ assert (endian >= -1 && endian <= 1);
+
+ if (endian == 0)
+ endian = gmp_detect_endian ();
+
+ p = (unsigned char *) src;
+
+ word_step = (order != endian) ? 2 * size : 0;
+
+ /* Process bytes from the least significant end, so point p at the
+ least significant word. */
+ if (order == 1)
+ {
+ p += size * (count - 1);
+ word_step = - word_step;
+ }
+
+ /* And at least significant byte of that word. */
+ if (endian == 1)
+ p += (size - 1);
+
+ rn = (size * count + sizeof(mp_limb_t) - 1) / sizeof(mp_limb_t);
+ rp = MPZ_REALLOC (r, rn);
+
+ for (limb = 0, bytes = 0, i = 0; count > 0; count--, p += word_step)
+ {
+ size_t j;
+ for (j = 0; j < size; j++, p -= (ptrdiff_t) endian)
+ {
+ limb |= (mp_limb_t) *p << (bytes++ * CHAR_BIT);
+ if (bytes == sizeof(mp_limb_t))
+ {
+ rp[i++] = limb;
+ bytes = 0;
+ limb = 0;
+ }
+ }
+ }
+ if (bytes > 0)
+ rp[i++] = limb;
+ assert (i == rn);
+
+ r->_mp_size = mpn_normalized_size (rp, i);
+}
+
+void *
+mpz_export (void *r, size_t *countp, int order, size_t size, int endian,
+ size_t nails, const mpz_t u)
+{
+ unsigned char *p;
+ ptrdiff_t word_step;
+ size_t count, k;
+ mp_size_t un;
+
+ /* The current (partial) limb. */
+ mp_limb_t limb;
+ /* The number of bytes left to to in this limb. */
+ size_t bytes;
+ /* The index where the limb was read. */
+ mp_size_t i;
+
+ if (nails != 0)
+ gmp_die ("mpz_import: Nails not supported.");
+
+ assert (order == 1 || order == -1);
+ assert (endian >= -1 && endian <= 1);
+ assert (size > 0 || u->_mp_size == 0);
+
+ un = GMP_ABS (u->_mp_size);
+ if (un == 0)
+ {
+ if (countp)
+ *countp = 0;
+ return r;
+ }
+
+ /* Count bytes in top limb. */
+ for (limb = u->_mp_d[un-1], k = 0; limb > 0; k++, limb >>= CHAR_BIT)
+ ;
+
+ assert (k > 0);
+
+ count = (k + (un-1) * sizeof (mp_limb_t) + size - 1) / size;
+
+ if (!r)
+ r = gmp_xalloc (count * size);
+
+ if (endian == 0)
+ endian = gmp_detect_endian ();
+
+ p = (unsigned char *) r;
+
+ word_step = (order != endian) ? 2 * size : 0;
+
+ /* Process bytes from the least significant end, so point p at the
+ least significant word. */
+ if (order == 1)
+ {
+ p += size * (count - 1);
+ word_step = - word_step;
+ }
+
+ /* And at least significant byte of that word. */
+ if (endian == 1)
+ p += (size - 1);
+
+ for (bytes = 0, i = 0, k = 0; k < count; k++, p += word_step)
+ {
+ size_t j;
+ for (j = 0; j < size; j++, p -= (ptrdiff_t) endian)
+ {
+ if (bytes == 0)
+ {
+ if (i < un)
+ limb = u->_mp_d[i++];
+ bytes = sizeof (mp_limb_t);
+ }
+ *p = limb;
+ limb >>= CHAR_BIT;
+ bytes--;
+ }
+ }
+ assert (i == un);
+ assert (k == count);
+
+ if (countp)
+ *countp = count;
+
+ return r;
+}
diff --git a/lib/gmp/mini-gmp.h b/lib/gmp/mini-gmp.h
new file mode 100644
index 000000000..8c94ca2ed
--- /dev/null
+++ b/lib/gmp/mini-gmp.h
@@ -0,0 +1,256 @@
+/* mini-gmp, a minimalistic implementation of a GNU GMP subset.
+
+Copyright 2011, 2012, 2013 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 3 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with the GNU MP Library. If not, see http://www.gnu.org/licenses/. */
+
+/* About mini-gmp: This is a minimal implementation of a subset of the
+ GMP interface. It is intended for inclusion into applications which
+ have modest bignums needs, as a fallback when the real GMP library
+ is not installed.
+
+ This file defines the public interface. */
+
+#ifndef __MINI_GMP_H__
+#define __MINI_GMP_H__
+
+/* For size_t */
+#include <stddef.h>
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+void mp_set_memory_functions (void *(*) (size_t),
+ void *(*) (void *, size_t, size_t),
+ void (*) (void *, size_t));
+
+void mp_get_memory_functions (void *(**) (size_t),
+ void *(**) (void *, size_t, size_t),
+ void (**) (void *, size_t));
+
+typedef unsigned long mp_limb_t;
+typedef long mp_size_t;
+typedef unsigned long mp_bitcnt_t;
+
+typedef mp_limb_t *mp_ptr;
+typedef const mp_limb_t *mp_srcptr;
+
+typedef struct
+{
+ int _mp_alloc; /* Number of *limbs* allocated and pointed
+ to by the _mp_d field. */
+ int _mp_size; /* abs(_mp_size) is the number of limbs the
+ last field points to. If _mp_size is
+ negative this is a negative number. */
+ mp_limb_t *_mp_d; /* Pointer to the limbs. */
+} __mpz_struct;
+
+typedef __mpz_struct mpz_t[1];
+
+typedef __mpz_struct *mpz_ptr;
+typedef const __mpz_struct *mpz_srcptr;
+
+void mpn_copyi (mp_ptr, mp_srcptr, mp_size_t);
+void mpn_copyd (mp_ptr, mp_srcptr, mp_size_t);
+
+int mpn_cmp (mp_srcptr, mp_srcptr, mp_size_t);
+
+mp_limb_t mpn_add_1 (mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);
+mp_limb_t mpn_add_n (mp_ptr, mp_srcptr, mp_srcptr, mp_size_t);
+mp_limb_t mpn_add (mp_ptr, mp_srcptr, mp_size_t, mp_srcptr, mp_size_t);
+
+mp_limb_t mpn_sub_1 (mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);
+mp_limb_t mpn_sub_n (mp_ptr, mp_srcptr, mp_srcptr, mp_size_t);
+mp_limb_t mpn_sub (mp_ptr, mp_srcptr, mp_size_t, mp_srcptr, mp_size_t);
+
+mp_limb_t mpn_mul_1 (mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);
+mp_limb_t mpn_addmul_1 (mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);
+mp_limb_t mpn_submul_1 (mp_ptr, mp_srcptr, mp_size_t, mp_limb_t);
+
+mp_limb_t mpn_mul (mp_ptr, mp_srcptr, mp_size_t, mp_srcptr, mp_size_t);
+void mpn_mul_n (mp_ptr, mp_srcptr, mp_srcptr, mp_size_t);
+void mpn_sqr (mp_ptr, mp_srcptr, mp_size_t);
+
+mp_limb_t mpn_lshift (mp_ptr, mp_srcptr, mp_size_t, unsigned int);
+mp_limb_t mpn_rshift (mp_ptr, mp_srcptr, mp_size_t, unsigned int);
+
+mp_limb_t mpn_invert_3by2 (mp_limb_t, mp_limb_t);
+#define mpn_invert_limb(x) mpn_invert_3by2 ((x), 0)
+
+size_t mpn_get_str (unsigned char *, int, mp_ptr, mp_size_t);
+mp_size_t mpn_set_str (mp_ptr, const unsigned char *, size_t, int);
+
+void mpz_init (mpz_t);
+void mpz_init2 (mpz_t, mp_bitcnt_t);
+void mpz_clear (mpz_t);
+
+#define mpz_odd_p(z) (((z)->_mp_size != 0) & (int) (z)->_mp_d[0])
+#define mpz_even_p(z) (! mpz_odd_p (z))
+
+int mpz_sgn (const mpz_t);
+int mpz_cmp_si (const mpz_t, long);
+int mpz_cmp_ui (const mpz_t, unsigned long);
+int mpz_cmp (const mpz_t, const mpz_t);
+int mpz_cmpabs_ui (const mpz_t, unsigned long);
+int mpz_cmpabs (const mpz_t, const mpz_t);
+int mpz_cmp_d (const mpz_t, double);
+int mpz_cmpabs_d (const mpz_t, double);
+
+void mpz_abs (mpz_t, const mpz_t);
+void mpz_neg (mpz_t, const mpz_t);
+void mpz_swap (mpz_t, mpz_t);
+
+void mpz_add_ui (mpz_t, const mpz_t, unsigned long);
+void mpz_add (mpz_t, const mpz_t, const mpz_t);
+void mpz_sub_ui (mpz_t, const mpz_t, unsigned long);
+void mpz_ui_sub (mpz_t, unsigned long, const mpz_t);
+void mpz_sub (mpz_t, const mpz_t, const mpz_t);
+
+void mpz_mul_si (mpz_t, const mpz_t, long int);
+void mpz_mul_ui (mpz_t, const mpz_t, unsigned long int);
+void mpz_mul (mpz_t, const mpz_t, const mpz_t);
+void mpz_mul_2exp (mpz_t, const mpz_t, mp_bitcnt_t);
+
+void mpz_cdiv_qr (mpz_t, mpz_t, const mpz_t, const mpz_t);
+void mpz_fdiv_qr (mpz_t, mpz_t, const mpz_t, const mpz_t);
+void mpz_tdiv_qr (mpz_t, mpz_t, const mpz_t, const mpz_t);
+void mpz_cdiv_q (mpz_t, const mpz_t, const mpz_t);
+void mpz_fdiv_q (mpz_t, const mpz_t, const mpz_t);
+void mpz_tdiv_q (mpz_t, const mpz_t, const mpz_t);
+void mpz_cdiv_r (mpz_t, const mpz_t, const mpz_t);
+void mpz_fdiv_r (mpz_t, const mpz_t, const mpz_t);
+void mpz_tdiv_r (mpz_t, const mpz_t, const mpz_t);
+
+void mpz_cdiv_q_2exp (mpz_t, const mpz_t, mp_bitcnt_t);
+void mpz_fdiv_q_2exp (mpz_t, const mpz_t, mp_bitcnt_t);
+void mpz_tdiv_q_2exp (mpz_t, const mpz_t, mp_bitcnt_t);
+void mpz_cdiv_r_2exp (mpz_t, const mpz_t, mp_bitcnt_t);
+void mpz_fdiv_r_2exp (mpz_t, const mpz_t, mp_bitcnt_t);
+void mpz_tdiv_r_2exp (mpz_t, const mpz_t, mp_bitcnt_t);
+
+void mpz_mod (mpz_t, const mpz_t, const mpz_t);
+
+void mpz_divexact (mpz_t, const mpz_t, const mpz_t);
+
+int mpz_divisible_p (const mpz_t, const mpz_t);
+
+unsigned long mpz_cdiv_qr_ui (mpz_t, mpz_t, const mpz_t, unsigned long);
+unsigned long mpz_fdiv_qr_ui (mpz_t, mpz_t, const mpz_t, unsigned long);
+unsigned long mpz_tdiv_qr_ui (mpz_t, mpz_t, const mpz_t, unsigned long);
+unsigned long mpz_cdiv_q_ui (mpz_t, const mpz_t, unsigned long);
+unsigned long mpz_fdiv_q_ui (mpz_t, const mpz_t, unsigned long);
+unsigned long mpz_tdiv_q_ui (mpz_t, const mpz_t, unsigned long);
+unsigned long mpz_cdiv_r_ui (mpz_t, const mpz_t, unsigned long);
+unsigned long mpz_fdiv_r_ui (mpz_t, const mpz_t, unsigned long);
+unsigned long mpz_tdiv_r_ui (mpz_t, const mpz_t, unsigned long);
+unsigned long mpz_cdiv_ui (const mpz_t, unsigned long);
+unsigned long mpz_fdiv_ui (const mpz_t, unsigned long);
+unsigned long mpz_tdiv_ui (const mpz_t, unsigned long);
+
+unsigned long mpz_mod_ui (mpz_t, const mpz_t, unsigned long);
+
+void mpz_divexact_ui (mpz_t, const mpz_t, unsigned long);
+
+int mpz_divisible_ui_p (const mpz_t, unsigned long);
+
+unsigned long mpz_gcd_ui (mpz_t, const mpz_t, unsigned long);
+void mpz_gcd (mpz_t, const mpz_t, const mpz_t);
+void mpz_gcdext (mpz_t, mpz_t, mpz_t, const mpz_t, const mpz_t);
+void mpz_lcm_ui (mpz_t, const mpz_t, unsigned long);
+void mpz_lcm (mpz_t, const mpz_t, const mpz_t);
+int mpz_invert (mpz_t, const mpz_t, const mpz_t);
+
+void mpz_sqrtrem (mpz_t, mpz_t, const mpz_t);
+void mpz_sqrt (mpz_t, const mpz_t);
+
+void mpz_pow_ui (mpz_t, const mpz_t, unsigned long);
+void mpz_ui_pow_ui (mpz_t, unsigned long, unsigned long);
+void mpz_powm (mpz_t, const mpz_t, const mpz_t, const mpz_t);
+void mpz_powm_ui (mpz_t, const mpz_t, unsigned long, const mpz_t);
+
+void mpz_rootrem (mpz_t, mpz_t, const mpz_t, unsigned long);
+int mpz_root (mpz_t, const mpz_t, unsigned long);
+
+void mpz_fac_ui (mpz_t, unsigned long);
+void mpz_bin_uiui (mpz_t, unsigned long, unsigned long);
+
+int mpz_tstbit (const mpz_t, mp_bitcnt_t);
+void mpz_setbit (mpz_t, mp_bitcnt_t);
+void mpz_clrbit (mpz_t, mp_bitcnt_t);
+void mpz_combit (mpz_t, mp_bitcnt_t);
+
+void mpz_com (mpz_t, const mpz_t);
+void mpz_and (mpz_t, const mpz_t, const mpz_t);
+void mpz_ior (mpz_t, const mpz_t, const mpz_t);
+void mpz_xor (mpz_t, const mpz_t, const mpz_t);
+
+mp_bitcnt_t mpz_popcount (const mpz_t);
+mp_bitcnt_t mpz_hamdist (const mpz_t, const mpz_t);
+mp_bitcnt_t mpz_scan0 (const mpz_t, mp_bitcnt_t);
+mp_bitcnt_t mpz_scan1 (const mpz_t, mp_bitcnt_t);
+
+int mpz_fits_slong_p (const mpz_t);
+int mpz_fits_ulong_p (const mpz_t);
+long int mpz_get_si (const mpz_t);
+unsigned long int mpz_get_ui (const mpz_t);
+double mpz_get_d (const mpz_t);
+size_t mpz_size (const mpz_t);
+mp_limb_t mpz_getlimbn (const mpz_t, mp_size_t);
+
+void mpz_set_si (mpz_t, signed long int);
+void mpz_set_ui (mpz_t, unsigned long int);
+void mpz_set (mpz_t, const mpz_t);
+void mpz_set_d (mpz_t, double);
+
+void mpz_init_set_si (mpz_t, signed long int);
+void mpz_init_set_ui (mpz_t, unsigned long int);
+void mpz_init_set (mpz_t, const mpz_t);
+void mpz_init_set_d (mpz_t, double);
+
+size_t mpz_sizeinbase (const mpz_t, int);
+char *mpz_get_str (char *, int, const mpz_t);
+int mpz_set_str (mpz_t, const char *, int);
+int mpz_init_set_str (mpz_t, const char *, int);
+
+/* This long list taken from gmp.h. */
+/* For reference, "defined(EOF)" cannot be used here. In g++ 2.95.4,
+ <iostream> defines EOF but not FILE. */
+#if defined (FILE) \
+ || defined (H_STDIO) \
+ || defined (_H_STDIO) /* AIX */ \
+ || defined (_STDIO_H) /* glibc, Sun, SCO */ \
+ || defined (_STDIO_H_) /* BSD, OSF */ \
+ || defined (__STDIO_H) /* Borland */ \
+ || defined (__STDIO_H__) /* IRIX */ \
+ || defined (_STDIO_INCLUDED) /* HPUX */ \
+ || defined (__dj_include_stdio_h_) /* DJGPP */ \
+ || defined (_FILE_DEFINED) /* Microsoft */ \
+ || defined (__STDIO__) /* Apple MPW MrC */ \
+ || defined (_MSL_STDIO_H) /* Metrowerks */ \
+ || defined (_STDIO_H_INCLUDED) /* QNX4 */ \
+ || defined (_ISO_STDIO_ISO_H) /* Sun C++ */ \
+ || defined (__STDIO_LOADED) /* VMS */
+size_t mpz_out_str (FILE *, int, const mpz_t);
+#endif
+
+void mpz_import (mpz_t, size_t, int, size_t, int, size_t, const void *);
+void *mpz_export (void *, size_t *, int, size_t, int, size_t, const mpz_t);
+
+#if defined (__cplusplus)
+}
+#endif
+#endif /* __MINI_GMP_H__ */
diff --git a/lib/jsoncpp/CMakeLists.txt b/lib/jsoncpp/CMakeLists.txt
new file mode 100644
index 000000000..9056e4b6d
--- /dev/null
+++ b/lib/jsoncpp/CMakeLists.txt
@@ -0,0 +1,7 @@
+if(MSVC)
+ set(CMAKE_CXX_FLAGS_RELEASE "/MT /O2 /Ob2 /D NDEBUG")
+endif()
+
+add_library(jsoncpp jsoncpp.cpp)
+target_link_libraries(jsoncpp)
+
diff --git a/lib/jsoncpp/json/UPDATING b/lib/jsoncpp/json/UPDATING
new file mode 100644
index 000000000..d00076601
--- /dev/null
+++ b/lib/jsoncpp/json/UPDATING
@@ -0,0 +1,16 @@
+#!/bin/sh
+cd ..
+svn co https://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/trunk/jsoncpp jsoncpp
+svn up jsoncpp
+cd jsoncpp
+python amalgamate.py
+cp -R dist/json ..
+cp dist/jsoncpp.cpp ../json
+
+# maybe you need to patch:
+# src/json/jsoncpp.cpp:
+# -#include <json/json.h>
+# +#include "json/json.h"
+
+#svn export --force https://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/trunk/jsoncpp/src/lib_json json
+#svn export --force https://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/trunk/jsoncpp/include/json json
diff --git a/lib/jsoncpp/json/json.h b/lib/jsoncpp/json/json.h
new file mode 100644
index 000000000..e9b696c01
--- /dev/null
+++ b/lib/jsoncpp/json/json.h
@@ -0,0 +1,1981 @@
+/// Json-cpp amalgated header (http://jsoncpp.sourceforge.net/).
+/// It is intended to be used with #include "json/json.h"
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: LICENSE
+// //////////////////////////////////////////////////////////////////////
+
+/*
+The JsonCpp library's source code, including accompanying documentation,
+tests and demonstration applications, are licensed under the following
+conditions...
+
+The author (Baptiste Lepilleur) explicitly disclaims copyright in all
+jurisdictions which recognize such a disclaimer. In such jurisdictions,
+this software is released into the Public Domain.
+
+In jurisdictions which do not recognize Public Domain property (e.g. Germany as of
+2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur, and is
+released under the terms of the MIT License (see below).
+
+In jurisdictions which recognize Public Domain property, the user of this
+software may choose to accept it either as 1) Public Domain, 2) under the
+conditions of the MIT License (see below), or 3) under the terms of dual
+Public Domain/MIT License conditions described here, as they choose.
+
+The MIT License is about as close to Public Domain as a license can get, and is
+described in clear, concise terms at:
+
+ http://en.wikipedia.org/wiki/MIT_License
+
+The full text of the MIT License follows:
+
+========================================================================
+Copyright (c) 2007-2010 Baptiste Lepilleur
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use, copy,
+modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+========================================================================
+(END LICENSE TEXT)
+
+The MIT license is compatible with both the GPL and commercial
+software, affording one all of the rights of Public Domain with the
+minor nuisance of being required to keep the above copyright notice
+and license text in the source code. Note also that by accepting the
+Public Domain "license" you can re-license your copy using whatever
+license you like.
+
+*/
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: LICENSE
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+#ifndef JSON_AMALGATED_H_INCLUDED
+# define JSON_AMALGATED_H_INCLUDED
+/// If defined, indicates that the source file is amalgated
+/// to prevent private header inclusion.
+#define JSON_IS_AMALGAMATION
+#define JSONCPP_STRING
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/version.h
+// //////////////////////////////////////////////////////////////////////
+
+// DO NOT EDIT. This file (and "version") is generated by CMake.
+// Run CMake configure step to update it.
+#ifndef JSON_VERSION_H_INCLUDED
+# define JSON_VERSION_H_INCLUDED
+
+# define JSONCPP_VERSION_STRING "0.10.6"
+# define JSONCPP_VERSION_MAJOR 0
+# define JSONCPP_VERSION_MINOR 10
+# define JSONCPP_VERSION_PATCH 6
+# define JSONCPP_VERSION_QUALIFIER
+# define JSONCPP_VERSION_HEXA ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | (JSONCPP_VERSION_PATCH << 8))
+
+#endif // JSON_VERSION_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/version.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/config.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef JSON_CONFIG_H_INCLUDED
+#define JSON_CONFIG_H_INCLUDED
+
+/// If defined, indicates that json library is embedded in CppTL library.
+//# define JSON_IN_CPPTL 1
+
+/// If defined, indicates that json may leverage CppTL library
+//# define JSON_USE_CPPTL 1
+/// If defined, indicates that cpptl vector based map should be used instead of
+/// std::map
+/// as Value container.
+//# define JSON_USE_CPPTL_SMALLMAP 1
+
+// If non-zero, the library uses exceptions to report bad input instead of C
+// assertion macros. The default is to use exceptions.
+#ifndef JSON_USE_EXCEPTION
+#define JSON_USE_EXCEPTION 1
+#endif
+
+/// If defined, indicates that the source file is amalgated
+/// to prevent private header inclusion.
+/// Remarks: it is automatically defined in the generated amalgated header.
+// #define JSON_IS_AMALGAMATION
+
+#ifdef JSON_IN_CPPTL
+#include <cpptl/config.h>
+#ifndef JSON_USE_CPPTL
+#define JSON_USE_CPPTL 1
+#endif
+#endif
+
+#ifdef JSON_IN_CPPTL
+#define JSON_API CPPTL_API
+#elif defined(JSON_DLL_BUILD)
+#if defined(_MSC_VER)
+#define JSON_API __declspec(dllexport)
+#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
+#endif // if defined(_MSC_VER)
+#elif defined(JSON_DLL)
+#if defined(_MSC_VER)
+#define JSON_API __declspec(dllimport)
+#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
+#endif // if defined(_MSC_VER)
+#endif // ifdef JSON_IN_CPPTL
+#if !defined(JSON_API)
+#define JSON_API
+#endif
+
+// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for
+// integer
+// Storages, and 64 bits integer support is disabled.
+// #define JSON_NO_INT64 1
+
+#if defined(_MSC_VER) && _MSC_VER <= 1200 // MSVC 6
+// Microsoft Visual Studio 6 only support conversion from __int64 to double
+// (no conversion from unsigned __int64).
+#define JSON_USE_INT64_DOUBLE_CONVERSION 1
+// Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255'
+// characters in the debug information)
+// All projects I've ever seen with VS6 were using this globally (not bothering
+// with pragma push/pop).
+#pragma warning(disable : 4786)
+#endif // if defined(_MSC_VER) && _MSC_VER < 1200 // MSVC 6
+
+#if defined(_MSC_VER) && _MSC_VER >= 1500 // MSVC 2008
+/// Indicates that the following function is deprecated.
+#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message))
+#elif defined(__clang__) && defined(__has_feature)
+#if __has_feature(attribute_deprecated_with_message)
+#define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
+#endif
+#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
+#define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
+#elif defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))
+#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__))
+#endif
+
+#if !defined(JSONCPP_DEPRECATED)
+#define JSONCPP_DEPRECATED(message)
+#endif // if !defined(JSONCPP_DEPRECATED)
+
+namespace Json {
+typedef int Int;
+typedef unsigned int UInt;
+#if defined(JSON_NO_INT64)
+typedef int LargestInt;
+typedef unsigned int LargestUInt;
+#undef JSON_HAS_INT64
+#else // if defined(JSON_NO_INT64)
+// For Microsoft Visual use specific types as long long is not supported
+#if defined(_MSC_VER) // Microsoft Visual Studio
+typedef __int64 Int64;
+typedef unsigned __int64 UInt64;
+#else // if defined(_MSC_VER) // Other platforms, use long long
+typedef long long int Int64;
+typedef unsigned long long int UInt64;
+#endif // if defined(_MSC_VER)
+typedef Int64 LargestInt;
+typedef UInt64 LargestUInt;
+#define JSON_HAS_INT64
+#endif // if defined(JSON_NO_INT64)
+} // end namespace Json
+
+#endif // JSON_CONFIG_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/config.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/forwards.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef JSON_FORWARDS_H_INCLUDED
+#define JSON_FORWARDS_H_INCLUDED
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include "config.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+
+namespace Json {
+
+// writer.h
+class FastWriter;
+class StyledWriter;
+
+// reader.h
+class Reader;
+
+// features.h
+class Features;
+
+// value.h
+typedef unsigned int ArrayIndex;
+class StaticString;
+class Path;
+class PathArgument;
+class Value;
+class ValueIteratorBase;
+class ValueIterator;
+class ValueConstIterator;
+
+} // namespace Json
+
+#endif // JSON_FORWARDS_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/forwards.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/features.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef CPPTL_JSON_FEATURES_H_INCLUDED
+#define CPPTL_JSON_FEATURES_H_INCLUDED
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include "forwards.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+
+namespace Json {
+
+/** \brief Configuration passed to reader and writer.
+ * This configuration object can be used to force the Reader or Writer
+ * to behave in a standard conforming way.
+ */
+class JSON_API Features {
+public:
+ /** \brief A configuration that allows all features and assumes all strings
+ * are UTF-8.
+ * - C & C++ comments are allowed
+ * - Root object can be any JSON value
+ * - Assumes Value strings are encoded in UTF-8
+ */
+ static Features all();
+
+ /** \brief A configuration that is strictly compatible with the JSON
+ * specification.
+ * - Comments are forbidden.
+ * - Root object must be either an array or an object value.
+ * - Assumes Value strings are encoded in UTF-8
+ */
+ static Features strictMode();
+
+ /** \brief Initialize the configuration like JsonConfig::allFeatures;
+ */
+ Features();
+
+ /// \c true if comments are allowed. Default: \c true.
+ bool allowComments_;
+
+ /// \c true if root must be either an array or an object value. Default: \c
+ /// false.
+ bool strictRoot_;
+};
+
+} // namespace Json
+
+#endif // CPPTL_JSON_FEATURES_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/features.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/value.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef CPPTL_JSON_H_INCLUDED
+#define CPPTL_JSON_H_INCLUDED
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include "forwards.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+#include <string>
+#include <vector>
+#include <exception>
+
+#ifndef JSON_USE_CPPTL_SMALLMAP
+#include <map>
+#else
+#include <cpptl/smallmap.h>
+#endif
+#ifdef JSON_USE_CPPTL
+#include <cpptl/forwards.h>
+#endif
+
+// Disable warning C4251: <data member>: <type> needs to have dll-interface to
+// be used by...
+#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+#pragma warning(push)
+#pragma warning(disable : 4251)
+#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+
+//Conditional NORETURN attribute on the throw functions would:
+// a) suppress false positives from static code analysis
+// b) possibly improve optimization opportunities.
+#if !defined(JSONCPP_NORETURN)
+# if defined(_MSC_VER)
+# define JSONCPP_NORETURN __declspec(noreturn)
+# elif defined(__GNUC__)
+# define JSONCPP_NORETURN __attribute__ ((__noreturn__))
+# else
+# define JSONCPP_NORETURN
+# endif
+#endif
+
+/** \brief JSON (JavaScript Object Notation).
+ */
+namespace Json {
+
+/** Base class for all exceptions we throw.
+ *
+ * We use nothing but these internally. Of course, STL can throw others.
+ */
+class JSON_API Exception : public std::exception {
+public:
+ Exception(std::string const& msg);
+ virtual ~Exception() throw();
+ virtual char const* what() const throw();
+protected:
+ std::string const msg_;
+};
+
+/** Exceptions which the user cannot easily avoid.
+ *
+ * E.g. out-of-memory (when we use malloc), stack-overflow, malicious input
+ *
+ * \remark derived from Json::Exception
+ */
+class JSON_API RuntimeError : public Exception {
+public:
+ RuntimeError(std::string const& msg);
+};
+
+/** Exceptions thrown by JSON_ASSERT/JSON_FAIL macros.
+ *
+ * These are precondition-violations (user bugs) and internal errors (our bugs).
+ *
+ * \remark derived from Json::Exception
+ */
+class JSON_API LogicError : public Exception {
+public:
+ LogicError(std::string const& msg);
+};
+
+/// used internally
+JSONCPP_NORETURN void throwRuntimeError(std::string const& msg);
+/// used internally
+JSONCPP_NORETURN void throwLogicError(std::string const& msg);
+
+/** \brief Type of the value held by a Value object.
+ */
+enum ValueType {
+ nullValue = 0, ///< 'null' value
+ intValue, ///< signed integer value
+ uintValue, ///< unsigned integer value
+ realValue, ///< double value
+ stringValue, ///< UTF-8 string value
+ booleanValue, ///< bool value
+ arrayValue, ///< array value (ordered list)
+ objectValue ///< object value (collection of name/value pairs).
+};
+
+enum CommentPlacement {
+ commentBefore = 0, ///< a comment placed on the line before a value
+ commentAfterOnSameLine, ///< a comment just after a value on the same line
+ commentAfter, ///< a comment on the line after a value (only make sense for
+ /// root value)
+ numberOfCommentPlacement
+};
+
+//# ifdef JSON_USE_CPPTL
+// typedef CppTL::AnyEnumerator<const char *> EnumMemberNames;
+// typedef CppTL::AnyEnumerator<const Value &> EnumValues;
+//# endif
+
+/** \brief Lightweight wrapper to tag static string.
+ *
+ * Value constructor and objectValue member assignement takes advantage of the
+ * StaticString and avoid the cost of string duplication when storing the
+ * string or the member name.
+ *
+ * Example of usage:
+ * \code
+ * Json::Value aValue( StaticString("some text") );
+ * Json::Value object;
+ * static const StaticString code("code");
+ * object[code] = 1234;
+ * \endcode
+ */
+class JSON_API StaticString {
+public:
+ explicit StaticString(const char* czstring) : c_str_(czstring) {}
+
+ operator const char*() const { return c_str_; }
+
+ const char* c_str() const { return c_str_; }
+
+private:
+ const char* c_str_;
+};
+
+/** \brief Represents a <a HREF="http://www.json.org">JSON</a> value.
+ *
+ * This class is a discriminated union wrapper that can represents a:
+ * - signed integer [range: Value::minInt - Value::maxInt]
+ * - unsigned integer (range: 0 - Value::maxUInt)
+ * - double
+ * - UTF-8 string
+ * - boolean
+ * - 'null'
+ * - an ordered list of Value
+ * - collection of name/value pairs (javascript object)
+ *
+ * The type of the held value is represented by a #ValueType and
+ * can be obtained using type().
+ *
+ * Values of an #objectValue or #arrayValue can be accessed using operator[]()
+ * methods.
+ * Non-const methods will automatically create the a #nullValue element
+ * if it does not exist.
+ * The sequence of an #arrayValue will be automatically resized and initialized
+ * with #nullValue. resize() can be used to enlarge or truncate an #arrayValue.
+ *
+ * The get() methods can be used to obtain default value in the case the
+ * required element does not exist.
+ *
+ * It is possible to iterate over the list of a #objectValue values using
+ * the getMemberNames() method.
+ *
+ * \note #Value string-length fit in size_t, but keys must be < 2^30.
+ * (The reason is an implementation detail.) A #CharReader will raise an
+ * exception if a bound is exceeded to avoid security holes in your app,
+ * but the Value API does *not* check bounds. That is the responsibility
+ * of the caller.
+ */
+class JSON_API Value {
+ friend class ValueIteratorBase;
+public:
+ typedef std::vector<std::string> Members;
+ typedef ValueIterator iterator;
+ typedef ValueConstIterator const_iterator;
+ typedef Json::UInt UInt;
+ typedef Json::Int Int;
+#if defined(JSON_HAS_INT64)
+ typedef Json::UInt64 UInt64;
+ typedef Json::Int64 Int64;
+#endif // defined(JSON_HAS_INT64)
+ typedef Json::LargestInt LargestInt;
+ typedef Json::LargestUInt LargestUInt;
+ typedef Json::ArrayIndex ArrayIndex;
+
+ static const Value& nullRef;
+#if !defined(__ARMEL__)
+ /// \deprecated This exists for binary compatibility only. Use nullRef.
+ static const Value null;
+#endif
+ /// Minimum signed integer value that can be stored in a Json::Value.
+ static const LargestInt minLargestInt;
+ /// Maximum signed integer value that can be stored in a Json::Value.
+ static const LargestInt maxLargestInt;
+ /// Maximum unsigned integer value that can be stored in a Json::Value.
+ static const LargestUInt maxLargestUInt;
+
+ /// Minimum signed int value that can be stored in a Json::Value.
+ static const Int minInt;
+ /// Maximum signed int value that can be stored in a Json::Value.
+ static const Int maxInt;
+ /// Maximum unsigned int value that can be stored in a Json::Value.
+ static const UInt maxUInt;
+
+#if defined(JSON_HAS_INT64)
+ /// Minimum signed 64 bits int value that can be stored in a Json::Value.
+ static const Int64 minInt64;
+ /// Maximum signed 64 bits int value that can be stored in a Json::Value.
+ static const Int64 maxInt64;
+ /// Maximum unsigned 64 bits int value that can be stored in a Json::Value.
+ static const UInt64 maxUInt64;
+#endif // defined(JSON_HAS_INT64)
+
+private:
+#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION
+ class CZString {
+ public:
+ enum DuplicationPolicy {
+ noDuplication = 0,
+ duplicate,
+ duplicateOnCopy
+ };
+ CZString(ArrayIndex index);
+ CZString(char const* str, unsigned length, DuplicationPolicy allocate);
+ CZString(CZString const& other);
+ ~CZString();
+ CZString& operator=(CZString other);
+ bool operator<(CZString const& other) const;
+ bool operator==(CZString const& other) const;
+ ArrayIndex index() const;
+ //const char* c_str() const; ///< \deprecated
+ char const* data() const;
+ unsigned length() const;
+ bool isStaticString() const;
+
+ private:
+ void swap(CZString& other);
+
+ struct StringStorage {
+ unsigned policy_: 2;
+ unsigned length_: 30; // 1GB max
+ };
+
+ char const* cstr_; // actually, a prefixed string, unless policy is noDup
+ union {
+ ArrayIndex index_;
+ StringStorage storage_;
+ };
+ };
+
+public:
+#ifndef JSON_USE_CPPTL_SMALLMAP
+ typedef std::map<CZString, Value> ObjectValues;
+#else
+ typedef CppTL::SmallMap<CZString, Value> ObjectValues;
+#endif // ifndef JSON_USE_CPPTL_SMALLMAP
+#endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION
+
+public:
+ /** \brief Create a default Value of the given type.
+
+ This is a very useful constructor.
+ To create an empty array, pass arrayValue.
+ To create an empty object, pass objectValue.
+ Another Value can then be set to this one by assignment.
+This is useful since clear() and resize() will not alter types.
+
+ Examples:
+\code
+Json::Value null_value; // null
+Json::Value arr_value(Json::arrayValue); // []
+Json::Value obj_value(Json::objectValue); // {}
+\endcode
+ */
+ Value(ValueType type = nullValue);
+ Value(Int value);
+ Value(UInt value);
+#if defined(JSON_HAS_INT64)
+ Value(Int64 value);
+ Value(UInt64 value);
+#endif // if defined(JSON_HAS_INT64)
+ Value(double value);
+ Value(const char* value); ///< Copy til first 0. (NULL causes to seg-fault.)
+ Value(const char* begin, const char* end); ///< Copy all, incl zeroes.
+ /** \brief Constructs a value from a static string.
+
+ * Like other value string constructor but do not duplicate the string for
+ * internal storage. The given string must remain alive after the call to this
+ * constructor.
+ * \note This works only for null-terminated strings. (We cannot change the
+ * size of this class, so we have nowhere to store the length,
+ * which might be computed later for various operations.)
+ *
+ * Example of usage:
+ * \code
+ * static StaticString foo("some text");
+ * Json::Value aValue(foo);
+ * \endcode
+ */
+ Value(const StaticString& value);
+ Value(const std::string& value); ///< Copy data() til size(). Embedded zeroes too.
+#ifdef JSON_USE_CPPTL
+ Value(const CppTL::ConstString& value);
+#endif
+ Value(bool value);
+ /// Deep copy.
+ Value(const Value& other);
+ ~Value();
+
+ /// Deep copy, then swap(other).
+ /// \note Over-write existing comments. To preserve comments, use #swapPayload().
+ Value &operator=(const Value &other);
+ /// Swap everything.
+ void swap(Value& other);
+ /// Swap values but leave comments and source offsets in place.
+ void swapPayload(Value& other);
+
+ ValueType type() const;
+
+ /// Compare payload only, not comments etc.
+ bool operator<(const Value& other) const;
+ bool operator<=(const Value& other) const;
+ bool operator>=(const Value& other) const;
+ bool operator>(const Value& other) const;
+ bool operator==(const Value& other) const;
+ bool operator!=(const Value& other) const;
+ int compare(const Value& other) const;
+
+ const char* asCString() const; ///< Embedded zeroes could cause you trouble!
+ std::string asString() const; ///< Embedded zeroes are possible.
+ /** Get raw char* of string-value.
+ * \return false if !string. (Seg-fault if str or end are NULL.)
+ */
+ bool getString(
+ char const** begin, char const** end) const;
+#ifdef JSON_USE_CPPTL
+ CppTL::ConstString asConstString() const;
+#endif
+ Int asInt() const;
+ UInt asUInt() const;
+#if defined(JSON_HAS_INT64)
+ Int64 asInt64() const;
+ UInt64 asUInt64() const;
+#endif // if defined(JSON_HAS_INT64)
+ LargestInt asLargestInt() const;
+ LargestUInt asLargestUInt() const;
+ float asFloat() const;
+ double asDouble() const;
+ bool asBool() const;
+
+ bool isNull() const;
+ bool isBool() const;
+ bool isInt() const;
+ bool isInt64() const;
+ bool isUInt() const;
+ bool isUInt64() const;
+ bool isIntegral() const;
+ bool isDouble() const;
+ bool isNumeric() const;
+ bool isString() const;
+ bool isArray() const;
+ bool isObject() const;
+
+ bool isConvertibleTo(ValueType other) const;
+
+ /// Number of values in array or object
+ ArrayIndex size() const;
+
+ /// \brief Return true if empty array, empty object, or null;
+ /// otherwise, false.
+ bool empty() const;
+
+ /// Return isNull()
+ bool operator!() const;
+
+ /// Remove all object members and array elements.
+ /// \pre type() is arrayValue, objectValue, or nullValue
+ /// \post type() is unchanged
+ void clear();
+
+ /// Resize the array to size elements.
+ /// New elements are initialized to null.
+ /// May only be called on nullValue or arrayValue.
+ /// \pre type() is arrayValue or nullValue
+ /// \post type() is arrayValue
+ void resize(ArrayIndex size);
+
+ /// Access an array element (zero based index ).
+ /// If the array contains less than index element, then null value are
+ /// inserted
+ /// in the array so that its size is index+1.
+ /// (You may need to say 'value[0u]' to get your compiler to distinguish
+ /// this from the operator[] which takes a string.)
+ Value& operator[](ArrayIndex index);
+
+ /// Access an array element (zero based index ).
+ /// If the array contains less than index element, then null value are
+ /// inserted
+ /// in the array so that its size is index+1.
+ /// (You may need to say 'value[0u]' to get your compiler to distinguish
+ /// this from the operator[] which takes a string.)
+ Value& operator[](int index);
+
+ /// Access an array element (zero based index )
+ /// (You may need to say 'value[0u]' to get your compiler to distinguish
+ /// this from the operator[] which takes a string.)
+ const Value& operator[](ArrayIndex index) const;
+
+ /// Access an array element (zero based index )
+ /// (You may need to say 'value[0u]' to get your compiler to distinguish
+ /// this from the operator[] which takes a string.)
+ const Value& operator[](int index) const;
+
+ /// If the array contains at least index+1 elements, returns the element
+ /// value,
+ /// otherwise returns defaultValue.
+ Value get(ArrayIndex index, const Value& defaultValue) const;
+ /// Return true if index < size().
+ bool isValidIndex(ArrayIndex index) const;
+ /// \brief Append value to array at the end.
+ ///
+ /// Equivalent to jsonvalue[jsonvalue.size()] = value;
+ Value& append(const Value& value);
+
+ /// Access an object value by name, create a null member if it does not exist.
+ /// \note Because of our implementation, keys are limited to 2^30 -1 chars.
+ /// Exceeding that will cause an exception.
+ Value& operator[](const char* key);
+ /// Access an object value by name, returns null if there is no member with
+ /// that name.
+ const Value& operator[](const char* key) const;
+ /// Access an object value by name, create a null member if it does not exist.
+ /// \param key may contain embedded nulls.
+ Value& operator[](const std::string& key);
+ /// Access an object value by name, returns null if there is no member with
+ /// that name.
+ /// \param key may contain embedded nulls.
+ const Value& operator[](const std::string& key) const;
+ /** \brief Access an object value by name, create a null member if it does not
+ exist.
+
+ * If the object has no entry for that name, then the member name used to store
+ * the new entry is not duplicated.
+ * Example of use:
+ * \code
+ * Json::Value object;
+ * static const StaticString code("code");
+ * object[code] = 1234;
+ * \endcode
+ */
+ Value& operator[](const StaticString& key);
+#ifdef JSON_USE_CPPTL
+ /// Access an object value by name, create a null member if it does not exist.
+ Value& operator[](const CppTL::ConstString& key);
+ /// Access an object value by name, returns null if there is no member with
+ /// that name.
+ const Value& operator[](const CppTL::ConstString& key) const;
+#endif
+ /// Return the member named key if it exist, defaultValue otherwise.
+ /// \note deep copy
+ Value get(const char* key, const Value& defaultValue) const;
+ /// Return the member named key if it exist, defaultValue otherwise.
+ /// \note deep copy
+ /// \note key may contain embedded nulls.
+ Value get(const char* begin, const char* end, const Value& defaultValue) const;
+ /// Return the member named key if it exist, defaultValue otherwise.
+ /// \note deep copy
+ /// \param key may contain embedded nulls.
+ Value get(const std::string& key, const Value& defaultValue) const;
+#ifdef JSON_USE_CPPTL
+ /// Return the member named key if it exist, defaultValue otherwise.
+ /// \note deep copy
+ Value get(const CppTL::ConstString& key, const Value& defaultValue) const;
+#endif
+ /// Most general and efficient version of isMember()const, get()const,
+ /// and operator[]const
+ /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30
+ Value const* find(char const* begin, char const* end) const;
+ /// Most general and efficient version of object-mutators.
+ /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30
+ /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue.
+ Value const* demand(char const* begin, char const* end);
+ /// \brief Remove and return the named member.
+ ///
+ /// Do nothing if it did not exist.
+ /// \return the removed Value, or null.
+ /// \pre type() is objectValue or nullValue
+ /// \post type() is unchanged
+ /// \deprecated
+ Value removeMember(const char* key);
+ /// Same as removeMember(const char*)
+ /// \param key may contain embedded nulls.
+ /// \deprecated
+ Value removeMember(const std::string& key);
+ /// Same as removeMember(const char* begin, const char* end, Value* removed),
+ /// but 'key' is null-terminated.
+ bool removeMember(const char* key, Value* removed);
+ /** \brief Remove the named map member.
+
+ Update 'removed' iff removed.
+ \param key may contain embedded nulls.
+ \return true iff removed (no exceptions)
+ */
+ bool removeMember(std::string const& key, Value* removed);
+ /// Same as removeMember(std::string const& key, Value* removed)
+ bool removeMember(const char* begin, const char* end, Value* removed);
+ /** \brief Remove the indexed array element.
+
+ O(n) expensive operations.
+ Update 'removed' iff removed.
+ \return true iff removed (no exceptions)
+ */
+ bool removeIndex(ArrayIndex i, Value* removed);
+
+ /// Return true if the object has a member named key.
+ /// \note 'key' must be null-terminated.
+ bool isMember(const char* key) const;
+ /// Return true if the object has a member named key.
+ /// \param key may contain embedded nulls.
+ bool isMember(const std::string& key) const;
+ /// Same as isMember(std::string const& key)const
+ bool isMember(const char* begin, const char* end) const;
+#ifdef JSON_USE_CPPTL
+ /// Return true if the object has a member named key.
+ bool isMember(const CppTL::ConstString& key) const;
+#endif
+
+ /// \brief Return a list of the member names.
+ ///
+ /// If null, return an empty list.
+ /// \pre type() is objectValue or nullValue
+ /// \post if type() was nullValue, it remains nullValue
+ Members getMemberNames() const;
+
+ //# ifdef JSON_USE_CPPTL
+ // EnumMemberNames enumMemberNames() const;
+ // EnumValues enumValues() const;
+ //# endif
+
+ /// \deprecated Always pass len.
+ JSONCPP_DEPRECATED("Use setComment(std::string const&) instead.")
+ void setComment(const char* comment, CommentPlacement placement);
+ /// Comments must be //... or /* ... */
+ void setComment(const char* comment, size_t len, CommentPlacement placement);
+ /// Comments must be //... or /* ... */
+ void setComment(const std::string& comment, CommentPlacement placement);
+ bool hasComment(CommentPlacement placement) const;
+ /// Include delimiters and embedded newlines.
+ std::string getComment(CommentPlacement placement) const;
+
+ std::string toStyledString() const;
+
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ iterator begin();
+ iterator end();
+
+private:
+ void initBasic(ValueType type, bool allocated = false);
+
+ Value& resolveReference(const char* key);
+ Value& resolveReference(const char* key, const char* end);
+
+ struct CommentInfo {
+ CommentInfo();
+ ~CommentInfo();
+
+ void setComment(const char* text, size_t len);
+
+ char* comment_;
+ };
+
+ // struct MemberNamesTransform
+ //{
+ // typedef const char *result_type;
+ // const char *operator()( const CZString &name ) const
+ // {
+ // return name.c_str();
+ // }
+ //};
+
+ union ValueHolder {
+ LargestInt int_;
+ LargestUInt uint_;
+ double real_;
+ bool bool_;
+ char* string_; // actually ptr to unsigned, followed by str, unless !allocated_
+ ObjectValues* map_;
+ } value_;
+ ValueType type_ : 8;
+ unsigned int allocated_ : 1; // Notes: if declared as bool, bitfield is useless.
+ // If not allocated_, string_ must be null-terminated.
+ CommentInfo* comments_;
+};
+
+/** \brief Experimental and untested: represents an element of the "path" to
+ * access a node.
+ */
+class JSON_API PathArgument {
+public:
+ friend class Path;
+
+ PathArgument();
+ PathArgument(ArrayIndex index);
+ PathArgument(const char* key);
+ PathArgument(const std::string& key);
+
+private:
+ enum Kind {
+ kindNone = 0,
+ kindIndex,
+ kindKey
+ };
+ std::string key_;
+ ArrayIndex index_;
+ Kind kind_;
+};
+
+/** \brief Experimental and untested: represents a "path" to access a node.
+ *
+ * Syntax:
+ * - "." => root node
+ * - ".[n]" => elements at index 'n' of root node (an array value)
+ * - ".name" => member named 'name' of root node (an object value)
+ * - ".name1.name2.name3"
+ * - ".[0][1][2].name1[3]"
+ * - ".%" => member name is provided as parameter
+ * - ".[%]" => index is provied as parameter
+ */
+class JSON_API Path {
+public:
+ Path(const std::string& path,
+ const PathArgument& a1 = PathArgument(),
+ const PathArgument& a2 = PathArgument(),
+ const PathArgument& a3 = PathArgument(),
+ const PathArgument& a4 = PathArgument(),
+ const PathArgument& a5 = PathArgument());
+
+ const Value& resolve(const Value& root) const;
+ Value resolve(const Value& root, const Value& defaultValue) const;
+ /// Creates the "path" to access the specified node and returns a reference on
+ /// the node.
+ Value& make(Value& root) const;
+
+private:
+ typedef std::vector<const PathArgument*> InArgs;
+ typedef std::vector<PathArgument> Args;
+
+ void makePath(const std::string& path, const InArgs& in);
+ void addPathInArg(const std::string& path,
+ const InArgs& in,
+ InArgs::const_iterator& itInArg,
+ PathArgument::Kind kind);
+ void invalidPath(const std::string& path, int location);
+
+ Args args_;
+};
+
+/** \brief base class for Value iterators.
+ *
+ */
+class JSON_API ValueIteratorBase {
+public:
+ typedef std::bidirectional_iterator_tag iterator_category;
+ typedef unsigned int size_t;
+ typedef int difference_type;
+ typedef ValueIteratorBase SelfType;
+
+ bool operator==(const SelfType& other) const { return isEqual(other); }
+
+ bool operator!=(const SelfType& other) const { return !isEqual(other); }
+
+ difference_type operator-(const SelfType& other) const {
+ return other.computeDistance(*this);
+ }
+
+ /// Return either the index or the member name of the referenced value as a
+ /// Value.
+ Value key() const;
+
+ /// Return the index of the referenced Value, or -1 if it is not an arrayValue.
+ UInt index() const;
+
+ /// Return the member name of the referenced Value, or "" if it is not an
+ /// objectValue.
+ /// \note Avoid `c_str()` on result, as embedded zeroes are possible.
+ std::string name() const;
+
+ /// Return the member name of the referenced Value. "" if it is not an
+ /// objectValue.
+ /// \deprecated This cannot be used for UTF-8 strings, since there can be embedded nulls.
+ JSONCPP_DEPRECATED("Use `key = name();` instead.")
+ char const* memberName() const;
+ /// Return the member name of the referenced Value, or NULL if it is not an
+ /// objectValue.
+ /// \note Better version than memberName(). Allows embedded nulls.
+ char const* memberName(char const** end) const;
+
+protected:
+ Value& deref() const;
+
+ void increment();
+
+ void decrement();
+
+ difference_type computeDistance(const SelfType& other) const;
+
+ bool isEqual(const SelfType& other) const;
+
+ void copy(const SelfType& other);
+
+private:
+ Value::ObjectValues::iterator current_;
+ // Indicates that iterator is for a null value.
+ bool isNull_;
+
+public:
+ // For some reason, BORLAND needs these at the end, rather
+ // than earlier. No idea why.
+ ValueIteratorBase();
+ explicit ValueIteratorBase(const Value::ObjectValues::iterator& current);
+};
+
+/** \brief const iterator for object and array value.
+ *
+ */
+class JSON_API ValueConstIterator : public ValueIteratorBase {
+ friend class Value;
+
+public:
+ typedef const Value value_type;
+ //typedef unsigned int size_t;
+ //typedef int difference_type;
+ typedef const Value& reference;
+ typedef const Value* pointer;
+ typedef ValueConstIterator SelfType;
+
+ ValueConstIterator();
+
+private:
+/*! \internal Use by Value to create an iterator.
+ */
+ explicit ValueConstIterator(const Value::ObjectValues::iterator& current);
+public:
+ SelfType& operator=(const ValueIteratorBase& other);
+
+ SelfType operator++(int) {
+ SelfType temp(*this);
+ ++*this;
+ return temp;
+ }
+
+ SelfType operator--(int) {
+ SelfType temp(*this);
+ --*this;
+ return temp;
+ }
+
+ SelfType& operator--() {
+ decrement();
+ return *this;
+ }
+
+ SelfType& operator++() {
+ increment();
+ return *this;
+ }
+
+ reference operator*() const { return deref(); }
+
+ pointer operator->() const { return &deref(); }
+};
+
+/** \brief Iterator for object and array value.
+ */
+class JSON_API ValueIterator : public ValueIteratorBase {
+ friend class Value;
+
+public:
+ typedef Value value_type;
+ typedef unsigned int size_t;
+ typedef int difference_type;
+ typedef Value& reference;
+ typedef Value* pointer;
+ typedef ValueIterator SelfType;
+
+ ValueIterator();
+ ValueIterator(const ValueConstIterator& other);
+ ValueIterator(const ValueIterator& other);
+
+private:
+/*! \internal Use by Value to create an iterator.
+ */
+ explicit ValueIterator(const Value::ObjectValues::iterator& current);
+public:
+ SelfType& operator=(const SelfType& other);
+
+ SelfType operator++(int) {
+ SelfType temp(*this);
+ ++*this;
+ return temp;
+ }
+
+ SelfType operator--(int) {
+ SelfType temp(*this);
+ --*this;
+ return temp;
+ }
+
+ SelfType& operator--() {
+ decrement();
+ return *this;
+ }
+
+ SelfType& operator++() {
+ increment();
+ return *this;
+ }
+
+ reference operator*() const { return deref(); }
+
+ pointer operator->() const { return &deref(); }
+};
+
+} // namespace Json
+
+
+namespace std {
+/// Specialize std::swap() for Json::Value.
+template<>
+inline void swap(Json::Value& a, Json::Value& b) { a.swap(b); }
+}
+
+
+#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+#pragma warning(pop)
+#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+
+#endif // CPPTL_JSON_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/value.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/reader.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef CPPTL_JSON_READER_H_INCLUDED
+#define CPPTL_JSON_READER_H_INCLUDED
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include "features.h"
+#include "value.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+#include <deque>
+#include <iosfwd>
+#include <stack>
+#include <string>
+#include <istream>
+
+// Disable warning C4251: <data member>: <type> needs to have dll-interface to
+// be used by...
+#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+#pragma warning(push)
+#pragma warning(disable : 4251)
+#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+
+namespace Json {
+
+/** \brief Unserialize a <a HREF="http://www.json.org">JSON</a> document into a
+ *Value.
+ *
+ * \deprecated Use CharReader and CharReaderBuilder.
+ */
+class JSON_API Reader {
+public:
+ typedef char Char;
+ typedef const Char* Location;
+
+ /** \brief Constructs a Reader allowing all features
+ * for parsing.
+ */
+ Reader();
+
+ /** \brief Constructs a Reader allowing the specified feature set
+ * for parsing.
+ */
+ Reader(const Features& features);
+
+ /** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a>
+ * document.
+ * \param document UTF-8 encoded string containing the document to read.
+ * \param root [out] Contains the root value of the document if it was
+ * successfully parsed.
+ * \param collectComments \c true to collect comment and allow writing them
+ * back during
+ * serialization, \c false to discard comments.
+ * This parameter is ignored if
+ * Features::allowComments_
+ * is \c false.
+ * \return \c true if the document was successfully parsed, \c false if an
+ * error occurred.
+ */
+ bool
+ parse(const std::string& document, Value& root, bool collectComments = true);
+
+ /** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a>
+ document.
+ * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the
+ document to read.
+ * \param endDoc Pointer on the end of the UTF-8 encoded string of the
+ document to read.
+ * Must be >= beginDoc.
+ * \param root [out] Contains the root value of the document if it was
+ * successfully parsed.
+ * \param collectComments \c true to collect comment and allow writing them
+ back during
+ * serialization, \c false to discard comments.
+ * This parameter is ignored if
+ Features::allowComments_
+ * is \c false.
+ * \return \c true if the document was successfully parsed, \c false if an
+ error occurred.
+ */
+ bool parse(const char* beginDoc,
+ const char* endDoc,
+ Value& root,
+ bool collectComments = true);
+
+ /// \brief Parse from input stream.
+ /// \see Json::operator>>(std::istream&, Json::Value&).
+ bool parse(std::istream& is, Value& root, bool collectComments = true);
+
+ /** \brief Returns a user friendly string that list errors in the parsed
+ * document.
+ * \return Formatted error message with the list of errors with their location
+ * in
+ * the parsed document. An empty string is returned if no error
+ * occurred
+ * during parsing.
+ * \deprecated Use getFormattedErrorMessages() instead (typo fix).
+ */
+ JSONCPP_DEPRECATED("Use getFormattedErrorMessages() instead.")
+ std::string getFormatedErrorMessages() const;
+
+ /** \brief Returns a user friendly string that list errors in the parsed
+ * document.
+ * \return Formatted error message with the list of errors with their location
+ * in
+ * the parsed document. An empty string is returned if no error
+ * occurred
+ * during parsing.
+ */
+ std::string getFormattedErrorMessages() const;
+
+private:
+ enum TokenType {
+ tokenEndOfStream = 0,
+ tokenObjectBegin,
+ tokenObjectEnd,
+ tokenArrayBegin,
+ tokenArrayEnd,
+ tokenString,
+ tokenNumber,
+ tokenTrue,
+ tokenFalse,
+ tokenNull,
+ tokenArraySeparator,
+ tokenMemberSeparator,
+ tokenComment,
+ tokenError
+ };
+
+ class Token {
+ public:
+ TokenType type_;
+ Location start_;
+ Location end_;
+ };
+
+ class ErrorInfo {
+ public:
+ Token token_;
+ std::string message_;
+ Location extra_;
+ };
+
+ typedef std::deque<ErrorInfo> Errors;
+
+ bool readToken(Token& token);
+ void skipSpaces();
+ bool match(Location pattern, int patternLength);
+ bool readComment();
+ bool readCStyleComment();
+ bool readCppStyleComment();
+ bool readString();
+ void readNumber();
+ bool readValue();
+ bool readObject(Token& token);
+ bool readArray(Token& token);
+ bool decodeNumber(Token& token);
+ bool decodeNumber(Token& token, Value& decoded);
+ bool decodeString(Token& token);
+ bool decodeString(Token& token, std::string& decoded);
+ bool decodeDouble(Token& token);
+ bool decodeDouble(Token& token, Value& decoded);
+ bool decodeUnicodeCodePoint(Token& token,
+ Location& current,
+ Location end,
+ unsigned int& unicode);
+ bool decodeUnicodeEscapeSequence(Token& token,
+ Location& current,
+ Location end,
+ unsigned int& unicode);
+ bool addError(const std::string& message, Token& token, Location extra = 0);
+ bool recoverFromError(TokenType skipUntilToken);
+ bool addErrorAndRecover(const std::string& message,
+ Token& token,
+ TokenType skipUntilToken);
+ void skipUntilSpace();
+ Value& currentValue();
+ Char getNextChar();
+ void
+ getLocationLineAndColumn(Location location, int& line, int& column) const;
+ std::string getLocationLineAndColumn(Location location) const;
+ void addComment(Location begin, Location end, CommentPlacement placement);
+ void skipCommentTokens(Token& token);
+
+ typedef std::stack<Value*> Nodes;
+ Nodes nodes_;
+ Errors errors_;
+ std::string document_;
+ Location begin_;
+ Location end_;
+ Location current_;
+ Location lastValueEnd_;
+ Value* lastValue_;
+ std::string commentsBefore_;
+ Features features_;
+ bool collectComments_;
+}; // Reader
+
+/** Interface for reading JSON from a char array.
+ */
+class JSON_API CharReader {
+public:
+ virtual ~CharReader() {}
+ /** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a>
+ document.
+ * The document must be a UTF-8 encoded string containing the document to read.
+ *
+ * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the
+ document to read.
+ * \param endDoc Pointer on the end of the UTF-8 encoded string of the
+ document to read.
+ * Must be >= beginDoc.
+ * \param root [out] Contains the root value of the document if it was
+ * successfully parsed.
+ * \param errs [out] Formatted error messages (if not NULL)
+ * a user friendly string that lists errors in the parsed
+ * document.
+ * \return \c true if the document was successfully parsed, \c false if an
+ error occurred.
+ */
+ virtual bool parse(
+ char const* beginDoc, char const* endDoc,
+ Value* root, std::string* errs) = 0;
+
+ class Factory {
+ public:
+ virtual ~Factory() {}
+ /** \brief Allocate a CharReader via operator new().
+ * \throw std::exception if something goes wrong (e.g. invalid settings)
+ */
+ virtual CharReader* newCharReader() const = 0;
+ }; // Factory
+}; // CharReader
+
+/** \brief Build a CharReader implementation.
+
+Usage:
+\code
+ using namespace Json;
+ CharReaderBuilder builder;
+ builder["collectComments"] = false;
+ Value value;
+ std::string errs;
+ bool ok = parseFromStream(builder, std::cin, &value, &errs);
+\endcode
+*/
+class JSON_API CharReaderBuilder : public CharReader::Factory {
+public:
+ // Note: We use a Json::Value so that we can add data-members to this class
+ // without a major version bump.
+ /** Configuration of this builder.
+ These are case-sensitive.
+ Available settings (case-sensitive):
+ - `"collectComments": false or true`
+ - true to collect comment and allow writing them
+ back during serialization, false to discard comments.
+ This parameter is ignored if allowComments is false.
+ - `"allowComments": false or true`
+ - true if comments are allowed.
+ - `"strictRoot": false or true`
+ - true if root must be either an array or an object value
+ - `"allowDroppedNullPlaceholders": false or true`
+ - true if dropped null placeholders are allowed. (See StreamWriterBuilder.)
+ - `"allowNumericKeys": false or true`
+ - true if numeric object keys are allowed.
+ - `"allowSingleQuotes": false or true`
+ - true if '' are allowed for strings (both keys and values)
+ - `"stackLimit": integer`
+ - Exceeding stackLimit (recursive depth of `readValue()`) will
+ cause an exception.
+ - This is a security issue (seg-faults caused by deeply nested JSON),
+ so the default is low.
+ - `"failIfExtra": false or true`
+ - If true, `parse()` returns false when extra non-whitespace trails
+ the JSON value in the input string.
+ - `"rejectDupKeys": false or true`
+ - If true, `parse()` returns false when a key is duplicated within an object.
+ - `"allowSpecialFloats": false or true`
+ - If true, special float values (NaNs and infinities) are allowed
+ and their values are lossfree restorable.
+
+ You can examine 'settings_` yourself
+ to see the defaults. You can also write and read them just like any
+ JSON Value.
+ \sa setDefaults()
+ */
+ Json::Value settings_;
+
+ CharReaderBuilder();
+ virtual ~CharReaderBuilder();
+
+ virtual CharReader* newCharReader() const;
+
+ /** \return true if 'settings' are legal and consistent;
+ * otherwise, indicate bad settings via 'invalid'.
+ */
+ bool validate(Json::Value* invalid) const;
+
+ /** A simple way to update a specific setting.
+ */
+ Value& operator[](std::string key);
+
+ /** Called by ctor, but you can use this to reset settings_.
+ * \pre 'settings' != NULL (but Json::null is fine)
+ * \remark Defaults:
+ * \snippet src/lib_json/json_reader.cpp CharReaderBuilderDefaults
+ */
+ static void setDefaults(Json::Value* settings);
+ /** Same as old Features::strictMode().
+ * \pre 'settings' != NULL (but Json::null is fine)
+ * \remark Defaults:
+ * \snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode
+ */
+ static void strictMode(Json::Value* settings);
+};
+
+/** Consume entire stream and use its begin/end.
+ * Someday we might have a real StreamReader, but for now this
+ * is convenient.
+ */
+bool JSON_API parseFromStream(
+ CharReader::Factory const&,
+ std::istream&,
+ Value* root, std::string* errs);
+
+/** \brief Read from 'sin' into 'root'.
+
+ Always keep comments from the input JSON.
+
+ This can be used to read a file into a particular sub-object.
+ For example:
+ \code
+ Json::Value root;
+ cin >> root["dir"]["file"];
+ cout << root;
+ \endcode
+ Result:
+ \verbatim
+ {
+ "dir": {
+ "file": {
+ // The input stream JSON would be nested here.
+ }
+ }
+ }
+ \endverbatim
+ \throw std::exception on parse error.
+ \see Json::operator<<()
+*/
+JSON_API std::istream& operator>>(std::istream&, Value&);
+
+} // namespace Json
+
+#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+#pragma warning(pop)
+#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+
+#endif // CPPTL_JSON_READER_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/reader.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/writer.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef JSON_WRITER_H_INCLUDED
+#define JSON_WRITER_H_INCLUDED
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include "value.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+#include <vector>
+#include <string>
+#include <ostream>
+
+// Disable warning C4251: <data member>: <type> needs to have dll-interface to
+// be used by...
+#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+#pragma warning(push)
+#pragma warning(disable : 4251)
+#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+
+namespace Json {
+
+class Value;
+
+/**
+
+Usage:
+\code
+ using namespace Json;
+ void writeToStdout(StreamWriter::Factory const& factory, Value const& value) {
+ std::unique_ptr<StreamWriter> const writer(
+ factory.newStreamWriter());
+ writer->write(value, &std::cout);
+ std::cout << std::endl; // add lf and flush
+ }
+\endcode
+*/
+class JSON_API StreamWriter {
+protected:
+ std::ostream* sout_; // not owned; will not delete
+public:
+ StreamWriter();
+ virtual ~StreamWriter();
+ /** Write Value into document as configured in sub-class.
+ Do not take ownership of sout, but maintain a reference during function.
+ \pre sout != NULL
+ \return zero on success (For now, we always return zero, so check the stream instead.)
+ \throw std::exception possibly, depending on configuration
+ */
+ virtual int write(Value const& root, std::ostream* sout) = 0;
+
+ /** \brief A simple abstract factory.
+ */
+ class JSON_API Factory {
+ public:
+ virtual ~Factory();
+ /** \brief Allocate a CharReader via operator new().
+ * \throw std::exception if something goes wrong (e.g. invalid settings)
+ */
+ virtual StreamWriter* newStreamWriter() const = 0;
+ }; // Factory
+}; // StreamWriter
+
+/** \brief Write into stringstream, then return string, for convenience.
+ * A StreamWriter will be created from the factory, used, and then deleted.
+ */
+std::string JSON_API writeString(StreamWriter::Factory const& factory, Value const& root);
+
+
+/** \brief Build a StreamWriter implementation.
+
+Usage:
+\code
+ using namespace Json;
+ Value value = ...;
+ StreamWriterBuilder builder;
+ builder["commentStyle"] = "None";
+ builder["indentation"] = " "; // or whatever you like
+ std::unique_ptr<Json::StreamWriter> writer(
+ builder.newStreamWriter());
+ writer->write(value, &std::cout);
+ std::cout << std::endl; // add lf and flush
+\endcode
+*/
+class JSON_API StreamWriterBuilder : public StreamWriter::Factory {
+public:
+ // Note: We use a Json::Value so that we can add data-members to this class
+ // without a major version bump.
+ /** Configuration of this builder.
+ Available settings (case-sensitive):
+ - "commentStyle": "None" or "All"
+ - "indentation": "<anything>"
+ - "enableYAMLCompatibility": false or true
+ - slightly change the whitespace around colons
+ - "dropNullPlaceholders": false or true
+ - Drop the "null" string from the writer's output for nullValues.
+ Strictly speaking, this is not valid JSON. But when the output is being
+ fed to a browser's Javascript, it makes for smaller output and the
+ browser can handle the output just fine.
+ - "useSpecialFloats": false or true
+ - If true, outputs non-finite floating point values in the following way:
+ NaN values as "NaN", positive infinity as "Infinity", and negative infinity
+ as "-Infinity".
+
+ You can examine 'settings_` yourself
+ to see the defaults. You can also write and read them just like any
+ JSON Value.
+ \sa setDefaults()
+ */
+ Json::Value settings_;
+
+ StreamWriterBuilder();
+ virtual ~StreamWriterBuilder();
+
+ /**
+ * \throw std::exception if something goes wrong (e.g. invalid settings)
+ */
+ virtual StreamWriter* newStreamWriter() const;
+
+ /** \return true if 'settings' are legal and consistent;
+ * otherwise, indicate bad settings via 'invalid'.
+ */
+ bool validate(Json::Value* invalid) const;
+ /** A simple way to update a specific setting.
+ */
+ Value& operator[](std::string key);
+
+ /** Called by ctor, but you can use this to reset settings_.
+ * \pre 'settings' != NULL (but Json::null is fine)
+ * \remark Defaults:
+ * \snippet src/lib_json/json_writer.cpp StreamWriterBuilderDefaults
+ */
+ static void setDefaults(Json::Value* settings);
+};
+
+/** \brief Abstract class for writers.
+ * \deprecated Use StreamWriter. (And really, this is an implementation detail.)
+ */
+class JSON_API Writer {
+public:
+ virtual ~Writer();
+
+ virtual std::string write(const Value& root) = 0;
+};
+
+/** \brief Outputs a Value in <a HREF="http://www.json.org">JSON</a> format
+ *without formatting (not human friendly).
+ *
+ * The JSON document is written in a single line. It is not intended for 'human'
+ *consumption,
+ * but may be usefull to support feature such as RPC where bandwith is limited.
+ * \sa Reader, Value
+ * \deprecated Use StreamWriterBuilder.
+ */
+class JSON_API FastWriter : public Writer {
+
+public:
+ FastWriter();
+ virtual ~FastWriter() {}
+
+ void enableYAMLCompatibility();
+
+public: // overridden from Writer
+ virtual std::string write(const Value& root);
+
+private:
+ void writeValue(const Value& value);
+
+ std::string document_;
+ bool yamlCompatiblityEnabled_;
+};
+
+/** \brief Writes a Value in <a HREF="http://www.json.org">JSON</a> format in a
+ *human friendly way.
+ *
+ * The rules for line break and indent are as follow:
+ * - Object value:
+ * - if empty then print {} without indent and line break
+ * - if not empty the print '{', line break & indent, print one value per
+ *line
+ * and then unindent and line break and print '}'.
+ * - Array value:
+ * - if empty then print [] without indent and line break
+ * - if the array contains no object value, empty array or some other value
+ *types,
+ * and all the values fit on one lines, then print the array on a single
+ *line.
+ * - otherwise, it the values do not fit on one line, or the array contains
+ * object or non empty array, then print one value per line.
+ *
+ * If the Value have comments then they are outputed according to their
+ *#CommentPlacement.
+ *
+ * \sa Reader, Value, Value::setComment()
+ * \deprecated Use StreamWriterBuilder.
+ */
+class JSON_API StyledWriter : public Writer {
+public:
+ StyledWriter();
+ virtual ~StyledWriter() {}
+
+public: // overridden from Writer
+ /** \brief Serialize a Value in <a HREF="http://www.json.org">JSON</a> format.
+ * \param root Value to serialize.
+ * \return String containing the JSON document that represents the root value.
+ */
+ virtual std::string write(const Value& root);
+
+private:
+ void writeValue(const Value& value);
+ void writeArrayValue(const Value& value);
+ bool isMultineArray(const Value& value);
+ void pushValue(const std::string& value);
+ void writeIndent();
+ void writeWithIndent(const std::string& value);
+ void indent();
+ void unindent();
+ void writeCommentBeforeValue(const Value& root);
+ void writeCommentAfterValueOnSameLine(const Value& root);
+ bool hasCommentForValue(const Value& value);
+ static std::string normalizeEOL(const std::string& text);
+
+ typedef std::vector<std::string> ChildValues;
+
+ ChildValues childValues_;
+ std::string document_;
+ std::string indentString_;
+ int rightMargin_;
+ int indentSize_;
+ bool addChildValues_;
+};
+
+/** \brief Writes a Value in <a HREF="http://www.json.org">JSON</a> format in a
+ human friendly way,
+ to a stream rather than to a string.
+ *
+ * The rules for line break and indent are as follow:
+ * - Object value:
+ * - if empty then print {} without indent and line break
+ * - if not empty the print '{', line break & indent, print one value per
+ line
+ * and then unindent and line break and print '}'.
+ * - Array value:
+ * - if empty then print [] without indent and line break
+ * - if the array contains no object value, empty array or some other value
+ types,
+ * and all the values fit on one lines, then print the array on a single
+ line.
+ * - otherwise, it the values do not fit on one line, or the array contains
+ * object or non empty array, then print one value per line.
+ *
+ * If the Value have comments then they are outputed according to their
+ #CommentPlacement.
+ *
+ * \param indentation Each level will be indented by this amount extra.
+ * \sa Reader, Value, Value::setComment()
+ * \deprecated Use StreamWriterBuilder.
+ */
+class JSON_API StyledStreamWriter {
+public:
+ StyledStreamWriter(std::string indentation = "\t");
+ ~StyledStreamWriter() {}
+
+public:
+ /** \brief Serialize a Value in <a HREF="http://www.json.org">JSON</a> format.
+ * \param out Stream to write to. (Can be ostringstream, e.g.)
+ * \param root Value to serialize.
+ * \note There is no point in deriving from Writer, since write() should not
+ * return a value.
+ */
+ void write(std::ostream& out, const Value& root);
+
+private:
+ void writeValue(const Value& value);
+ void writeArrayValue(const Value& value);
+ bool isMultineArray(const Value& value);
+ void pushValue(const std::string& value);
+ void writeIndent();
+ void writeWithIndent(const std::string& value);
+ void indent();
+ void unindent();
+ void writeCommentBeforeValue(const Value& root);
+ void writeCommentAfterValueOnSameLine(const Value& root);
+ bool hasCommentForValue(const Value& value);
+ static std::string normalizeEOL(const std::string& text);
+
+ typedef std::vector<std::string> ChildValues;
+
+ ChildValues childValues_;
+ std::ostream* document_;
+ std::string indentString_;
+ int rightMargin_;
+ std::string indentation_;
+ bool addChildValues_ : 1;
+ bool indented_ : 1;
+};
+
+#if defined(JSON_HAS_INT64)
+std::string JSON_API valueToString(Int value);
+std::string JSON_API valueToString(UInt value);
+#endif // if defined(JSON_HAS_INT64)
+std::string JSON_API valueToString(LargestInt value);
+std::string JSON_API valueToString(LargestUInt value);
+std::string JSON_API valueToString(double value);
+std::string JSON_API valueToString(bool value);
+std::string JSON_API valueToQuotedString(const char* value);
+
+/// \brief Output using the StyledStreamWriter.
+/// \see Json::operator>>()
+JSON_API std::ostream& operator<<(std::ostream&, const Value& root);
+
+} // namespace Json
+
+#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+#pragma warning(pop)
+#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
+
+#endif // JSON_WRITER_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/writer.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: include/json/assertions.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef CPPTL_JSON_ASSERTIONS_H_INCLUDED
+#define CPPTL_JSON_ASSERTIONS_H_INCLUDED
+
+#include <stdlib.h>
+#include <sstream>
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include "config.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+
+/** It should not be possible for a maliciously designed file to
+ * cause an abort() or seg-fault, so these macros are used only
+ * for pre-condition violations and internal logic errors.
+ */
+#if JSON_USE_EXCEPTION
+
+// @todo <= add detail about condition in exception
+# define JSON_ASSERT(condition) \
+ {if (!(condition)) {Json::throwLogicError( "assert json failed" );}}
+
+# define JSON_FAIL_MESSAGE(message) \
+ { \
+ std::ostringstream oss; oss << message; \
+ Json::throwLogicError(oss.str()); \
+ abort(); \
+ }
+
+#else // JSON_USE_EXCEPTION
+
+# define JSON_ASSERT(condition) assert(condition)
+
+// The call to assert() will show the failure message in debug builds. In
+// release builds we abort, for a core-dump or debugger.
+# define JSON_FAIL_MESSAGE(message) \
+ { \
+ std::ostringstream oss; oss << message; \
+ assert(false && oss.str().c_str()); \
+ abort(); \
+ }
+
+
+#endif
+
+#define JSON_ASSERT_MESSAGE(condition, message) \
+ if (!(condition)) { \
+ JSON_FAIL_MESSAGE(message); \
+ }
+
+#endif // CPPTL_JSON_ASSERTIONS_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: include/json/assertions.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+#endif //ifndef JSON_AMALGATED_H_INCLUDED
diff --git a/lib/jsoncpp/jsoncpp.cpp b/lib/jsoncpp/jsoncpp.cpp
new file mode 100644
index 000000000..618191612
--- /dev/null
+++ b/lib/jsoncpp/jsoncpp.cpp
@@ -0,0 +1,4937 @@
+/// Json-cpp amalgated source (http://jsoncpp.sourceforge.net/).
+/// It is intended to be used with #include "json/json.h"
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: LICENSE
+// //////////////////////////////////////////////////////////////////////
+
+/*
+The JsonCpp library's source code, including accompanying documentation,
+tests and demonstration applications, are licensed under the following
+conditions...
+
+The author (Baptiste Lepilleur) explicitly disclaims copyright in all
+jurisdictions which recognize such a disclaimer. In such jurisdictions,
+this software is released into the Public Domain.
+
+In jurisdictions which do not recognize Public Domain property (e.g. Germany as of
+2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur, and is
+released under the terms of the MIT License (see below).
+
+In jurisdictions which recognize Public Domain property, the user of this
+software may choose to accept it either as 1) Public Domain, 2) under the
+conditions of the MIT License (see below), or 3) under the terms of dual
+Public Domain/MIT License conditions described here, as they choose.
+
+The MIT License is about as close to Public Domain as a license can get, and is
+described in clear, concise terms at:
+
+ http://en.wikipedia.org/wiki/MIT_License
+
+The full text of the MIT License follows:
+
+========================================================================
+Copyright (c) 2007-2010 Baptiste Lepilleur
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use, copy,
+modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+========================================================================
+(END LICENSE TEXT)
+
+The MIT license is compatible with both the GPL and commercial
+software, affording one all of the rights of Public Domain with the
+minor nuisance of being required to keep the above copyright notice
+and license text in the source code. Note also that by accepting the
+Public Domain "license" you can re-license your copy using whatever
+license you like.
+
+*/
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: LICENSE
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+#include "json/json.h"
+
+#ifndef JSON_IS_AMALGAMATION
+#error "Compile with -I PATH_TO_JSON_DIRECTORY"
+#endif
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: src/lib_json/json_tool.h
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#ifndef LIB_JSONCPP_JSON_TOOL_H_INCLUDED
+#define LIB_JSONCPP_JSON_TOOL_H_INCLUDED
+
+/* This header provides common string manipulation support, such as UTF-8,
+ * portable conversion from/to string...
+ *
+ * It is an internal header that must not be exposed.
+ */
+
+namespace Json {
+
+/// Converts a unicode code-point to UTF-8.
+static inline std::string codePointToUTF8(unsigned int cp) {
+ std::string result;
+
+ // based on description from http://en.wikipedia.org/wiki/UTF-8
+
+ if (cp <= 0x7f) {
+ result.resize(1);
+ result[0] = static_cast<char>(cp);
+ } else if (cp <= 0x7FF) {
+ result.resize(2);
+ result[1] = static_cast<char>(0x80 | (0x3f & cp));
+ result[0] = static_cast<char>(0xC0 | (0x1f & (cp >> 6)));
+ } else if (cp <= 0xFFFF) {
+ result.resize(3);
+ result[2] = static_cast<char>(0x80 | (0x3f & cp));
+ result[1] = static_cast<char>(0x80 | (0x3f & (cp >> 6)));
+ result[0] = static_cast<char>(0xE0 | (0xf & (cp >> 12)));
+ } else if (cp <= 0x10FFFF) {
+ result.resize(4);
+ result[3] = static_cast<char>(0x80 | (0x3f & cp));
+ result[2] = static_cast<char>(0x80 | (0x3f & (cp >> 6)));
+ result[1] = static_cast<char>(0x80 | (0x3f & (cp >> 12)));
+ result[0] = static_cast<char>(0xF0 | (0x7 & (cp >> 18)));
+ }
+
+ return result;
+}
+
+/// Returns true if ch is a control character (in range [1,31]).
+static inline bool isControlCharacter(char ch) { return ch > 0 && ch <= 0x1F; }
+
+enum {
+ /// Constant that specify the size of the buffer that must be passed to
+ /// uintToString.
+ uintToStringBufferSize = 3 * sizeof(LargestUInt) + 1
+};
+
+// Defines a char buffer for use with uintToString().
+typedef char UIntToStringBuffer[uintToStringBufferSize];
+
+/** Converts an unsigned integer to string.
+ * @param value Unsigned interger to convert to string
+ * @param current Input/Output string buffer.
+ * Must have at least uintToStringBufferSize chars free.
+ */
+static inline void uintToString(LargestUInt value, char*& current) {
+ *--current = 0;
+ do {
+ *--current = static_cast<signed char>(value % 10U + static_cast<unsigned>('0'));
+ value /= 10;
+ } while (value != 0);
+}
+
+/** Change ',' to '.' everywhere in buffer.
+ *
+ * We had a sophisticated way, but it did not work in WinCE.
+ * @see https://github.com/open-source-parsers/jsoncpp/pull/9
+ */
+static inline void fixNumericLocale(char* begin, char* end) {
+ while (begin < end) {
+ if (*begin == ',') {
+ *begin = '.';
+ }
+ ++begin;
+ }
+}
+
+} // namespace Json {
+
+#endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: src/lib_json/json_tool.h
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: src/lib_json/json_reader.cpp
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2011 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include <json/assertions.h>
+#include <json/reader.h>
+#include <json/value.h>
+#include "json_tool.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+#include <utility>
+#include <cstdio>
+#include <cassert>
+#include <cstring>
+#include <istream>
+#include <sstream>
+#include <memory>
+#include <set>
+#include <limits>
+
+#if defined(_MSC_VER)
+#if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above
+#define snprintf sprintf_s
+#elif _MSC_VER >= 1900 // VC++ 14.0 and above
+#define snprintf std::snprintf
+#else
+#define snprintf _snprintf
+#endif
+#elif defined(__ANDROID__)
+#define snprintf snprintf
+#elif __cplusplus >= 201103L
+#define snprintf std::snprintf
+#endif
+
+#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0
+// Disable warning about strdup being deprecated.
+#pragma warning(disable : 4996)
+#endif
+
+static int const stackLimit_g = 1000;
+static int stackDepth_g = 0; // see readValue()
+
+namespace Json {
+
+typedef std::auto_ptr<CharReader> CharReaderPtr;
+
+// Implementation of class Features
+// ////////////////////////////////
+
+Features::Features()
+ : allowComments_(true), strictRoot_(false)
+{}
+Features Features::all() { return Features(); }
+
+Features Features::strictMode() {
+ Features features;
+ features.allowComments_ = false;
+ features.strictRoot_ = true;
+ return features;
+}
+
+// Implementation of class Reader
+// ////////////////////////////////
+
+static bool containsNewLine(Reader::Location begin, Reader::Location end) {
+ for (; begin < end; ++begin)
+ if (*begin == '\n' || *begin == '\r')
+ return true;
+ return false;
+}
+
+// Class Reader
+// //////////////////////////////////////////////////////////////////
+
+Reader::Reader()
+ : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(),
+ lastValue_(), commentsBefore_(), features_(Features::all()),
+ collectComments_() {}
+
+Reader::Reader(const Features& features)
+ : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(),
+ lastValue_(), commentsBefore_(), features_(features), collectComments_() {
+}
+
+bool
+Reader::parse(const std::string& document, Value& root, bool collectComments) {
+ document_ = document;
+ const char* begin = document_.c_str();
+ const char* end = begin + document_.length();
+ return parse(begin, end, root, collectComments);
+}
+
+bool Reader::parse(std::istream& sin, Value& root, bool collectComments) {
+ // std::istream_iterator<char> begin(sin);
+ // std::istream_iterator<char> end;
+ // Those would allow streamed input from a file, if parse() were a
+ // template function.
+
+ // Since std::string is reference-counted, this at least does not
+ // create an extra copy.
+ std::string doc;
+ std::getline(sin, doc, (char)EOF);
+ return parse(doc, root, collectComments);
+}
+
+bool Reader::parse(const char* beginDoc,
+ const char* endDoc,
+ Value& root,
+ bool collectComments) {
+ if (!features_.allowComments_) {
+ collectComments = false;
+ }
+
+ begin_ = beginDoc;
+ end_ = endDoc;
+ collectComments_ = collectComments;
+ current_ = begin_;
+ lastValueEnd_ = 0;
+ lastValue_ = 0;
+ commentsBefore_ = "";
+ errors_.clear();
+ while (!nodes_.empty())
+ nodes_.pop();
+ nodes_.push(&root);
+
+ stackDepth_g = 0; // Yes, this is bad coding, but options are limited.
+ bool successful = readValue();
+ Token token;
+ skipCommentTokens(token);
+ if (collectComments_ && !commentsBefore_.empty())
+ root.setComment(commentsBefore_, commentAfter);
+ if (features_.strictRoot_) {
+ if (!root.isArray() && !root.isObject()) {
+ // Set error location to start of doc, ideally should be first token found
+ // in doc
+ token.type_ = tokenError;
+ token.start_ = beginDoc;
+ token.end_ = endDoc;
+ addError(
+ "A valid JSON document must be either an array or an object value.",
+ token);
+ return false;
+ }
+ }
+ return successful;
+}
+
+bool Reader::readValue() {
+ // This is a non-reentrant way to support a stackLimit. Terrible!
+ // But this deprecated class has a security problem: Bad input can
+ // cause a seg-fault. This seems like a fair, binary-compatible way
+ // to prevent the problem.
+ if (stackDepth_g >= stackLimit_g) throwRuntimeError("Exceeded stackLimit in readValue().");
+ ++stackDepth_g;
+
+ Token token;
+ skipCommentTokens(token);
+ bool successful = true;
+
+ if (collectComments_ && !commentsBefore_.empty()) {
+ currentValue().setComment(commentsBefore_, commentBefore);
+ commentsBefore_ = "";
+ }
+
+ switch (token.type_) {
+ case tokenObjectBegin:
+ successful = readObject(token);
+ break;
+ case tokenArrayBegin:
+ successful = readArray(token);
+ break;
+ case tokenNumber:
+ successful = decodeNumber(token);
+ break;
+ case tokenString:
+ successful = decodeString(token);
+ break;
+ case tokenTrue:
+ {
+ Value v(true);
+ currentValue().swapPayload(v);
+ }
+ break;
+ case tokenFalse:
+ {
+ Value v(false);
+ currentValue().swapPayload(v);
+ }
+ break;
+ case tokenNull:
+ {
+ Value v;
+ currentValue().swapPayload(v);
+ }
+ break;
+ // Else, fall through...
+ default:
+ return addError("Syntax error: value, object or array expected.", token);
+ }
+
+ if (collectComments_) {
+ lastValueEnd_ = current_;
+ lastValue_ = &currentValue();
+ }
+
+ --stackDepth_g;
+ return successful;
+}
+
+void Reader::skipCommentTokens(Token& token) {
+ if (features_.allowComments_) {
+ do {
+ readToken(token);
+ } while (token.type_ == tokenComment);
+ } else {
+ readToken(token);
+ }
+}
+
+bool Reader::readToken(Token& token) {
+ skipSpaces();
+ token.start_ = current_;
+ Char c = getNextChar();
+ bool ok = true;
+ switch (c) {
+ case '{':
+ token.type_ = tokenObjectBegin;
+ break;
+ case '}':
+ token.type_ = tokenObjectEnd;
+ break;
+ case '[':
+ token.type_ = tokenArrayBegin;
+ break;
+ case ']':
+ token.type_ = tokenArrayEnd;
+ break;
+ case '"':
+ token.type_ = tokenString;
+ ok = readString();
+ break;
+ case '/':
+ token.type_ = tokenComment;
+ ok = readComment();
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '-':
+ token.type_ = tokenNumber;
+ readNumber();
+ break;
+ case 't':
+ token.type_ = tokenTrue;
+ ok = match("rue", 3);
+ break;
+ case 'f':
+ token.type_ = tokenFalse;
+ ok = match("alse", 4);
+ break;
+ case 'n':
+ token.type_ = tokenNull;
+ ok = match("ull", 3);
+ break;
+ case ',':
+ token.type_ = tokenArraySeparator;
+ break;
+ case ':':
+ token.type_ = tokenMemberSeparator;
+ break;
+ case 0:
+ token.type_ = tokenEndOfStream;
+ break;
+ default:
+ ok = false;
+ break;
+ }
+ if (!ok)
+ token.type_ = tokenError;
+ token.end_ = current_;
+ return true;
+}
+
+void Reader::skipSpaces() {
+ while (current_ != end_) {
+ Char c = *current_;
+ if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
+ ++current_;
+ else
+ break;
+ }
+}
+
+bool Reader::match(Location pattern, int patternLength) {
+ if (end_ - current_ < patternLength)
+ return false;
+ int index = patternLength;
+ while (index--)
+ if (current_[index] != pattern[index])
+ return false;
+ current_ += patternLength;
+ return true;
+}
+
+bool Reader::readComment() {
+ Location commentBegin = current_ - 1;
+ Char c = getNextChar();
+ bool successful = false;
+ if (c == '*')
+ successful = readCStyleComment();
+ else if (c == '/')
+ successful = readCppStyleComment();
+ if (!successful)
+ return false;
+
+ if (collectComments_) {
+ CommentPlacement placement = commentBefore;
+ if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) {
+ if (c != '*' || !containsNewLine(commentBegin, current_))
+ placement = commentAfterOnSameLine;
+ }
+
+ addComment(commentBegin, current_, placement);
+ }
+ return true;
+}
+
+static std::string normalizeEOL(Reader::Location begin, Reader::Location end) {
+ std::string normalized;
+ normalized.reserve(end - begin);
+ Reader::Location current = begin;
+ while (current != end) {
+ char c = *current++;
+ if (c == '\r') {
+ if (current != end && *current == '\n')
+ // convert dos EOL
+ ++current;
+ // convert Mac EOL
+ normalized += '\n';
+ } else {
+ normalized += c;
+ }
+ }
+ return normalized;
+}
+
+void
+Reader::addComment(Location begin, Location end, CommentPlacement placement) {
+ assert(collectComments_);
+ const std::string& normalized = normalizeEOL(begin, end);
+ if (placement == commentAfterOnSameLine) {
+ assert(lastValue_ != 0);
+ lastValue_->setComment(normalized, placement);
+ } else {
+ commentsBefore_ += normalized;
+ }
+}
+
+bool Reader::readCStyleComment() {
+ while (current_ != end_) {
+ Char c = getNextChar();
+ if (c == '*' && *current_ == '/')
+ break;
+ }
+ return getNextChar() == '/';
+}
+
+bool Reader::readCppStyleComment() {
+ while (current_ != end_) {
+ Char c = getNextChar();
+ if (c == '\n')
+ break;
+ if (c == '\r') {
+ // Consume DOS EOL. It will be normalized in addComment.
+ if (current_ != end_ && *current_ == '\n')
+ getNextChar();
+ // Break on Moc OS 9 EOL.
+ break;
+ }
+ }
+ return true;
+}
+
+void Reader::readNumber() {
+ const char *p = current_;
+ char c = '0'; // stopgap for already consumed character
+ // integral part
+ while (c >= '0' && c <= '9')
+ c = (current_ = p) < end_ ? *p++ : 0;
+ // fractional part
+ if (c == '.') {
+ c = (current_ = p) < end_ ? *p++ : 0;
+ while (c >= '0' && c <= '9')
+ c = (current_ = p) < end_ ? *p++ : 0;
+ }
+ // exponential part
+ if (c == 'e' || c == 'E') {
+ c = (current_ = p) < end_ ? *p++ : 0;
+ if (c == '+' || c == '-')
+ c = (current_ = p) < end_ ? *p++ : 0;
+ while (c >= '0' && c <= '9')
+ c = (current_ = p) < end_ ? *p++ : 0;
+ }
+}
+
+bool Reader::readString() {
+ Char c = 0;
+ while (current_ != end_) {
+ c = getNextChar();
+ if (c == '\\')
+ getNextChar();
+ else if (c == '"')
+ break;
+ }
+ return c == '"';
+}
+
+bool Reader::readObject(Token& /*tokenStart*/) {
+ Token tokenName;
+ std::string name;
+ Value init(objectValue);
+ currentValue().swapPayload(init);
+ while (readToken(tokenName)) {
+ bool initialTokenOk = true;
+ while (tokenName.type_ == tokenComment && initialTokenOk)
+ initialTokenOk = readToken(tokenName);
+ if (!initialTokenOk)
+ break;
+ if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object
+ return true;
+ name = "";
+ if (tokenName.type_ == tokenString) {
+ if (!decodeString(tokenName, name))
+ return recoverFromError(tokenObjectEnd);
+ } else {
+ break;
+ }
+
+ Token colon;
+ if (!readToken(colon) || colon.type_ != tokenMemberSeparator) {
+ return addErrorAndRecover(
+ "Missing ':' after object member name", colon, tokenObjectEnd);
+ }
+ Value& value = currentValue()[name];
+ nodes_.push(&value);
+ bool ok = readValue();
+ nodes_.pop();
+ if (!ok) // error already set
+ return recoverFromError(tokenObjectEnd);
+
+ Token comma;
+ if (!readToken(comma) ||
+ (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator &&
+ comma.type_ != tokenComment)) {
+ return addErrorAndRecover(
+ "Missing ',' or '}' in object declaration", comma, tokenObjectEnd);
+ }
+ bool finalizeTokenOk = true;
+ while (comma.type_ == tokenComment && finalizeTokenOk)
+ finalizeTokenOk = readToken(comma);
+ if (comma.type_ == tokenObjectEnd)
+ return true;
+ }
+ return addErrorAndRecover(
+ "Missing '}' or object member name", tokenName, tokenObjectEnd);
+}
+
+bool Reader::readArray(Token& /*tokenStart*/) {
+ Value init(arrayValue);
+ currentValue().swapPayload(init);
+ skipSpaces();
+ if (*current_ == ']') // empty array
+ {
+ Token endArray;
+ readToken(endArray);
+ return true;
+ }
+ int index = 0;
+ for (;;) {
+ Value& value = currentValue()[index++];
+ nodes_.push(&value);
+ bool ok = readValue();
+ nodes_.pop();
+ if (!ok) // error already set
+ return recoverFromError(tokenArrayEnd);
+
+ Token token;
+ // Accept Comment after last item in the array.
+ ok = readToken(token);
+ while (token.type_ == tokenComment && ok) {
+ ok = readToken(token);
+ }
+ bool badTokenType =
+ (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd);
+ if (!ok || badTokenType) {
+ return addErrorAndRecover(
+ "Missing ',' or ']' in array declaration", token, tokenArrayEnd);
+ }
+ if (token.type_ == tokenArrayEnd)
+ break;
+ }
+ return true;
+}
+
+bool Reader::decodeNumber(Token& token) {
+ Value decoded;
+ if (!decodeNumber(token, decoded))
+ return false;
+ currentValue().swapPayload(decoded);
+ return true;
+}
+
+bool Reader::decodeNumber(Token& token, Value& decoded) {
+ // Attempts to parse the number as an integer. If the number is
+ // larger than the maximum supported value of an integer then
+ // we decode the number as a double.
+ Location current = token.start_;
+ bool isNegative = *current == '-';
+ if (isNegative)
+ ++current;
+ // TODO: Help the compiler do the div and mod at compile time or get rid of them.
+ Value::LargestUInt maxIntegerValue =
+ isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1
+ : Value::maxLargestUInt;
+ Value::LargestUInt threshold = maxIntegerValue / 10;
+ Value::LargestUInt value = 0;
+ while (current < token.end_) {
+ Char c = *current++;
+ if (c < '0' || c > '9')
+ return decodeDouble(token, decoded);
+ Value::UInt digit(c - '0');
+ if (value >= threshold) {
+ // We've hit or exceeded the max value divided by 10 (rounded down). If
+ // a) we've only just touched the limit, b) this is the last digit, and
+ // c) it's small enough to fit in that rounding delta, we're okay.
+ // Otherwise treat this number as a double to avoid overflow.
+ if (value > threshold || current != token.end_ ||
+ digit > maxIntegerValue % 10) {
+ return decodeDouble(token, decoded);
+ }
+ }
+ value = value * 10 + digit;
+ }
+ if (isNegative && value == maxIntegerValue)
+ decoded = Value::minLargestInt;
+ else if (isNegative)
+ decoded = -Value::LargestInt(value);
+ else if (value <= Value::LargestUInt(Value::maxInt))
+ decoded = Value::LargestInt(value);
+ else
+ decoded = value;
+ return true;
+}
+
+bool Reader::decodeDouble(Token& token) {
+ Value decoded;
+ if (!decodeDouble(token, decoded))
+ return false;
+ currentValue().swapPayload(decoded);
+ return true;
+}
+
+bool Reader::decodeDouble(Token& token, Value& decoded) {
+ double value = 0;
+ std::string buffer(token.start_, token.end_);
+ std::istringstream is(buffer);
+ if (!(is >> value))
+ return addError("'" + std::string(token.start_, token.end_) +
+ "' is not a number.",
+ token);
+ decoded = value;
+ return true;
+}
+
+bool Reader::decodeString(Token& token) {
+ std::string decoded_string;
+ if (!decodeString(token, decoded_string))
+ return false;
+ Value decoded(decoded_string);
+ currentValue().swapPayload(decoded);
+ return true;
+}
+
+bool Reader::decodeString(Token& token, std::string& decoded) {
+ decoded.reserve(token.end_ - token.start_ - 2);
+ Location current = token.start_ + 1; // skip '"'
+ Location end = token.end_ - 1; // do not include '"'
+ while (current != end) {
+ Char c = *current++;
+ if (c == '"')
+ break;
+ else if (c == '\\') {
+ if (current == end)
+ return addError("Empty escape sequence in string", token, current);
+ Char escape = *current++;
+ switch (escape) {
+ case '"':
+ decoded += '"';
+ break;
+ case '/':
+ decoded += '/';
+ break;
+ case '\\':
+ decoded += '\\';
+ break;
+ case 'b':
+ decoded += '\b';
+ break;
+ case 'f':
+ decoded += '\f';
+ break;
+ case 'n':
+ decoded += '\n';
+ break;
+ case 'r':
+ decoded += '\r';
+ break;
+ case 't':
+ decoded += '\t';
+ break;
+ case 'u': {
+ unsigned int unicode;
+ if (!decodeUnicodeCodePoint(token, current, end, unicode))
+ return false;
+ decoded += codePointToUTF8(unicode);
+ } break;
+ default:
+ return addError("Bad escape sequence in string", token, current);
+ }
+ } else {
+ decoded += c;
+ }
+ }
+ return true;
+}
+
+bool Reader::decodeUnicodeCodePoint(Token& token,
+ Location& current,
+ Location end,
+ unsigned int& unicode) {
+
+ if (!decodeUnicodeEscapeSequence(token, current, end, unicode))
+ return false;
+ if (unicode >= 0xD800 && unicode <= 0xDBFF) {
+ // surrogate pairs
+ if (end - current < 6)
+ return addError(
+ "additional six characters expected to parse unicode surrogate pair.",
+ token,
+ current);
+ unsigned int surrogatePair;
+ if (*(current++) == '\\' && *(current++) == 'u') {
+ if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) {
+ unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF);
+ } else
+ return false;
+ } else
+ return addError("expecting another \\u token to begin the second half of "
+ "a unicode surrogate pair",
+ token,
+ current);
+ }
+ return true;
+}
+
+bool Reader::decodeUnicodeEscapeSequence(Token& token,
+ Location& current,
+ Location end,
+ unsigned int& unicode) {
+ if (end - current < 4)
+ return addError(
+ "Bad unicode escape sequence in string: four digits expected.",
+ token,
+ current);
+ unicode = 0;
+ for (int index = 0; index < 4; ++index) {
+ Char c = *current++;
+ unicode *= 16;
+ if (c >= '0' && c <= '9')
+ unicode += c - '0';
+ else if (c >= 'a' && c <= 'f')
+ unicode += c - 'a' + 10;
+ else if (c >= 'A' && c <= 'F')
+ unicode += c - 'A' + 10;
+ else
+ return addError(
+ "Bad unicode escape sequence in string: hexadecimal digit expected.",
+ token,
+ current);
+ }
+ return true;
+}
+
+bool
+Reader::addError(const std::string& message, Token& token, Location extra) {
+ ErrorInfo info;
+ info.token_ = token;
+ info.message_ = message;
+ info.extra_ = extra;
+ errors_.push_back(info);
+ return false;
+}
+
+bool Reader::recoverFromError(TokenType skipUntilToken) {
+ int errorCount = int(errors_.size());
+ Token skip;
+ for (;;) {
+ if (!readToken(skip))
+ errors_.resize(errorCount); // discard errors caused by recovery
+ if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream)
+ break;
+ }
+ errors_.resize(errorCount);
+ return false;
+}
+
+bool Reader::addErrorAndRecover(const std::string& message,
+ Token& token,
+ TokenType skipUntilToken) {
+ addError(message, token);
+ return recoverFromError(skipUntilToken);
+}
+
+Value& Reader::currentValue() { return *(nodes_.top()); }
+
+Reader::Char Reader::getNextChar() {
+ if (current_ == end_)
+ return 0;
+ return *current_++;
+}
+
+void Reader::getLocationLineAndColumn(Location location,
+ int& line,
+ int& column) const {
+ Location current = begin_;
+ Location lastLineStart = current;
+ line = 0;
+ while (current < location && current != end_) {
+ Char c = *current++;
+ if (c == '\r') {
+ if (*current == '\n')
+ ++current;
+ lastLineStart = current;
+ ++line;
+ } else if (c == '\n') {
+ lastLineStart = current;
+ ++line;
+ }
+ }
+ // column & line start at 1
+ column = int(location - lastLineStart) + 1;
+ ++line;
+}
+
+std::string Reader::getLocationLineAndColumn(Location location) const {
+ int line, column;
+ getLocationLineAndColumn(location, line, column);
+ char buffer[18 + 16 + 16 + 1];
+ snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column);
+ return buffer;
+}
+
+// Deprecated. Preserved for backward compatibility
+std::string Reader::getFormatedErrorMessages() const {
+ return getFormattedErrorMessages();
+}
+
+std::string Reader::getFormattedErrorMessages() const {
+ std::string formattedMessage;
+ for (Errors::const_iterator itError = errors_.begin();
+ itError != errors_.end();
+ ++itError) {
+ const ErrorInfo& error = *itError;
+ formattedMessage +=
+ "* " + getLocationLineAndColumn(error.token_.start_) + "\n";
+ formattedMessage += " " + error.message_ + "\n";
+ if (error.extra_)
+ formattedMessage +=
+ "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n";
+ }
+ return formattedMessage;
+}
+
+// Reader
+/////////////////////////
+
+// exact copy of Features
+class OurFeatures {
+public:
+ static OurFeatures all();
+ OurFeatures();
+ bool allowComments_;
+ bool strictRoot_;
+ bool allowDroppedNullPlaceholders_;
+ bool allowNumericKeys_;
+ bool allowSingleQuotes_;
+ bool failIfExtra_;
+ bool rejectDupKeys_;
+ bool allowSpecialFloats_;
+ int stackLimit_;
+}; // OurFeatures
+
+// exact copy of Implementation of class Features
+// ////////////////////////////////
+
+OurFeatures::OurFeatures()
+ : allowComments_(true), strictRoot_(false)
+ , allowDroppedNullPlaceholders_(false), allowNumericKeys_(false)
+ , allowSingleQuotes_(false)
+ , failIfExtra_(false)
+ , allowSpecialFloats_(false)
+{
+}
+
+OurFeatures OurFeatures::all() { return OurFeatures(); }
+
+// Implementation of class Reader
+// ////////////////////////////////
+
+// exact copy of Reader, renamed to OurReader
+class OurReader {
+public:
+ typedef char Char;
+ typedef const Char* Location;
+ struct StructuredError {
+ size_t offset_start;
+ size_t offset_limit;
+ std::string message;
+ };
+
+ OurReader(OurFeatures const& features);
+ bool parse(const char* beginDoc,
+ const char* endDoc,
+ Value& root,
+ bool collectComments = true);
+ std::string getFormattedErrorMessages() const;
+
+private:
+ OurReader(OurReader const&); // no impl
+ void operator=(OurReader const&); // no impl
+
+ enum TokenType {
+ tokenEndOfStream = 0,
+ tokenObjectBegin,
+ tokenObjectEnd,
+ tokenArrayBegin,
+ tokenArrayEnd,
+ tokenString,
+ tokenNumber,
+ tokenTrue,
+ tokenFalse,
+ tokenNull,
+ tokenNaN,
+ tokenPosInf,
+ tokenNegInf,
+ tokenArraySeparator,
+ tokenMemberSeparator,
+ tokenComment,
+ tokenError
+ };
+
+ class Token {
+ public:
+ TokenType type_;
+ Location start_;
+ Location end_;
+ };
+
+ class ErrorInfo {
+ public:
+ Token token_;
+ std::string message_;
+ Location extra_;
+ };
+
+ typedef std::deque<ErrorInfo> Errors;
+
+ bool readToken(Token& token);
+ void skipSpaces();
+ bool match(Location pattern, int patternLength);
+ bool readComment();
+ bool readCStyleComment();
+ bool readCppStyleComment();
+ bool readString();
+ bool readStringSingleQuote();
+ bool readNumber(bool checkInf);
+ bool readValue();
+ bool readObject(Token& token);
+ bool readArray(Token& token);
+ bool decodeNumber(Token& token);
+ bool decodeNumber(Token& token, Value& decoded);
+ bool decodeString(Token& token);
+ bool decodeString(Token& token, std::string& decoded);
+ bool decodeDouble(Token& token);
+ bool decodeDouble(Token& token, Value& decoded);
+ bool decodeUnicodeCodePoint(Token& token,
+ Location& current,
+ Location end,
+ unsigned int& unicode);
+ bool decodeUnicodeEscapeSequence(Token& token,
+ Location& current,
+ Location end,
+ unsigned int& unicode);
+ bool addError(const std::string& message, Token& token, Location extra = 0);
+ bool recoverFromError(TokenType skipUntilToken);
+ bool addErrorAndRecover(const std::string& message,
+ Token& token,
+ TokenType skipUntilToken);
+ void skipUntilSpace();
+ Value& currentValue();
+ Char getNextChar();
+ void
+ getLocationLineAndColumn(Location location, int& line, int& column) const;
+ std::string getLocationLineAndColumn(Location location) const;
+ void addComment(Location begin, Location end, CommentPlacement placement);
+ void skipCommentTokens(Token& token);
+
+ typedef std::stack<Value*> Nodes;
+ Nodes nodes_;
+ Errors errors_;
+ std::string document_;
+ Location begin_;
+ Location end_;
+ Location current_;
+ Location lastValueEnd_;
+ Value* lastValue_;
+ std::string commentsBefore_;
+ int stackDepth_;
+
+ OurFeatures const features_;
+ bool collectComments_;
+}; // OurReader
+
+// complete copy of Read impl, for OurReader
+
+OurReader::OurReader(OurFeatures const& features)
+ : errors_(), document_(), begin_(), end_(), current_(), lastValueEnd_(),
+ lastValue_(), commentsBefore_(), features_(features), collectComments_() {
+}
+
+bool OurReader::parse(const char* beginDoc,
+ const char* endDoc,
+ Value& root,
+ bool collectComments) {
+ if (!features_.allowComments_) {
+ collectComments = false;
+ }
+
+ begin_ = beginDoc;
+ end_ = endDoc;
+ collectComments_ = collectComments;
+ current_ = begin_;
+ lastValueEnd_ = 0;
+ lastValue_ = 0;
+ commentsBefore_ = "";
+ errors_.clear();
+ while (!nodes_.empty())
+ nodes_.pop();
+ nodes_.push(&root);
+
+ stackDepth_ = 0;
+ bool successful = readValue();
+ Token token;
+ skipCommentTokens(token);
+ if (features_.failIfExtra_) {
+ if (token.type_ != tokenError && token.type_ != tokenEndOfStream) {
+ addError("Extra non-whitespace after JSON value.", token);
+ return false;
+ }
+ }
+ if (collectComments_ && !commentsBefore_.empty())
+ root.setComment(commentsBefore_, commentAfter);
+ if (features_.strictRoot_) {
+ if (!root.isArray() && !root.isObject()) {
+ // Set error location to start of doc, ideally should be first token found
+ // in doc
+ token.type_ = tokenError;
+ token.start_ = beginDoc;
+ token.end_ = endDoc;
+ addError(
+ "A valid JSON document must be either an array or an object value.",
+ token);
+ return false;
+ }
+ }
+ return successful;
+}
+
+bool OurReader::readValue() {
+ if (stackDepth_ >= features_.stackLimit_) throwRuntimeError("Exceeded stackLimit in readValue().");
+ ++stackDepth_;
+ Token token;
+ skipCommentTokens(token);
+ bool successful = true;
+
+ if (collectComments_ && !commentsBefore_.empty()) {
+ currentValue().setComment(commentsBefore_, commentBefore);
+ commentsBefore_ = "";
+ }
+
+ switch (token.type_) {
+ case tokenObjectBegin:
+ successful = readObject(token);
+ break;
+ case tokenArrayBegin:
+ successful = readArray(token);
+ break;
+ case tokenNumber:
+ successful = decodeNumber(token);
+ break;
+ case tokenString:
+ successful = decodeString(token);
+ break;
+ case tokenTrue:
+ {
+ Value v(true);
+ currentValue().swapPayload(v);
+ }
+ break;
+ case tokenFalse:
+ {
+ Value v(false);
+ currentValue().swapPayload(v);
+ }
+ break;
+ case tokenNull:
+ {
+ Value v;
+ currentValue().swapPayload(v);
+ }
+ break;
+ case tokenNaN:
+ {
+ Value v(std::numeric_limits<double>::quiet_NaN());
+ currentValue().swapPayload(v);
+ }
+ break;
+ case tokenPosInf:
+ {
+ Value v(std::numeric_limits<double>::infinity());
+ currentValue().swapPayload(v);
+ }
+ break;
+ case tokenNegInf:
+ {
+ Value v(-std::numeric_limits<double>::infinity());
+ currentValue().swapPayload(v);
+ }
+ break;
+ case tokenArraySeparator:
+ case tokenObjectEnd:
+ case tokenArrayEnd:
+ if (features_.allowDroppedNullPlaceholders_) {
+ // "Un-read" the current token and mark the current value as a null
+ // token.
+ current_--;
+ Value v;
+ currentValue().swapPayload(v);
+ break;
+ } // else, fall through ...
+ default:
+ return addError("Syntax error: value, object or array expected.", token);
+ }
+
+ if (collectComments_) {
+ lastValueEnd_ = current_;
+ lastValue_ = &currentValue();
+ }
+
+ --stackDepth_;
+ return successful;
+}
+
+void OurReader::skipCommentTokens(Token& token) {
+ if (features_.allowComments_) {
+ do {
+ readToken(token);
+ } while (token.type_ == tokenComment);
+ } else {
+ readToken(token);
+ }
+}
+
+bool OurReader::readToken(Token& token) {
+ skipSpaces();
+ token.start_ = current_;
+ Char c = getNextChar();
+ bool ok = true;
+ switch (c) {
+ case '{':
+ token.type_ = tokenObjectBegin;
+ break;
+ case '}':
+ token.type_ = tokenObjectEnd;
+ break;
+ case '[':
+ token.type_ = tokenArrayBegin;
+ break;
+ case ']':
+ token.type_ = tokenArrayEnd;
+ break;
+ case '"':
+ token.type_ = tokenString;
+ ok = readString();
+ break;
+ case '\'':
+ if (features_.allowSingleQuotes_) {
+ token.type_ = tokenString;
+ ok = readStringSingleQuote();
+ break;
+ } // else continue
+ case '/':
+ token.type_ = tokenComment;
+ ok = readComment();
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ token.type_ = tokenNumber;
+ readNumber(false);
+ break;
+ case '-':
+ if (readNumber(true)) {
+ token.type_ = tokenNumber;
+ } else {
+ token.type_ = tokenNegInf;
+ ok = features_.allowSpecialFloats_ && match("nfinity", 7);
+ }
+ break;
+ case 't':
+ token.type_ = tokenTrue;
+ ok = match("rue", 3);
+ break;
+ case 'f':
+ token.type_ = tokenFalse;
+ ok = match("alse", 4);
+ break;
+ case 'n':
+ token.type_ = tokenNull;
+ ok = match("ull", 3);
+ break;
+ case 'N':
+ if (features_.allowSpecialFloats_) {
+ token.type_ = tokenNaN;
+ ok = match("aN", 2);
+ } else {
+ ok = false;
+ }
+ break;
+ case 'I':
+ if (features_.allowSpecialFloats_) {
+ token.type_ = tokenPosInf;
+ ok = match("nfinity", 7);
+ } else {
+ ok = false;
+ }
+ break;
+ case ',':
+ token.type_ = tokenArraySeparator;
+ break;
+ case ':':
+ token.type_ = tokenMemberSeparator;
+ break;
+ case 0:
+ token.type_ = tokenEndOfStream;
+ break;
+ default:
+ ok = false;
+ break;
+ }
+ if (!ok)
+ token.type_ = tokenError;
+ token.end_ = current_;
+ return true;
+}
+
+void OurReader::skipSpaces() {
+ while (current_ != end_) {
+ Char c = *current_;
+ if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
+ ++current_;
+ else
+ break;
+ }
+}
+
+bool OurReader::match(Location pattern, int patternLength) {
+ if (end_ - current_ < patternLength)
+ return false;
+ int index = patternLength;
+ while (index--)
+ if (current_[index] != pattern[index])
+ return false;
+ current_ += patternLength;
+ return true;
+}
+
+bool OurReader::readComment() {
+ Location commentBegin = current_ - 1;
+ Char c = getNextChar();
+ bool successful = false;
+ if (c == '*')
+ successful = readCStyleComment();
+ else if (c == '/')
+ successful = readCppStyleComment();
+ if (!successful)
+ return false;
+
+ if (collectComments_) {
+ CommentPlacement placement = commentBefore;
+ if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) {
+ if (c != '*' || !containsNewLine(commentBegin, current_))
+ placement = commentAfterOnSameLine;
+ }
+
+ addComment(commentBegin, current_, placement);
+ }
+ return true;
+}
+
+void
+OurReader::addComment(Location begin, Location end, CommentPlacement placement) {
+ assert(collectComments_);
+ const std::string& normalized = normalizeEOL(begin, end);
+ if (placement == commentAfterOnSameLine) {
+ assert(lastValue_ != 0);
+ lastValue_->setComment(normalized, placement);
+ } else {
+ commentsBefore_ += normalized;
+ }
+}
+
+bool OurReader::readCStyleComment() {
+ while (current_ != end_) {
+ Char c = getNextChar();
+ if (c == '*' && *current_ == '/')
+ break;
+ }
+ return getNextChar() == '/';
+}
+
+bool OurReader::readCppStyleComment() {
+ while (current_ != end_) {
+ Char c = getNextChar();
+ if (c == '\n')
+ break;
+ if (c == '\r') {
+ // Consume DOS EOL. It will be normalized in addComment.
+ if (current_ != end_ && *current_ == '\n')
+ getNextChar();
+ // Break on Moc OS 9 EOL.
+ break;
+ }
+ }
+ return true;
+}
+
+bool OurReader::readNumber(bool checkInf) {
+ const char *p = current_;
+ if (checkInf && p != end_ && *p == 'I') {
+ current_ = ++p;
+ return false;
+ }
+ char c = '0'; // stopgap for already consumed character
+ // integral part
+ while (c >= '0' && c <= '9')
+ c = (current_ = p) < end_ ? *p++ : 0;
+ // fractional part
+ if (c == '.') {
+ c = (current_ = p) < end_ ? *p++ : 0;
+ while (c >= '0' && c <= '9')
+ c = (current_ = p) < end_ ? *p++ : 0;
+ }
+ // exponential part
+ if (c == 'e' || c == 'E') {
+ c = (current_ = p) < end_ ? *p++ : 0;
+ if (c == '+' || c == '-')
+ c = (current_ = p) < end_ ? *p++ : 0;
+ while (c >= '0' && c <= '9')
+ c = (current_ = p) < end_ ? *p++ : 0;
+ }
+ return true;
+}
+bool OurReader::readString() {
+ Char c = 0;
+ while (current_ != end_) {
+ c = getNextChar();
+ if (c == '\\')
+ getNextChar();
+ else if (c == '"')
+ break;
+ }
+ return c == '"';
+}
+
+
+bool OurReader::readStringSingleQuote() {
+ Char c = 0;
+ while (current_ != end_) {
+ c = getNextChar();
+ if (c == '\\')
+ getNextChar();
+ else if (c == '\'')
+ break;
+ }
+ return c == '\'';
+}
+
+bool OurReader::readObject(Token& /*tokenStart*/) {
+ Token tokenName;
+ std::string name;
+ Value init(objectValue);
+ currentValue().swapPayload(init);
+ while (readToken(tokenName)) {
+ bool initialTokenOk = true;
+ while (tokenName.type_ == tokenComment && initialTokenOk)
+ initialTokenOk = readToken(tokenName);
+ if (!initialTokenOk)
+ break;
+ if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object
+ return true;
+ name = "";
+ if (tokenName.type_ == tokenString) {
+ if (!decodeString(tokenName, name))
+ return recoverFromError(tokenObjectEnd);
+ } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) {
+ Value numberName;
+ if (!decodeNumber(tokenName, numberName))
+ return recoverFromError(tokenObjectEnd);
+ name = numberName.asString();
+ } else {
+ break;
+ }
+
+ Token colon;
+ if (!readToken(colon) || colon.type_ != tokenMemberSeparator) {
+ return addErrorAndRecover(
+ "Missing ':' after object member name", colon, tokenObjectEnd);
+ }
+ if (name.length() >= (1U<<30)) throwRuntimeError("keylength >= 2^30");
+ if (features_.rejectDupKeys_ && currentValue().isMember(name)) {
+ std::string msg = "Duplicate key: '" + name + "'";
+ return addErrorAndRecover(
+ msg, tokenName, tokenObjectEnd);
+ }
+ Value& value = currentValue()[name];
+ nodes_.push(&value);
+ bool ok = readValue();
+ nodes_.pop();
+ if (!ok) // error already set
+ return recoverFromError(tokenObjectEnd);
+
+ Token comma;
+ if (!readToken(comma) ||
+ (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator &&
+ comma.type_ != tokenComment)) {
+ return addErrorAndRecover(
+ "Missing ',' or '}' in object declaration", comma, tokenObjectEnd);
+ }
+ bool finalizeTokenOk = true;
+ while (comma.type_ == tokenComment && finalizeTokenOk)
+ finalizeTokenOk = readToken(comma);
+ if (comma.type_ == tokenObjectEnd)
+ return true;
+ }
+ return addErrorAndRecover(
+ "Missing '}' or object member name", tokenName, tokenObjectEnd);
+}
+
+bool OurReader::readArray(Token& /*tokenStart*/) {
+ Value init(arrayValue);
+ currentValue().swapPayload(init);
+ skipSpaces();
+ if (*current_ == ']') // empty array
+ {
+ Token endArray;
+ readToken(endArray);
+ return true;
+ }
+ int index = 0;
+ for (;;) {
+ Value& value = currentValue()[index++];
+ nodes_.push(&value);
+ bool ok = readValue();
+ nodes_.pop();
+ if (!ok) // error already set
+ return recoverFromError(tokenArrayEnd);
+
+ Token token;
+ // Accept Comment after last item in the array.
+ ok = readToken(token);
+ while (token.type_ == tokenComment && ok) {
+ ok = readToken(token);
+ }
+ bool badTokenType =
+ (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd);
+ if (!ok || badTokenType) {
+ return addErrorAndRecover(
+ "Missing ',' or ']' in array declaration", token, tokenArrayEnd);
+ }
+ if (token.type_ == tokenArrayEnd)
+ break;
+ }
+ return true;
+}
+
+bool OurReader::decodeNumber(Token& token) {
+ Value decoded;
+ if (!decodeNumber(token, decoded))
+ return false;
+ currentValue().swapPayload(decoded);
+ return true;
+}
+
+bool OurReader::decodeNumber(Token& token, Value& decoded) {
+ // Attempts to parse the number as an integer. If the number is
+ // larger than the maximum supported value of an integer then
+ // we decode the number as a double.
+ Location current = token.start_;
+ bool isNegative = *current == '-';
+ if (isNegative)
+ ++current;
+ // TODO: Help the compiler do the div and mod at compile time or get rid of them.
+ Value::LargestUInt maxIntegerValue =
+ isNegative ? Value::LargestUInt(-Value::minLargestInt)
+ : Value::maxLargestUInt;
+ Value::LargestUInt threshold = maxIntegerValue / 10;
+ Value::LargestUInt value = 0;
+ while (current < token.end_) {
+ Char c = *current++;
+ if (c < '0' || c > '9')
+ return decodeDouble(token, decoded);
+ Value::UInt digit(c - '0');
+ if (value >= threshold) {
+ // We've hit or exceeded the max value divided by 10 (rounded down). If
+ // a) we've only just touched the limit, b) this is the last digit, and
+ // c) it's small enough to fit in that rounding delta, we're okay.
+ // Otherwise treat this number as a double to avoid overflow.
+ if (value > threshold || current != token.end_ ||
+ digit > maxIntegerValue % 10) {
+ return decodeDouble(token, decoded);
+ }
+ }
+ value = value * 10 + digit;
+ }
+ if (isNegative)
+ decoded = -Value::LargestInt(value);
+ else if (value <= Value::LargestUInt(Value::maxInt))
+ decoded = Value::LargestInt(value);
+ else
+ decoded = value;
+ return true;
+}
+
+bool OurReader::decodeDouble(Token& token) {
+ Value decoded;
+ if (!decodeDouble(token, decoded))
+ return false;
+ currentValue().swapPayload(decoded);
+ return true;
+}
+
+bool OurReader::decodeDouble(Token& token, Value& decoded) {
+ double value = 0;
+ std::string buffer( token.start_, token.end_ );
+ std::istringstream is(buffer);
+ if (!(is >> value))
+ return addError("'" + std::string(token.start_, token.end_) +
+ "' is not a number.",
+ token);
+ decoded = value;
+ return true;
+}
+
+bool OurReader::decodeString(Token& token) {
+ std::string decoded_string;
+ if (!decodeString(token, decoded_string))
+ return false;
+ Value decoded(decoded_string);
+ currentValue().swapPayload(decoded);
+ return true;
+}
+
+bool OurReader::decodeString(Token& token, std::string& decoded) {
+ decoded.reserve(token.end_ - token.start_ - 2);
+ Location current = token.start_ + 1; // skip '"'
+ Location end = token.end_ - 1; // do not include '"'
+ while (current != end) {
+ Char c = *current++;
+ if (c == '"')
+ break;
+ else if (c == '\\') {
+ if (current == end)
+ return addError("Empty escape sequence in string", token, current);
+ Char escape = *current++;
+ switch (escape) {
+ case '"':
+ decoded += '"';
+ break;
+ case '/':
+ decoded += '/';
+ break;
+ case '\\':
+ decoded += '\\';
+ break;
+ case 'b':
+ decoded += '\b';
+ break;
+ case 'f':
+ decoded += '\f';
+ break;
+ case 'n':
+ decoded += '\n';
+ break;
+ case 'r':
+ decoded += '\r';
+ break;
+ case 't':
+ decoded += '\t';
+ break;
+ case 'u': {
+ unsigned int unicode;
+ if (!decodeUnicodeCodePoint(token, current, end, unicode))
+ return false;
+ decoded += codePointToUTF8(unicode);
+ } break;
+ default:
+ return addError("Bad escape sequence in string", token, current);
+ }
+ } else {
+ decoded += c;
+ }
+ }
+ return true;
+}
+
+bool OurReader::decodeUnicodeCodePoint(Token& token,
+ Location& current,
+ Location end,
+ unsigned int& unicode) {
+
+ if (!decodeUnicodeEscapeSequence(token, current, end, unicode))
+ return false;
+ if (unicode >= 0xD800 && unicode <= 0xDBFF) {
+ // surrogate pairs
+ if (end - current < 6)
+ return addError(
+ "additional six characters expected to parse unicode surrogate pair.",
+ token,
+ current);
+ unsigned int surrogatePair;
+ if (*(current++) == '\\' && *(current++) == 'u') {
+ if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) {
+ unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF);
+ } else
+ return false;
+ } else
+ return addError("expecting another \\u token to begin the second half of "
+ "a unicode surrogate pair",
+ token,
+ current);
+ }
+ return true;
+}
+
+bool OurReader::decodeUnicodeEscapeSequence(Token& token,
+ Location& current,
+ Location end,
+ unsigned int& unicode) {
+ if (end - current < 4)
+ return addError(
+ "Bad unicode escape sequence in string: four digits expected.",
+ token,
+ current);
+ unicode = 0;
+ for (int index = 0; index < 4; ++index) {
+ Char c = *current++;
+ unicode *= 16;
+ if (c >= '0' && c <= '9')
+ unicode += c - '0';
+ else if (c >= 'a' && c <= 'f')
+ unicode += c - 'a' + 10;
+ else if (c >= 'A' && c <= 'F')
+ unicode += c - 'A' + 10;
+ else
+ return addError(
+ "Bad unicode escape sequence in string: hexadecimal digit expected.",
+ token,
+ current);
+ }
+ return true;
+}
+
+bool
+OurReader::addError(const std::string& message, Token& token, Location extra) {
+ ErrorInfo info;
+ info.token_ = token;
+ info.message_ = message;
+ info.extra_ = extra;
+ errors_.push_back(info);
+ return false;
+}
+
+bool OurReader::recoverFromError(TokenType skipUntilToken) {
+ int errorCount = int(errors_.size());
+ Token skip;
+ for (;;) {
+ if (!readToken(skip))
+ errors_.resize(errorCount); // discard errors caused by recovery
+ if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream)
+ break;
+ }
+ errors_.resize(errorCount);
+ return false;
+}
+
+bool OurReader::addErrorAndRecover(const std::string& message,
+ Token& token,
+ TokenType skipUntilToken) {
+ addError(message, token);
+ return recoverFromError(skipUntilToken);
+}
+
+Value& OurReader::currentValue() { return *(nodes_.top()); }
+
+OurReader::Char OurReader::getNextChar() {
+ if (current_ == end_)
+ return 0;
+ return *current_++;
+}
+
+void OurReader::getLocationLineAndColumn(Location location,
+ int& line,
+ int& column) const {
+ Location current = begin_;
+ Location lastLineStart = current;
+ line = 0;
+ while (current < location && current != end_) {
+ Char c = *current++;
+ if (c == '\r') {
+ if (*current == '\n')
+ ++current;
+ lastLineStart = current;
+ ++line;
+ } else if (c == '\n') {
+ lastLineStart = current;
+ ++line;
+ }
+ }
+ // column & line start at 1
+ column = int(location - lastLineStart) + 1;
+ ++line;
+}
+
+std::string OurReader::getLocationLineAndColumn(Location location) const {
+ int line, column;
+ getLocationLineAndColumn(location, line, column);
+ char buffer[18 + 16 + 16 + 1];
+ snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column);
+ return buffer;
+}
+
+std::string OurReader::getFormattedErrorMessages() const {
+ std::string formattedMessage;
+ for (Errors::const_iterator itError = errors_.begin();
+ itError != errors_.end();
+ ++itError) {
+ const ErrorInfo& error = *itError;
+ formattedMessage +=
+ "* " + getLocationLineAndColumn(error.token_.start_) + "\n";
+ formattedMessage += " " + error.message_ + "\n";
+ if (error.extra_)
+ formattedMessage +=
+ "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n";
+ }
+ return formattedMessage;
+}
+
+
+class OurCharReader : public CharReader {
+ bool const collectComments_;
+ OurReader reader_;
+public:
+ OurCharReader(
+ bool collectComments,
+ OurFeatures const& features)
+ : collectComments_(collectComments)
+ , reader_(features)
+ {}
+ virtual bool parse(
+ char const* beginDoc, char const* endDoc,
+ Value* root, std::string* errs) {
+ bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_);
+ if (errs) {
+ *errs = reader_.getFormattedErrorMessages();
+ }
+ return ok;
+ }
+};
+
+CharReaderBuilder::CharReaderBuilder()
+{
+ setDefaults(&settings_);
+}
+CharReaderBuilder::~CharReaderBuilder()
+{}
+CharReader* CharReaderBuilder::newCharReader() const
+{
+ bool collectComments = settings_["collectComments"].asBool();
+ OurFeatures features = OurFeatures::all();
+ features.allowComments_ = settings_["allowComments"].asBool();
+ features.strictRoot_ = settings_["strictRoot"].asBool();
+ features.allowDroppedNullPlaceholders_ = settings_["allowDroppedNullPlaceholders"].asBool();
+ features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool();
+ features.allowSingleQuotes_ = settings_["allowSingleQuotes"].asBool();
+ features.stackLimit_ = settings_["stackLimit"].asInt();
+ features.failIfExtra_ = settings_["failIfExtra"].asBool();
+ features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool();
+ features.allowSpecialFloats_ = settings_["allowSpecialFloats"].asBool();
+ return new OurCharReader(collectComments, features);
+}
+static void getValidReaderKeys(std::set<std::string>* valid_keys)
+{
+ valid_keys->clear();
+ valid_keys->insert("collectComments");
+ valid_keys->insert("allowComments");
+ valid_keys->insert("strictRoot");
+ valid_keys->insert("allowDroppedNullPlaceholders");
+ valid_keys->insert("allowNumericKeys");
+ valid_keys->insert("allowSingleQuotes");
+ valid_keys->insert("stackLimit");
+ valid_keys->insert("failIfExtra");
+ valid_keys->insert("rejectDupKeys");
+ valid_keys->insert("allowSpecialFloats");
+}
+bool CharReaderBuilder::validate(Json::Value* invalid) const
+{
+ Json::Value my_invalid;
+ if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL
+ Json::Value& inv = *invalid;
+ std::set<std::string> valid_keys;
+ getValidReaderKeys(&valid_keys);
+ Value::Members keys = settings_.getMemberNames();
+ size_t n = keys.size();
+ for (size_t i = 0; i < n; ++i) {
+ std::string const& key = keys[i];
+ if (valid_keys.find(key) == valid_keys.end()) {
+ inv[key] = settings_[key];
+ }
+ }
+ return 0u == inv.size();
+}
+Value& CharReaderBuilder::operator[](std::string key)
+{
+ return settings_[key];
+}
+// static
+void CharReaderBuilder::strictMode(Json::Value* settings)
+{
+//! [CharReaderBuilderStrictMode]
+ (*settings)["allowComments"] = false;
+ (*settings)["strictRoot"] = true;
+ (*settings)["allowDroppedNullPlaceholders"] = false;
+ (*settings)["allowNumericKeys"] = false;
+ (*settings)["allowSingleQuotes"] = false;
+ (*settings)["failIfExtra"] = true;
+ (*settings)["rejectDupKeys"] = true;
+ (*settings)["allowSpecialFloats"] = false;
+//! [CharReaderBuilderStrictMode]
+}
+// static
+void CharReaderBuilder::setDefaults(Json::Value* settings)
+{
+//! [CharReaderBuilderDefaults]
+ (*settings)["collectComments"] = true;
+ (*settings)["allowComments"] = true;
+ (*settings)["strictRoot"] = false;
+ (*settings)["allowDroppedNullPlaceholders"] = false;
+ (*settings)["allowNumericKeys"] = false;
+ (*settings)["allowSingleQuotes"] = false;
+ (*settings)["stackLimit"] = 1000;
+ (*settings)["failIfExtra"] = false;
+ (*settings)["rejectDupKeys"] = false;
+ (*settings)["allowSpecialFloats"] = false;
+//! [CharReaderBuilderDefaults]
+}
+
+//////////////////////////////////
+// global functions
+
+bool parseFromStream(
+ CharReader::Factory const& fact, std::istream& sin,
+ Value* root, std::string* errs)
+{
+ std::ostringstream ssin;
+ ssin << sin.rdbuf();
+ std::string doc = ssin.str();
+ char const* begin = doc.data();
+ char const* end = begin + doc.size();
+ // Note that we do not actually need a null-terminator.
+ CharReaderPtr const reader(fact.newCharReader());
+ return reader->parse(begin, end, root, errs);
+}
+
+std::istream& operator>>(std::istream& sin, Value& root) {
+ CharReaderBuilder b;
+ std::string errs;
+ bool ok = parseFromStream(b, sin, &root, &errs);
+ if (!ok) {
+ fprintf(stderr,
+ "Error from reader: %s",
+ errs.c_str());
+
+ throwRuntimeError("reader error");
+ }
+ return sin;
+}
+
+} // namespace Json
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: src/lib_json/json_reader.cpp
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: src/lib_json/json_valueiterator.inl
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2007-2010 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+// included by json_value.cpp
+
+namespace Json {
+
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// class ValueIteratorBase
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+
+ValueIteratorBase::ValueIteratorBase()
+ : current_(), isNull_(true) {
+}
+
+ValueIteratorBase::ValueIteratorBase(
+ const Value::ObjectValues::iterator& current)
+ : current_(current), isNull_(false) {}
+
+Value& ValueIteratorBase::deref() const {
+ return current_->second;
+}
+
+void ValueIteratorBase::increment() {
+ ++current_;
+}
+
+void ValueIteratorBase::decrement() {
+ --current_;
+}
+
+ValueIteratorBase::difference_type
+ValueIteratorBase::computeDistance(const SelfType& other) const {
+#ifdef JSON_USE_CPPTL_SMALLMAP
+ return other.current_ - current_;
+#else
+ // Iterator for null value are initialized using the default
+ // constructor, which initialize current_ to the default
+ // std::map::iterator. As begin() and end() are two instance
+ // of the default std::map::iterator, they can not be compared.
+ // To allow this, we handle this comparison specifically.
+ if (isNull_ && other.isNull_) {
+ return 0;
+ }
+
+ // Usage of std::distance is not portable (does not compile with Sun Studio 12
+ // RogueWave STL,
+ // which is the one used by default).
+ // Using a portable hand-made version for non random iterator instead:
+ // return difference_type( std::distance( current_, other.current_ ) );
+ difference_type myDistance = 0;
+ for (Value::ObjectValues::iterator it = current_; it != other.current_;
+ ++it) {
+ ++myDistance;
+ }
+ return myDistance;
+#endif
+}
+
+bool ValueIteratorBase::isEqual(const SelfType& other) const {
+ if (isNull_) {
+ return other.isNull_;
+ }
+ return current_ == other.current_;
+}
+
+void ValueIteratorBase::copy(const SelfType& other) {
+ current_ = other.current_;
+ isNull_ = other.isNull_;
+}
+
+Value ValueIteratorBase::key() const {
+ const Value::CZString czstring = (*current_).first;
+ if (czstring.data()) {
+ if (czstring.isStaticString())
+ return Value(StaticString(czstring.data()));
+ return Value(czstring.data(), czstring.data() + czstring.length());
+ }
+ return Value(czstring.index());
+}
+
+UInt ValueIteratorBase::index() const {
+ const Value::CZString czstring = (*current_).first;
+ if (!czstring.data())
+ return czstring.index();
+ return Value::UInt(-1);
+}
+
+std::string ValueIteratorBase::name() const {
+ char const* keey;
+ char const* end;
+ keey = memberName(&end);
+ if (!keey) return std::string();
+ return std::string(keey, end);
+}
+
+char const* ValueIteratorBase::memberName() const {
+ const char* cname = (*current_).first.data();
+ return cname ? cname : "";
+}
+
+char const* ValueIteratorBase::memberName(char const** end) const {
+ const char* cname = (*current_).first.data();
+ if (!cname) {
+ *end = NULL;
+ return NULL;
+ }
+ *end = cname + (*current_).first.length();
+ return cname;
+}
+
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// class ValueConstIterator
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+
+ValueConstIterator::ValueConstIterator() {}
+
+ValueConstIterator::ValueConstIterator(
+ const Value::ObjectValues::iterator& current)
+ : ValueIteratorBase(current) {}
+
+ValueConstIterator& ValueConstIterator::
+operator=(const ValueIteratorBase& other) {
+ copy(other);
+ return *this;
+}
+
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// class ValueIterator
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+
+ValueIterator::ValueIterator() {}
+
+ValueIterator::ValueIterator(const Value::ObjectValues::iterator& current)
+ : ValueIteratorBase(current) {}
+
+ValueIterator::ValueIterator(const ValueConstIterator& other)
+ : ValueIteratorBase(other) {}
+
+ValueIterator::ValueIterator(const ValueIterator& other)
+ : ValueIteratorBase(other) {}
+
+ValueIterator& ValueIterator::operator=(const SelfType& other) {
+ copy(other);
+ return *this;
+}
+
+} // namespace Json
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: src/lib_json/json_valueiterator.inl
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: src/lib_json/json_value.cpp
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2011 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include <json/assertions.h>
+#include <json/value.h>
+#include <json/writer.h>
+#endif // if !defined(JSON_IS_AMALGAMATION)
+#include <math.h>
+#include <sstream>
+#include <utility>
+#include <cstring>
+#include <cassert>
+#ifdef JSON_USE_CPPTL
+#include <cpptl/conststring.h>
+#endif
+#include <cstddef> // size_t
+#include <algorithm> // min()
+
+#define JSON_ASSERT_UNREACHABLE assert(false)
+
+namespace Json {
+
+// This is a walkaround to avoid the static initialization of Value::null.
+// kNull must be word-aligned to avoid crashing on ARM. We use an alignment of
+// 8 (instead of 4) as a bit of future-proofing.
+#if defined(__ARMEL__)
+#define ALIGNAS(byte_alignment) __attribute__((aligned(byte_alignment)))
+#else
+// This exists for binary compatibility only. Use nullRef.
+const Value Value::null;
+#define ALIGNAS(byte_alignment)
+#endif
+static const unsigned char ALIGNAS(8) kNull[sizeof(Value)] = { 0 };
+const unsigned char& kNullRef = kNull[0];
+const Value& Value::nullRef = reinterpret_cast<const Value&>(kNullRef);
+
+const Int Value::minInt = Int(~(UInt(-1) / 2));
+const Int Value::maxInt = Int(UInt(-1) / 2);
+const UInt Value::maxUInt = UInt(-1);
+#if defined(JSON_HAS_INT64)
+const Int64 Value::minInt64 = Int64(~(UInt64(-1) / 2));
+const Int64 Value::maxInt64 = Int64(UInt64(-1) / 2);
+const UInt64 Value::maxUInt64 = UInt64(-1);
+// The constant is hard-coded because some compiler have trouble
+// converting Value::maxUInt64 to a double correctly (AIX/xlC).
+// Assumes that UInt64 is a 64 bits integer.
+static const double maxUInt64AsDouble = 18446744073709551615.0;
+#endif // defined(JSON_HAS_INT64)
+const LargestInt Value::minLargestInt = LargestInt(~(LargestUInt(-1) / 2));
+const LargestInt Value::maxLargestInt = LargestInt(LargestUInt(-1) / 2);
+const LargestUInt Value::maxLargestUInt = LargestUInt(-1);
+
+#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+template <typename T, typename U>
+static inline bool InRange(double d, T min, U max) {
+ return d >= min && d <= max;
+}
+#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+static inline double integerToDouble(Json::UInt64 value) {
+ return static_cast<double>(Int64(value / 2)) * 2.0 + Int64(value & 1);
+}
+
+template <typename T> static inline double integerToDouble(T value) {
+ return static_cast<double>(value);
+}
+
+template <typename T, typename U>
+static inline bool InRange(double d, T min, U max) {
+ return d >= integerToDouble(min) && d <= integerToDouble(max);
+}
+#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+
+/** Duplicates the specified string value.
+ * @param value Pointer to the string to duplicate. Must be zero-terminated if
+ * length is "unknown".
+ * @param length Length of the value. if equals to unknown, then it will be
+ * computed using strlen(value).
+ * @return Pointer on the duplicate instance of string.
+ */
+static inline char* duplicateStringValue(const char* value,
+ size_t length) {
+ // Avoid an integer overflow in the call to malloc below by limiting length
+ // to a sane value.
+ if (length >= (size_t)Value::maxInt)
+ length = Value::maxInt - 1;
+
+ char* newString = static_cast<char*>(malloc(length + 1));
+ if (newString == NULL) {
+ throwRuntimeError(
+ "in Json::Value::duplicateStringValue(): "
+ "Failed to allocate string value buffer");
+ }
+ memcpy(newString, value, length);
+ newString[length] = 0;
+ return newString;
+}
+
+/* Record the length as a prefix.
+ */
+static inline char* duplicateAndPrefixStringValue(
+ const char* value,
+ unsigned int length)
+{
+ // Avoid an integer overflow in the call to malloc below by limiting length
+ // to a sane value.
+ JSON_ASSERT_MESSAGE(length <= (unsigned)Value::maxInt - sizeof(unsigned) - 1U,
+ "in Json::Value::duplicateAndPrefixStringValue(): "
+ "length too big for prefixing");
+ unsigned actualLength = length + static_cast<unsigned>(sizeof(unsigned)) + 1U;
+ char* newString = static_cast<char*>(malloc(actualLength));
+ if (newString == 0) {
+ throwRuntimeError(
+ "in Json::Value::duplicateAndPrefixStringValue(): "
+ "Failed to allocate string value buffer");
+ }
+ *reinterpret_cast<unsigned*>(newString) = length;
+ memcpy(newString + sizeof(unsigned), value, length);
+ newString[actualLength - 1U] = 0; // to avoid buffer over-run accidents by users later
+ return newString;
+}
+inline static void decodePrefixedString(
+ bool isPrefixed, char const* prefixed,
+ unsigned* length, char const** value)
+{
+ if (!isPrefixed) {
+ *length = static_cast<unsigned>(strlen(prefixed));
+ *value = prefixed;
+ } else {
+ *length = *reinterpret_cast<unsigned const*>(prefixed);
+ *value = prefixed + sizeof(unsigned);
+ }
+}
+/** Free the string duplicated by duplicateStringValue()/duplicateAndPrefixStringValue().
+ */
+static inline void releaseStringValue(char* value) { free(value); }
+
+} // namespace Json
+
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// ValueInternals...
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+#if !defined(JSON_IS_AMALGAMATION)
+
+#include "json_valueiterator.inl"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+
+namespace Json {
+
+Exception::Exception(std::string const& msg)
+ : msg_(msg)
+{}
+Exception::~Exception() throw()
+{}
+char const* Exception::what() const throw()
+{
+ return msg_.c_str();
+}
+RuntimeError::RuntimeError(std::string const& msg)
+ : Exception(msg)
+{}
+LogicError::LogicError(std::string const& msg)
+ : Exception(msg)
+{}
+JSONCPP_NORETURN void throwRuntimeError(std::string const& msg)
+{
+ throw RuntimeError(msg);
+}
+JSONCPP_NORETURN void throwLogicError(std::string const& msg)
+{
+ throw LogicError(msg);
+}
+
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// class Value::CommentInfo
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+
+Value::CommentInfo::CommentInfo() : comment_(0) {}
+
+Value::CommentInfo::~CommentInfo() {
+ if (comment_)
+ releaseStringValue(comment_);
+}
+
+void Value::CommentInfo::setComment(const char* text, size_t len) {
+ if (comment_) {
+ releaseStringValue(comment_);
+ comment_ = 0;
+ }
+ JSON_ASSERT(text != 0);
+ JSON_ASSERT_MESSAGE(
+ text[0] == '\0' || text[0] == '/',
+ "in Json::Value::setComment(): Comments must start with /");
+ // It seems that /**/ style comments are acceptable as well.
+ comment_ = duplicateStringValue(text, len);
+}
+
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// class Value::CZString
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+
+// Notes: policy_ indicates if the string was allocated when
+// a string is stored.
+
+Value::CZString::CZString(ArrayIndex aindex) : cstr_(0), index_(aindex) {}
+
+Value::CZString::CZString(char const* str, unsigned ulength, DuplicationPolicy allocate)
+ : cstr_(str)
+{
+ // allocate != duplicate
+ storage_.policy_ = allocate & 0x3;
+ storage_.length_ = ulength & 0x3FFFFFFF;
+}
+
+Value::CZString::CZString(const CZString& other)
+ : cstr_(other.storage_.policy_ != noDuplication && other.cstr_ != 0
+ ? duplicateStringValue(other.cstr_, other.storage_.length_)
+ : other.cstr_)
+{
+ storage_.policy_ = (other.cstr_
+ ? (static_cast<DuplicationPolicy>(other.storage_.policy_) == noDuplication
+ ? noDuplication : duplicate)
+ : static_cast<DuplicationPolicy>(other.storage_.policy_));
+ storage_.length_ = other.storage_.length_;
+}
+
+Value::CZString::~CZString() {
+ if (cstr_ && storage_.policy_ == duplicate)
+ releaseStringValue(const_cast<char*>(cstr_));
+}
+
+void Value::CZString::swap(CZString& other) {
+ std::swap(cstr_, other.cstr_);
+ std::swap(index_, other.index_);
+}
+
+Value::CZString& Value::CZString::operator=(CZString other) {
+ swap(other);
+ return *this;
+}
+
+bool Value::CZString::operator<(const CZString& other) const {
+ if (!cstr_) return index_ < other.index_;
+ //return strcmp(cstr_, other.cstr_) < 0;
+ // Assume both are strings.
+ unsigned this_len = this->storage_.length_;
+ unsigned other_len = other.storage_.length_;
+ unsigned min_len = std::min(this_len, other_len);
+ int comp = memcmp(this->cstr_, other.cstr_, min_len);
+ if (comp < 0) return true;
+ if (comp > 0) return false;
+ return (this_len < other_len);
+}
+
+bool Value::CZString::operator==(const CZString& other) const {
+ if (!cstr_) return index_ == other.index_;
+ //return strcmp(cstr_, other.cstr_) == 0;
+ // Assume both are strings.
+ unsigned this_len = this->storage_.length_;
+ unsigned other_len = other.storage_.length_;
+ if (this_len != other_len) return false;
+ int comp = memcmp(this->cstr_, other.cstr_, this_len);
+ return comp == 0;
+}
+
+ArrayIndex Value::CZString::index() const { return index_; }
+
+//const char* Value::CZString::c_str() const { return cstr_; }
+const char* Value::CZString::data() const { return cstr_; }
+unsigned Value::CZString::length() const { return storage_.length_; }
+bool Value::CZString::isStaticString() const { return storage_.policy_ == noDuplication; }
+
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// class Value::Value
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+// //////////////////////////////////////////////////////////////////
+
+/*! \internal Default constructor initialization must be equivalent to:
+ * memset( this, 0, sizeof(Value) )
+ * This optimization is used in ValueInternalMap fast allocator.
+ */
+Value::Value(ValueType vtype) {
+ initBasic(vtype);
+ switch (vtype) {
+ case nullValue:
+ break;
+ case intValue:
+ case uintValue:
+ value_.int_ = 0;
+ break;
+ case realValue:
+ value_.real_ = 0.0;
+ break;
+ case stringValue:
+ value_.string_ = 0;
+ break;
+ case arrayValue:
+ case objectValue:
+ value_.map_ = new ObjectValues();
+ break;
+ case booleanValue:
+ value_.bool_ = false;
+ break;
+ default:
+ JSON_ASSERT_UNREACHABLE;
+ }
+}
+
+Value::Value(Int value) {
+ initBasic(intValue);
+ value_.int_ = value;
+}
+
+Value::Value(UInt value) {
+ initBasic(uintValue);
+ value_.uint_ = value;
+}
+#if defined(JSON_HAS_INT64)
+Value::Value(Int64 value) {
+ initBasic(intValue);
+ value_.int_ = value;
+}
+Value::Value(UInt64 value) {
+ initBasic(uintValue);
+ value_.uint_ = value;
+}
+#endif // defined(JSON_HAS_INT64)
+
+Value::Value(double value) {
+ initBasic(realValue);
+ value_.real_ = value;
+}
+
+Value::Value(const char* value) {
+ initBasic(stringValue, true);
+ value_.string_ = duplicateAndPrefixStringValue(value, static_cast<unsigned>(strlen(value)));
+}
+
+Value::Value(const char* beginValue, const char* endValue) {
+ initBasic(stringValue, true);
+ value_.string_ =
+ duplicateAndPrefixStringValue(beginValue, static_cast<unsigned>(endValue - beginValue));
+}
+
+Value::Value(const std::string& value) {
+ initBasic(stringValue, true);
+ value_.string_ =
+ duplicateAndPrefixStringValue(value.data(), static_cast<unsigned>(value.length()));
+}
+
+Value::Value(const StaticString& value) {
+ initBasic(stringValue);
+ value_.string_ = const_cast<char*>(value.c_str());
+}
+
+#ifdef JSON_USE_CPPTL
+Value::Value(const CppTL::ConstString& value) {
+ initBasic(stringValue, true);
+ value_.string_ = duplicateAndPrefixStringValue(value, static_cast<unsigned>(value.length()));
+}
+#endif
+
+Value::Value(bool value) {
+ initBasic(booleanValue);
+ value_.bool_ = value;
+}
+
+Value::Value(Value const& other)
+ : type_(other.type_), allocated_(false)
+ ,
+ comments_(0)
+{
+ switch (type_) {
+ case nullValue:
+ case intValue:
+ case uintValue:
+ case realValue:
+ case booleanValue:
+ value_ = other.value_;
+ break;
+ case stringValue:
+ if (other.value_.string_ && other.allocated_) {
+ unsigned len;
+ char const* str;
+ decodePrefixedString(other.allocated_, other.value_.string_,
+ &len, &str);
+ value_.string_ = duplicateAndPrefixStringValue(str, len);
+ allocated_ = true;
+ } else {
+ value_.string_ = other.value_.string_;
+ allocated_ = false;
+ }
+ break;
+ case arrayValue:
+ case objectValue:
+ value_.map_ = new ObjectValues(*other.value_.map_);
+ break;
+ default:
+ JSON_ASSERT_UNREACHABLE;
+ }
+ if (other.comments_) {
+ comments_ = new CommentInfo[numberOfCommentPlacement];
+ for (int comment = 0; comment < numberOfCommentPlacement; ++comment) {
+ const CommentInfo& otherComment = other.comments_[comment];
+ if (otherComment.comment_)
+ comments_[comment].setComment(
+ otherComment.comment_, strlen(otherComment.comment_));
+ }
+ }
+}
+
+Value::~Value() {
+ switch (type_) {
+ case nullValue:
+ case intValue:
+ case uintValue:
+ case realValue:
+ case booleanValue:
+ break;
+ case stringValue:
+ if (allocated_)
+ releaseStringValue(value_.string_);
+ break;
+ case arrayValue:
+ case objectValue:
+ delete value_.map_;
+ break;
+ default:
+ JSON_ASSERT_UNREACHABLE;
+ }
+
+ if (comments_)
+ delete[] comments_;
+}
+
+Value &Value::operator=(const Value &other) {
+ Value temp(other);
+ swap(temp);
+ return *this;
+}
+
+void Value::swapPayload(Value& other) {
+ ValueType temp = type_;
+ type_ = other.type_;
+ other.type_ = temp;
+ std::swap(value_, other.value_);
+ int temp2 = allocated_;
+ allocated_ = other.allocated_;
+ other.allocated_ = temp2 & 0x1;
+}
+
+void Value::swap(Value& other) {
+ swapPayload(other);
+ std::swap(comments_, other.comments_);
+}
+
+ValueType Value::type() const { return type_; }
+
+int Value::compare(const Value& other) const {
+ if (*this < other)
+ return -1;
+ if (*this > other)
+ return 1;
+ return 0;
+}
+
+bool Value::operator<(const Value& other) const {
+ int typeDelta = type_ - other.type_;
+ if (typeDelta)
+ return typeDelta < 0 ? true : false;
+ switch (type_) {
+ case nullValue:
+ return false;
+ case intValue:
+ return value_.int_ < other.value_.int_;
+ case uintValue:
+ return value_.uint_ < other.value_.uint_;
+ case realValue:
+ return value_.real_ < other.value_.real_;
+ case booleanValue:
+ return value_.bool_ < other.value_.bool_;
+ case stringValue:
+ {
+ if ((value_.string_ == 0) || (other.value_.string_ == 0)) {
+ if (other.value_.string_) return true;
+ else return false;
+ }
+ unsigned this_len;
+ unsigned other_len;
+ char const* this_str;
+ char const* other_str;
+ decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str);
+ decodePrefixedString(other.allocated_, other.value_.string_, &other_len, &other_str);
+ unsigned min_len = std::min(this_len, other_len);
+ int comp = memcmp(this_str, other_str, min_len);
+ if (comp < 0) return true;
+ if (comp > 0) return false;
+ return (this_len < other_len);
+ }
+ case arrayValue:
+ case objectValue: {
+ int delta = int(value_.map_->size() - other.value_.map_->size());
+ if (delta)
+ return delta < 0;
+ return (*value_.map_) < (*other.value_.map_);
+ }
+ default:
+ JSON_ASSERT_UNREACHABLE;
+ }
+ return false; // unreachable
+}
+
+bool Value::operator<=(const Value& other) const { return !(other < *this); }
+
+bool Value::operator>=(const Value& other) const { return !(*this < other); }
+
+bool Value::operator>(const Value& other) const { return other < *this; }
+
+bool Value::operator==(const Value& other) const {
+ // if ( type_ != other.type_ )
+ // GCC 2.95.3 says:
+ // attempt to take address of bit-field structure member `Json::Value::type_'
+ // Beats me, but a temp solves the problem.
+ int temp = other.type_;
+ if (type_ != temp)
+ return false;
+ switch (type_) {
+ case nullValue:
+ return true;
+ case intValue:
+ return value_.int_ == other.value_.int_;
+ case uintValue:
+ return value_.uint_ == other.value_.uint_;
+ case realValue:
+ return value_.real_ == other.value_.real_;
+ case booleanValue:
+ return value_.bool_ == other.value_.bool_;
+ case stringValue:
+ {
+ if ((value_.string_ == 0) || (other.value_.string_ == 0)) {
+ return (value_.string_ == other.value_.string_);
+ }
+ unsigned this_len;
+ unsigned other_len;
+ char const* this_str;
+ char const* other_str;
+ decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str);
+ decodePrefixedString(other.allocated_, other.value_.string_, &other_len, &other_str);
+ if (this_len != other_len) return false;
+ int comp = memcmp(this_str, other_str, this_len);
+ return comp == 0;
+ }
+ case arrayValue:
+ case objectValue:
+ return value_.map_->size() == other.value_.map_->size() &&
+ (*value_.map_) == (*other.value_.map_);
+ default:
+ JSON_ASSERT_UNREACHABLE;
+ }
+ return false; // unreachable
+}
+
+bool Value::operator!=(const Value& other) const { return !(*this == other); }
+
+const char* Value::asCString() const {
+ JSON_ASSERT_MESSAGE(type_ == stringValue,
+ "in Json::Value::asCString(): requires stringValue");
+ if (value_.string_ == 0) return 0;
+ unsigned this_len;
+ char const* this_str;
+ decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str);
+ return this_str;
+}
+
+bool Value::getString(char const** str, char const** cend) const {
+ if (type_ != stringValue) return false;
+ if (value_.string_ == 0) return false;
+ unsigned length;
+ decodePrefixedString(this->allocated_, this->value_.string_, &length, str);
+ *cend = *str + length;
+ return true;
+}
+
+std::string Value::asString() const {
+ switch (type_) {
+ case nullValue:
+ return "";
+ case stringValue:
+ {
+ if (value_.string_ == 0) return "";
+ unsigned this_len;
+ char const* this_str;
+ decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str);
+ return std::string(this_str, this_len);
+ }
+ case booleanValue:
+ return value_.bool_ ? "true" : "false";
+ case intValue:
+ return valueToString(value_.int_);
+ case uintValue:
+ return valueToString(value_.uint_);
+ case realValue:
+ return valueToString(value_.real_);
+ default:
+ JSON_FAIL_MESSAGE("Type is not convertible to string");
+ }
+}
+
+#ifdef JSON_USE_CPPTL
+CppTL::ConstString Value::asConstString() const {
+ unsigned len;
+ char const* str;
+ decodePrefixedString(allocated_, value_.string_,
+ &len, &str);
+ return CppTL::ConstString(str, len);
+}
+#endif
+
+Value::Int Value::asInt() const {
+ switch (type_) {
+ case intValue:
+ JSON_ASSERT_MESSAGE(isInt(), "LargestInt out of Int range");
+ return Int(value_.int_);
+ case uintValue:
+ JSON_ASSERT_MESSAGE(isInt(), "LargestUInt out of Int range");
+ return Int(value_.uint_);
+ case realValue:
+ JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt, maxInt),
+ "double out of Int range");
+ return Int(value_.real_);
+ case nullValue:
+ return 0;
+ case booleanValue:
+ return value_.bool_ ? 1 : 0;
+ default:
+ break;
+ }
+ JSON_FAIL_MESSAGE("Value is not convertible to Int.");
+}
+
+Value::UInt Value::asUInt() const {
+ switch (type_) {
+ case intValue:
+ JSON_ASSERT_MESSAGE(isUInt(), "LargestInt out of UInt range");
+ return UInt(value_.int_);
+ case uintValue:
+ JSON_ASSERT_MESSAGE(isUInt(), "LargestUInt out of UInt range");
+ return UInt(value_.uint_);
+ case realValue:
+ JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt),
+ "double out of UInt range");
+ return UInt(value_.real_);
+ case nullValue:
+ return 0;
+ case booleanValue:
+ return value_.bool_ ? 1 : 0;
+ default:
+ break;
+ }
+ JSON_FAIL_MESSAGE("Value is not convertible to UInt.");
+}
+
+#if defined(JSON_HAS_INT64)
+
+Value::Int64 Value::asInt64() const {
+ switch (type_) {
+ case intValue:
+ return Int64(value_.int_);
+ case uintValue:
+ JSON_ASSERT_MESSAGE(isInt64(), "LargestUInt out of Int64 range");
+ return Int64(value_.uint_);
+ case realValue:
+ JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt64, maxInt64),
+ "double out of Int64 range");
+ return Int64(value_.real_);
+ case nullValue:
+ return 0;
+ case booleanValue:
+ return value_.bool_ ? 1 : 0;
+ default:
+ break;
+ }
+ JSON_FAIL_MESSAGE("Value is not convertible to Int64.");
+}
+
+Value::UInt64 Value::asUInt64() const {
+ switch (type_) {
+ case intValue:
+ JSON_ASSERT_MESSAGE(isUInt64(), "LargestInt out of UInt64 range");
+ return UInt64(value_.int_);
+ case uintValue:
+ return UInt64(value_.uint_);
+ case realValue:
+ JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64),
+ "double out of UInt64 range");
+ return UInt64(value_.real_);
+ case nullValue:
+ return 0;
+ case booleanValue:
+ return value_.bool_ ? 1 : 0;
+ default:
+ break;
+ }
+ JSON_FAIL_MESSAGE("Value is not convertible to UInt64.");
+}
+#endif // if defined(JSON_HAS_INT64)
+
+LargestInt Value::asLargestInt() const {
+#if defined(JSON_NO_INT64)
+ return asInt();
+#else
+ return asInt64();
+#endif
+}
+
+LargestUInt Value::asLargestUInt() const {
+#if defined(JSON_NO_INT64)
+ return asUInt();
+#else
+ return asUInt64();
+#endif
+}
+
+double Value::asDouble() const {
+ switch (type_) {
+ case intValue:
+ return static_cast<double>(value_.int_);
+ case uintValue:
+#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+ return static_cast<double>(value_.uint_);
+#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+ return integerToDouble(value_.uint_);
+#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+ case realValue:
+ return value_.real_;
+ case nullValue:
+ return 0.0;
+ case booleanValue:
+ return value_.bool_ ? 1.0 : 0.0;
+ default:
+ break;
+ }
+ JSON_FAIL_MESSAGE("Value is not convertible to double.");
+}
+
+float Value::asFloat() const {
+ switch (type_) {
+ case intValue:
+ return static_cast<float>(value_.int_);
+ case uintValue:
+#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+ return static_cast<float>(value_.uint_);
+#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+ return integerToDouble(value_.uint_);
+#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION)
+ case realValue:
+ return static_cast<float>(value_.real_);
+ case nullValue:
+ return 0.0;
+ case booleanValue:
+ return value_.bool_ ? 1.0f : 0.0f;
+ default:
+ break;
+ }
+ JSON_FAIL_MESSAGE("Value is not convertible to float.");
+}
+
+bool Value::asBool() const {
+ switch (type_) {
+ case booleanValue:
+ return value_.bool_;
+ case nullValue:
+ return false;
+ case intValue:
+ return value_.int_ ? true : false;
+ case uintValue:
+ return value_.uint_ ? true : false;
+ case realValue:
+ // This is kind of strange. Not recommended.
+ return (value_.real_ != 0.0) ? true : false;
+ default:
+ break;
+ }
+ JSON_FAIL_MESSAGE("Value is not convertible to bool.");
+}
+
+bool Value::isConvertibleTo(ValueType other) const {
+ switch (other) {
+ case nullValue:
+ return (isNumeric() && asDouble() == 0.0) ||
+ (type_ == booleanValue && value_.bool_ == false) ||
+ (type_ == stringValue && asString() == "") ||
+ (type_ == arrayValue && value_.map_->size() == 0) ||
+ (type_ == objectValue && value_.map_->size() == 0) ||
+ type_ == nullValue;
+ case intValue:
+ return isInt() ||
+ (type_ == realValue && InRange(value_.real_, minInt, maxInt)) ||
+ type_ == booleanValue || type_ == nullValue;
+ case uintValue:
+ return isUInt() ||
+ (type_ == realValue && InRange(value_.real_, 0, maxUInt)) ||
+ type_ == booleanValue || type_ == nullValue;
+ case realValue:
+ return isNumeric() || type_ == booleanValue || type_ == nullValue;
+ case booleanValue:
+ return isNumeric() || type_ == booleanValue || type_ == nullValue;
+ case stringValue:
+ return isNumeric() || type_ == booleanValue || type_ == stringValue ||
+ type_ == nullValue;
+ case arrayValue:
+ return type_ == arrayValue || type_ == nullValue;
+ case objectValue:
+ return type_ == objectValue || type_ == nullValue;
+ }
+ JSON_ASSERT_UNREACHABLE;
+ return false;
+}
+
+/// Number of values in array or object
+ArrayIndex Value::size() const {
+ switch (type_) {
+ case nullValue:
+ case intValue:
+ case uintValue:
+ case realValue:
+ case booleanValue:
+ case stringValue:
+ return 0;
+ case arrayValue: // size of the array is highest index + 1
+ if (!value_.map_->empty()) {
+ ObjectValues::const_iterator itLast = value_.map_->end();
+ --itLast;
+ return (*itLast).first.index() + 1;
+ }
+ return 0;
+ case objectValue:
+ return ArrayIndex(value_.map_->size());
+ }
+ JSON_ASSERT_UNREACHABLE;
+ return 0; // unreachable;
+}
+
+bool Value::empty() const {
+ if (isNull() || isArray() || isObject())
+ return size() == 0u;
+ else
+ return false;
+}
+
+bool Value::operator!() const { return isNull(); }
+
+void Value::clear() {
+ JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue ||
+ type_ == objectValue,
+ "in Json::Value::clear(): requires complex value");
+ switch (type_) {
+ case arrayValue:
+ case objectValue:
+ value_.map_->clear();
+ break;
+ default:
+ break;
+ }
+}
+
+void Value::resize(ArrayIndex newSize) {
+ JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == arrayValue,
+ "in Json::Value::resize(): requires arrayValue");
+ if (type_ == nullValue)
+ *this = Value(arrayValue);
+ ArrayIndex oldSize = size();
+ if (newSize == 0)
+ clear();
+ else if (newSize > oldSize)
+ (*this)[newSize - 1];
+ else {
+ for (ArrayIndex index = newSize; index < oldSize; ++index) {
+ value_.map_->erase(index);
+ }
+ assert(size() == newSize);
+ }
+}
+
+Value& Value::operator[](ArrayIndex index) {
+ JSON_ASSERT_MESSAGE(
+ type_ == nullValue || type_ == arrayValue,
+ "in Json::Value::operator[](ArrayIndex): requires arrayValue");
+ if (type_ == nullValue)
+ *this = Value(arrayValue);
+ CZString key(index);
+ ObjectValues::iterator it = value_.map_->lower_bound(key);
+ if (it != value_.map_->end() && (*it).first == key)
+ return (*it).second;
+
+ ObjectValues::value_type defaultValue(key, nullRef);
+ it = value_.map_->insert(it, defaultValue);
+ return (*it).second;
+}
+
+Value& Value::operator[](int index) {
+ JSON_ASSERT_MESSAGE(
+ index >= 0,
+ "in Json::Value::operator[](int index): index cannot be negative");
+ return (*this)[ArrayIndex(index)];
+}
+
+const Value& Value::operator[](ArrayIndex index) const {
+ JSON_ASSERT_MESSAGE(
+ type_ == nullValue || type_ == arrayValue,
+ "in Json::Value::operator[](ArrayIndex)const: requires arrayValue");
+ if (type_ == nullValue)
+ return nullRef;
+ CZString key(index);
+ ObjectValues::const_iterator it = value_.map_->find(key);
+ if (it == value_.map_->end())
+ return nullRef;
+ return (*it).second;
+}
+
+const Value& Value::operator[](int index) const {
+ JSON_ASSERT_MESSAGE(
+ index >= 0,
+ "in Json::Value::operator[](int index) const: index cannot be negative");
+ return (*this)[ArrayIndex(index)];
+}
+
+void Value::initBasic(ValueType vtype, bool allocated) {
+ type_ = vtype;
+ allocated_ = allocated;
+ comments_ = 0;
+}
+
+// Access an object value by name, create a null member if it does not exist.
+// @pre Type of '*this' is object or null.
+// @param key is null-terminated.
+Value& Value::resolveReference(const char* key) {
+ JSON_ASSERT_MESSAGE(
+ type_ == nullValue || type_ == objectValue,
+ "in Json::Value::resolveReference(): requires objectValue");
+ if (type_ == nullValue)
+ *this = Value(objectValue);
+ CZString actualKey(
+ key, static_cast<unsigned>(strlen(key)), CZString::noDuplication); // NOTE!
+ ObjectValues::iterator it = value_.map_->lower_bound(actualKey);
+ if (it != value_.map_->end() && (*it).first == actualKey)
+ return (*it).second;
+
+ ObjectValues::value_type defaultValue(actualKey, nullRef);
+ it = value_.map_->insert(it, defaultValue);
+ Value& value = (*it).second;
+ return value;
+}
+
+// @param key is not null-terminated.
+Value& Value::resolveReference(char const* key, char const* cend)
+{
+ JSON_ASSERT_MESSAGE(
+ type_ == nullValue || type_ == objectValue,
+ "in Json::Value::resolveReference(key, end): requires objectValue");
+ if (type_ == nullValue)
+ *this = Value(objectValue);
+ CZString actualKey(
+ key, static_cast<unsigned>(cend-key), CZString::duplicateOnCopy);
+ ObjectValues::iterator it = value_.map_->lower_bound(actualKey);
+ if (it != value_.map_->end() && (*it).first == actualKey)
+ return (*it).second;
+
+ ObjectValues::value_type defaultValue(actualKey, nullRef);
+ it = value_.map_->insert(it, defaultValue);
+ Value& value = (*it).second;
+ return value;
+}
+
+Value Value::get(ArrayIndex index, const Value& defaultValue) const {
+ const Value* value = &((*this)[index]);
+ return value == &nullRef ? defaultValue : *value;
+}
+
+bool Value::isValidIndex(ArrayIndex index) const { return index < size(); }
+
+Value const* Value::find(char const* key, char const* cend) const
+{
+ JSON_ASSERT_MESSAGE(
+ type_ == nullValue || type_ == objectValue,
+ "in Json::Value::find(key, end, found): requires objectValue or nullValue");
+ if (type_ == nullValue) return NULL;
+ CZString actualKey(key, static_cast<unsigned>(cend-key), CZString::noDuplication);
+ ObjectValues::const_iterator it = value_.map_->find(actualKey);
+ if (it == value_.map_->end()) return NULL;
+ return &(*it).second;
+}
+const Value& Value::operator[](const char* key) const
+{
+ Value const* found = find(key, key + strlen(key));
+ if (!found) return nullRef;
+ return *found;
+}
+Value const& Value::operator[](std::string const& key) const
+{
+ Value const* found = find(key.data(), key.data() + key.length());
+ if (!found) return nullRef;
+ return *found;
+}
+
+Value& Value::operator[](const char* key) {
+ return resolveReference(key, key + strlen(key));
+}
+
+Value& Value::operator[](const std::string& key) {
+ return resolveReference(key.data(), key.data() + key.length());
+}
+
+Value& Value::operator[](const StaticString& key) {
+ return resolveReference(key.c_str());
+}
+
+#ifdef JSON_USE_CPPTL
+Value& Value::operator[](const CppTL::ConstString& key) {
+ return resolveReference(key.c_str(), key.end_c_str());
+}
+Value const& Value::operator[](CppTL::ConstString const& key) const
+{
+ Value const* found = find(key.c_str(), key.end_c_str());
+ if (!found) return nullRef;
+ return *found;
+}
+#endif
+
+Value& Value::append(const Value& value) { return (*this)[size()] = value; }
+
+Value Value::get(char const* key, char const* cend, Value const& defaultValue) const
+{
+ Value const* found = find(key, cend);
+ return !found ? defaultValue : *found;
+}
+Value Value::get(char const* key, Value const& defaultValue) const
+{
+ return get(key, key + strlen(key), defaultValue);
+}
+Value Value::get(std::string const& key, Value const& defaultValue) const
+{
+ return get(key.data(), key.data() + key.length(), defaultValue);
+}
+
+
+bool Value::removeMember(const char* key, const char* cend, Value* removed)
+{
+ if (type_ != objectValue) {
+ return false;
+ }
+ CZString actualKey(key, static_cast<unsigned>(cend-key), CZString::noDuplication);
+ ObjectValues::iterator it = value_.map_->find(actualKey);
+ if (it == value_.map_->end())
+ return false;
+ *removed = it->second;
+ value_.map_->erase(it);
+ return true;
+}
+bool Value::removeMember(const char* key, Value* removed)
+{
+ return removeMember(key, key + strlen(key), removed);
+}
+bool Value::removeMember(std::string const& key, Value* removed)
+{
+ return removeMember(key.data(), key.data() + key.length(), removed);
+}
+Value Value::removeMember(const char* key)
+{
+ JSON_ASSERT_MESSAGE(type_ == nullValue || type_ == objectValue,
+ "in Json::Value::removeMember(): requires objectValue");
+ if (type_ == nullValue)
+ return nullRef;
+
+ Value removed; // null
+ removeMember(key, key + strlen(key), &removed);
+ return removed; // still null if removeMember() did nothing
+}
+Value Value::removeMember(const std::string& key)
+{
+ return removeMember(key.c_str());
+}
+
+bool Value::removeIndex(ArrayIndex index, Value* removed) {
+ if (type_ != arrayValue) {
+ return false;
+ }
+ CZString key(index);
+ ObjectValues::iterator it = value_.map_->find(key);
+ if (it == value_.map_->end()) {
+ return false;
+ }
+ *removed = it->second;
+ ArrayIndex oldSize = size();
+ // shift left all items left, into the place of the "removed"
+ for (ArrayIndex i = index; i < (oldSize - 1); ++i){
+ CZString keey(i);
+ (*value_.map_)[keey] = (*this)[i + 1];
+ }
+ // erase the last one ("leftover")
+ CZString keyLast(oldSize - 1);
+ ObjectValues::iterator itLast = value_.map_->find(keyLast);
+ value_.map_->erase(itLast);
+ return true;
+}
+
+#ifdef JSON_USE_CPPTL
+Value Value::get(const CppTL::ConstString& key,
+ const Value& defaultValue) const {
+ return get(key.c_str(), key.end_c_str(), defaultValue);
+}
+#endif
+
+bool Value::isMember(char const* key, char const* cend) const
+{
+ Value const* value = find(key, cend);
+ return NULL != value;
+}
+bool Value::isMember(char const* key) const
+{
+ return isMember(key, key + strlen(key));
+}
+bool Value::isMember(std::string const& key) const
+{
+ return isMember(key.data(), key.data() + key.length());
+}
+
+#ifdef JSON_USE_CPPTL
+bool Value::isMember(const CppTL::ConstString& key) const {
+ return isMember(key.c_str(), key.end_c_str());
+}
+#endif
+
+Value::Members Value::getMemberNames() const {
+ JSON_ASSERT_MESSAGE(
+ type_ == nullValue || type_ == objectValue,
+ "in Json::Value::getMemberNames(), value must be objectValue");
+ if (type_ == nullValue)
+ return Value::Members();
+ Members members;
+ members.reserve(value_.map_->size());
+ ObjectValues::const_iterator it = value_.map_->begin();
+ ObjectValues::const_iterator itEnd = value_.map_->end();
+ for (; it != itEnd; ++it) {
+ members.push_back(std::string((*it).first.data(),
+ (*it).first.length()));
+ }
+ return members;
+}
+//
+//# ifdef JSON_USE_CPPTL
+// EnumMemberNames
+// Value::enumMemberNames() const
+//{
+// if ( type_ == objectValue )
+// {
+// return CppTL::Enum::any( CppTL::Enum::transform(
+// CppTL::Enum::keys( *(value_.map_), CppTL::Type<const CZString &>() ),
+// MemberNamesTransform() ) );
+// }
+// return EnumMemberNames();
+//}
+//
+//
+// EnumValues
+// Value::enumValues() const
+//{
+// if ( type_ == objectValue || type_ == arrayValue )
+// return CppTL::Enum::anyValues( *(value_.map_),
+// CppTL::Type<const Value &>() );
+// return EnumValues();
+//}
+//
+//# endif
+
+static bool IsIntegral(double d) {
+ double integral_part;
+ return modf(d, &integral_part) == 0.0;
+}
+
+bool Value::isNull() const { return type_ == nullValue; }
+
+bool Value::isBool() const { return type_ == booleanValue; }
+
+bool Value::isInt() const {
+ switch (type_) {
+ case intValue:
+ return value_.int_ >= minInt && value_.int_ <= maxInt;
+ case uintValue:
+ return value_.uint_ <= UInt(maxInt);
+ case realValue:
+ return value_.real_ >= minInt && value_.real_ <= maxInt &&
+ IsIntegral(value_.real_);
+ default:
+ break;
+ }
+ return false;
+}
+
+bool Value::isUInt() const {
+ switch (type_) {
+ case intValue:
+ return value_.int_ >= 0 && LargestUInt(value_.int_) <= LargestUInt(maxUInt);
+ case uintValue:
+ return value_.uint_ <= maxUInt;
+ case realValue:
+ return value_.real_ >= 0 && value_.real_ <= maxUInt &&
+ IsIntegral(value_.real_);
+ default:
+ break;
+ }
+ return false;
+}
+
+bool Value::isInt64() const {
+#if defined(JSON_HAS_INT64)
+ switch (type_) {
+ case intValue:
+ return true;
+ case uintValue:
+ return value_.uint_ <= UInt64(maxInt64);
+ case realValue:
+ // Note that maxInt64 (= 2^63 - 1) is not exactly representable as a
+ // double, so double(maxInt64) will be rounded up to 2^63. Therefore we
+ // require the value to be strictly less than the limit.
+ return value_.real_ >= double(minInt64) &&
+ value_.real_ < double(maxInt64) && IsIntegral(value_.real_);
+ default:
+ break;
+ }
+#endif // JSON_HAS_INT64
+ return false;
+}
+
+bool Value::isUInt64() const {
+#if defined(JSON_HAS_INT64)
+ switch (type_) {
+ case intValue:
+ return value_.int_ >= 0;
+ case uintValue:
+ return true;
+ case realValue:
+ // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a
+ // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we
+ // require the value to be strictly less than the limit.
+ return value_.real_ >= 0 && value_.real_ < maxUInt64AsDouble &&
+ IsIntegral(value_.real_);
+ default:
+ break;
+ }
+#endif // JSON_HAS_INT64
+ return false;
+}
+
+bool Value::isIntegral() const {
+#if defined(JSON_HAS_INT64)
+ return isInt64() || isUInt64();
+#else
+ return isInt() || isUInt();
+#endif
+}
+
+bool Value::isDouble() const { return type_ == realValue || isIntegral(); }
+
+bool Value::isNumeric() const { return isIntegral() || isDouble(); }
+
+bool Value::isString() const { return type_ == stringValue; }
+
+bool Value::isArray() const { return type_ == arrayValue; }
+
+bool Value::isObject() const { return type_ == objectValue; }
+
+void Value::setComment(const char* comment, size_t len, CommentPlacement placement) {
+ if (!comments_)
+ comments_ = new CommentInfo[numberOfCommentPlacement];
+ if ((len > 0) && (comment[len-1] == '\n')) {
+ // Always discard trailing newline, to aid indentation.
+ len -= 1;
+ }
+ comments_[placement].setComment(comment, len);
+}
+
+void Value::setComment(const char* comment, CommentPlacement placement) {
+ setComment(comment, strlen(comment), placement);
+}
+
+void Value::setComment(const std::string& comment, CommentPlacement placement) {
+ setComment(comment.c_str(), comment.length(), placement);
+}
+
+bool Value::hasComment(CommentPlacement placement) const {
+ return comments_ != 0 && comments_[placement].comment_ != 0;
+}
+
+std::string Value::getComment(CommentPlacement placement) const {
+ if (hasComment(placement))
+ return comments_[placement].comment_;
+ return "";
+}
+
+std::string Value::toStyledString() const {
+ StyledWriter writer;
+ return writer.write(*this);
+}
+
+Value::const_iterator Value::begin() const {
+ switch (type_) {
+ case arrayValue:
+ case objectValue:
+ if (value_.map_)
+ return const_iterator(value_.map_->begin());
+ break;
+ default:
+ break;
+ }
+ return const_iterator();
+}
+
+Value::const_iterator Value::end() const {
+ switch (type_) {
+ case arrayValue:
+ case objectValue:
+ if (value_.map_)
+ return const_iterator(value_.map_->end());
+ break;
+ default:
+ break;
+ }
+ return const_iterator();
+}
+
+Value::iterator Value::begin() {
+ switch (type_) {
+ case arrayValue:
+ case objectValue:
+ if (value_.map_)
+ return iterator(value_.map_->begin());
+ break;
+ default:
+ break;
+ }
+ return iterator();
+}
+
+Value::iterator Value::end() {
+ switch (type_) {
+ case arrayValue:
+ case objectValue:
+ if (value_.map_)
+ return iterator(value_.map_->end());
+ break;
+ default:
+ break;
+ }
+ return iterator();
+}
+
+// class PathArgument
+// //////////////////////////////////////////////////////////////////
+
+PathArgument::PathArgument() : key_(), index_(), kind_(kindNone) {}
+
+PathArgument::PathArgument(ArrayIndex index)
+ : key_(), index_(index), kind_(kindIndex) {}
+
+PathArgument::PathArgument(const char* key)
+ : key_(key), index_(), kind_(kindKey) {}
+
+PathArgument::PathArgument(const std::string& key)
+ : key_(key.c_str()), index_(), kind_(kindKey) {}
+
+// class Path
+// //////////////////////////////////////////////////////////////////
+
+Path::Path(const std::string& path,
+ const PathArgument& a1,
+ const PathArgument& a2,
+ const PathArgument& a3,
+ const PathArgument& a4,
+ const PathArgument& a5) {
+ InArgs in;
+ in.push_back(&a1);
+ in.push_back(&a2);
+ in.push_back(&a3);
+ in.push_back(&a4);
+ in.push_back(&a5);
+ makePath(path, in);
+}
+
+void Path::makePath(const std::string& path, const InArgs& in) {
+ const char* current = path.c_str();
+ const char* end = current + path.length();
+ InArgs::const_iterator itInArg = in.begin();
+ while (current != end) {
+ if (*current == '[') {
+ ++current;
+ if (*current == '%')
+ addPathInArg(path, in, itInArg, PathArgument::kindIndex);
+ else {
+ ArrayIndex index = 0;
+ for (; current != end && *current >= '0' && *current <= '9'; ++current)
+ index = index * 10 + ArrayIndex(*current - '0');
+ args_.push_back(index);
+ }
+ if (current == end || *current++ != ']')
+ invalidPath(path, int(current - path.c_str()));
+ } else if (*current == '%') {
+ addPathInArg(path, in, itInArg, PathArgument::kindKey);
+ ++current;
+ } else if (*current == '.') {
+ ++current;
+ } else {
+ const char* beginName = current;
+ while (current != end && !strchr("[.", *current))
+ ++current;
+ args_.push_back(std::string(beginName, current));
+ }
+ }
+}
+
+void Path::addPathInArg(const std::string& /*path*/,
+ const InArgs& in,
+ InArgs::const_iterator& itInArg,
+ PathArgument::Kind kind) {
+ if (itInArg == in.end()) {
+ // Error: missing argument %d
+ } else if ((*itInArg)->kind_ != kind) {
+ // Error: bad argument type
+ } else {
+ args_.push_back(**itInArg);
+ }
+}
+
+void Path::invalidPath(const std::string& /*path*/, int /*location*/) {
+ // Error: invalid path.
+}
+
+const Value& Path::resolve(const Value& root) const {
+ const Value* node = &root;
+ for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) {
+ const PathArgument& arg = *it;
+ if (arg.kind_ == PathArgument::kindIndex) {
+ if (!node->isArray() || !node->isValidIndex(arg.index_)) {
+ // Error: unable to resolve path (array value expected at position...
+ }
+ node = &((*node)[arg.index_]);
+ } else if (arg.kind_ == PathArgument::kindKey) {
+ if (!node->isObject()) {
+ // Error: unable to resolve path (object value expected at position...)
+ }
+ node = &((*node)[arg.key_]);
+ if (node == &Value::nullRef) {
+ // Error: unable to resolve path (object has no member named '' at
+ // position...)
+ }
+ }
+ }
+ return *node;
+}
+
+Value Path::resolve(const Value& root, const Value& defaultValue) const {
+ const Value* node = &root;
+ for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) {
+ const PathArgument& arg = *it;
+ if (arg.kind_ == PathArgument::kindIndex) {
+ if (!node->isArray() || !node->isValidIndex(arg.index_))
+ return defaultValue;
+ node = &((*node)[arg.index_]);
+ } else if (arg.kind_ == PathArgument::kindKey) {
+ if (!node->isObject())
+ return defaultValue;
+ node = &((*node)[arg.key_]);
+ if (node == &Value::nullRef)
+ return defaultValue;
+ }
+ }
+ return *node;
+}
+
+Value& Path::make(Value& root) const {
+ Value* node = &root;
+ for (Args::const_iterator it = args_.begin(); it != args_.end(); ++it) {
+ const PathArgument& arg = *it;
+ if (arg.kind_ == PathArgument::kindIndex) {
+ if (!node->isArray()) {
+ // Error: node is not an array at position ...
+ }
+ node = &((*node)[arg.index_]);
+ } else if (arg.kind_ == PathArgument::kindKey) {
+ if (!node->isObject()) {
+ // Error: node is not an object at position...
+ }
+ node = &((*node)[arg.key_]);
+ }
+ }
+ return *node;
+}
+
+} // namespace Json
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: src/lib_json/json_value.cpp
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+// //////////////////////////////////////////////////////////////////////
+// Beginning of content of file: src/lib_json/json_writer.cpp
+// //////////////////////////////////////////////////////////////////////
+
+// Copyright 2011 Baptiste Lepilleur
+// Distributed under MIT license, or public domain if desired and
+// recognized in your jurisdiction.
+// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
+
+#if !defined(JSON_IS_AMALGAMATION)
+#include <json/writer.h>
+#include "json_tool.h"
+#endif // if !defined(JSON_IS_AMALGAMATION)
+#include <iomanip>
+#include <memory>
+#include <sstream>
+#include <utility>
+#include <set>
+#include <cassert>
+#include <cstring>
+#include <cstdio>
+
+#if defined(_MSC_VER) && _MSC_VER >= 1200 && _MSC_VER < 1800 // Between VC++ 6.0 and VC++ 11.0
+#include <float.h>
+#define isfinite _finite
+#elif defined(__sun) && defined(__SVR4) //Solaris
+#include <ieeefp.h>
+#define isfinite finite
+#else
+#include <cmath>
+#define isfinite std::isfinite
+#endif
+
+#if defined(_MSC_VER)
+#if !defined(WINCE) && defined(__STDC_SECURE_LIB__) && _MSC_VER >= 1500 // VC++ 9.0 and above
+#define snprintf sprintf_s
+#elif _MSC_VER >= 1900 // VC++ 14.0 and above
+#define snprintf std::snprintf
+#else
+#define snprintf _snprintf
+#endif
+#elif defined(__ANDROID__)
+#define snprintf snprintf
+#elif __cplusplus >= 201103L
+#define snprintf std::snprintf
+#endif
+
+#if defined(__BORLANDC__)
+#include <float.h>
+#define isfinite _finite
+#define snprintf _snprintf
+#endif
+
+#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0
+// Disable warning about strdup being deprecated.
+#pragma warning(disable : 4996)
+#endif
+
+namespace Json {
+
+typedef std::auto_ptr<StreamWriter> StreamWriterPtr;
+
+static bool containsControlCharacter(const char* str) {
+ while (*str) {
+ if (isControlCharacter(*(str++)))
+ return true;
+ }
+ return false;
+}
+
+static bool containsControlCharacter0(const char* str, unsigned len) {
+ char const* end = str + len;
+ while (end != str) {
+ if (isControlCharacter(*str) || 0==*str)
+ return true;
+ ++str;
+ }
+ return false;
+}
+
+std::string valueToString(LargestInt value) {
+ UIntToStringBuffer buffer;
+ char* current = buffer + sizeof(buffer);
+ if (value == Value::minLargestInt) {
+ uintToString(LargestUInt(Value::maxLargestInt) + 1, current);
+ *--current = '-';
+ } else if (value < 0) {
+ uintToString(LargestUInt(-value), current);
+ *--current = '-';
+ } else {
+ uintToString(LargestUInt(value), current);
+ }
+ assert(current >= buffer);
+ return current;
+}
+
+std::string valueToString(LargestUInt value) {
+ UIntToStringBuffer buffer;
+ char* current = buffer + sizeof(buffer);
+ uintToString(value, current);
+ assert(current >= buffer);
+ return current;
+}
+
+#if defined(JSON_HAS_INT64)
+
+std::string valueToString(Int value) {
+ return valueToString(LargestInt(value));
+}
+
+std::string valueToString(UInt value) {
+ return valueToString(LargestUInt(value));
+}
+
+#endif // # if defined(JSON_HAS_INT64)
+
+std::string valueToString(double value, bool useSpecialFloats, unsigned int precision) {
+ // Allocate a buffer that is more than large enough to store the 16 digits of
+ // precision requested below.
+ char buffer[32];
+ int len = -1;
+
+ char formatString[6];
+ sprintf(formatString, "%%.%dg", precision);
+
+ // Print into the buffer. We need not request the alternative representation
+ // that always has a decimal point because JSON doesn't distingish the
+ // concepts of reals and integers.
+ if (isfinite(value)) {
+ len = snprintf(buffer, sizeof(buffer), formatString, value);
+ } else {
+ // IEEE standard states that NaN values will not compare to themselves
+ if (value != value) {
+ len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "NaN" : "null");
+ } else if (value < 0) {
+ len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "-Infinity" : "-1e+9999");
+ } else {
+ len = snprintf(buffer, sizeof(buffer), useSpecialFloats ? "Infinity" : "1e+9999");
+ }
+ // For those, we do not need to call fixNumLoc, but it is fast.
+ }
+ assert(len >= 0);
+ fixNumericLocale(buffer, buffer + len);
+ return buffer;
+}
+
+std::string valueToString(double value) { return valueToString(value, false, 17); }
+
+std::string valueToString(bool value) { return value ? "true" : "false"; }
+
+std::string valueToQuotedString(const char* value) {
+ if (value == NULL)
+ return "";
+ // Not sure how to handle unicode...
+ if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL &&
+ !containsControlCharacter(value))
+ return std::string("\"") + value + "\"";
+ // We have to walk value and escape any special characters.
+ // Appending to std::string is not efficient, but this should be rare.
+ // (Note: forward slashes are *not* rare, but I am not escaping them.)
+ std::string::size_type maxsize =
+ strlen(value) * 2 + 3; // allescaped+quotes+NULL
+ std::string result;
+ result.reserve(maxsize); // to avoid lots of mallocs
+ result += "\"";
+ for (const char* c = value; *c != 0; ++c) {
+ switch (*c) {
+ case '\"':
+ result += "\\\"";
+ break;
+ case '\\':
+ result += "\\\\";
+ break;
+ case '\b':
+ result += "\\b";
+ break;
+ case '\f':
+ result += "\\f";
+ break;
+ case '\n':
+ result += "\\n";
+ break;
+ case '\r':
+ result += "\\r";
+ break;
+ case '\t':
+ result += "\\t";
+ break;
+ // case '/':
+ // Even though \/ is considered a legal escape in JSON, a bare
+ // slash is also legal, so I see no reason to escape it.
+ // (I hope I am not misunderstanding something.
+ // blep notes: actually escaping \/ may be useful in javascript to avoid </
+ // sequence.
+ // Should add a flag to allow this compatibility mode and prevent this
+ // sequence from occurring.
+ default:
+ if (isControlCharacter(*c)) {
+ std::ostringstream oss;
+ oss << "\\u" << std::hex << std::uppercase << std::setfill('0')
+ << std::setw(4) << static_cast<int>(*c);
+ result += oss.str();
+ } else {
+ result += *c;
+ }
+ break;
+ }
+ }
+ result += "\"";
+ return result;
+}
+
+// https://github.com/upcaste/upcaste/blob/master/src/upcore/src/cstring/strnpbrk.cpp
+static char const* strnpbrk(char const* s, char const* accept, size_t n) {
+ assert((s || !n) && accept);
+
+ char const* const end = s + n;
+ for (char const* cur = s; cur < end; ++cur) {
+ int const c = *cur;
+ for (char const* a = accept; *a; ++a) {
+ if (*a == c) {
+ return cur;
+ }
+ }
+ }
+ return NULL;
+}
+static std::string valueToQuotedStringN(const char* value, unsigned length) {
+ if (value == NULL)
+ return "";
+ // Not sure how to handle unicode...
+ if (strnpbrk(value, "\"\\\b\f\n\r\t", length) == NULL &&
+ !containsControlCharacter0(value, length))
+ return std::string("\"") + value + "\"";
+ // We have to walk value and escape any special characters.
+ // Appending to std::string is not efficient, but this should be rare.
+ // (Note: forward slashes are *not* rare, but I am not escaping them.)
+ std::string::size_type maxsize =
+ length * 2 + 3; // allescaped+quotes+NULL
+ std::string result;
+ result.reserve(maxsize); // to avoid lots of mallocs
+ result += "\"";
+ char const* end = value + length;
+ for (const char* c = value; c != end; ++c) {
+ switch (*c) {
+ case '\"':
+ result += "\\\"";
+ break;
+ case '\\':
+ result += "\\\\";
+ break;
+ case '\b':
+ result += "\\b";
+ break;
+ case '\f':
+ result += "\\f";
+ break;
+ case '\n':
+ result += "\\n";
+ break;
+ case '\r':
+ result += "\\r";
+ break;
+ case '\t':
+ result += "\\t";
+ break;
+ // case '/':
+ // Even though \/ is considered a legal escape in JSON, a bare
+ // slash is also legal, so I see no reason to escape it.
+ // (I hope I am not misunderstanding something.)
+ // blep notes: actually escaping \/ may be useful in javascript to avoid </
+ // sequence.
+ // Should add a flag to allow this compatibility mode and prevent this
+ // sequence from occurring.
+ default:
+ if ((isControlCharacter(*c)) || (*c == 0)) {
+ std::ostringstream oss;
+ oss << "\\u" << std::hex << std::uppercase << std::setfill('0')
+ << std::setw(4) << static_cast<int>(*c);
+ result += oss.str();
+ } else {
+ result += *c;
+ }
+ break;
+ }
+ }
+ result += "\"";
+ return result;
+}
+
+// Class Writer
+// //////////////////////////////////////////////////////////////////
+Writer::~Writer() {}
+
+// Class FastWriter
+// //////////////////////////////////////////////////////////////////
+
+FastWriter::FastWriter()
+ : yamlCompatiblityEnabled_(false) {}
+
+void FastWriter::enableYAMLCompatibility() { yamlCompatiblityEnabled_ = true; }
+
+std::string FastWriter::write(const Value& root) {
+ document_ = "";
+ writeValue(root);
+ document_ += "\n";
+ return document_;
+}
+
+void FastWriter::writeValue(const Value& value) {
+ switch (value.type()) {
+ case nullValue:
+ document_ += "null";
+ break;
+ case intValue:
+ document_ += valueToString(value.asLargestInt());
+ break;
+ case uintValue:
+ document_ += valueToString(value.asLargestUInt());
+ break;
+ case realValue:
+ document_ += valueToString(value.asDouble());
+ break;
+ case stringValue:
+ {
+ // Is NULL possible for value.string_?
+ char const* str;
+ char const* end;
+ bool ok = value.getString(&str, &end);
+ if (ok) document_ += valueToQuotedStringN(str, static_cast<unsigned>(end-str));
+ break;
+ }
+ case booleanValue:
+ document_ += valueToString(value.asBool());
+ break;
+ case arrayValue: {
+ document_ += '[';
+ int size = value.size();
+ for (int index = 0; index < size; ++index) {
+ if (index > 0)
+ document_ += ',';
+ writeValue(value[index]);
+ }
+ document_ += ']';
+ } break;
+ case objectValue: {
+ Value::Members members(value.getMemberNames());
+ document_ += '{';
+ for (Value::Members::iterator it = members.begin(); it != members.end();
+ ++it) {
+ const std::string& name = *it;
+ if (it != members.begin())
+ document_ += ',';
+ document_ += valueToQuotedStringN(name.data(), static_cast<unsigned>(name.length()));
+ document_ += yamlCompatiblityEnabled_ ? ": " : ":";
+ writeValue(value[name]);
+ }
+ document_ += '}';
+ } break;
+ }
+}
+
+// Class StyledWriter
+// //////////////////////////////////////////////////////////////////
+
+StyledWriter::StyledWriter()
+ : rightMargin_(74), indentSize_(3), addChildValues_() {}
+
+std::string StyledWriter::write(const Value& root) {
+ document_ = "";
+ addChildValues_ = false;
+ indentString_ = "";
+ writeCommentBeforeValue(root);
+ writeValue(root);
+ writeCommentAfterValueOnSameLine(root);
+ document_ += "\n";
+ return document_;
+}
+
+void StyledWriter::writeValue(const Value& value) {
+ switch (value.type()) {
+ case nullValue:
+ pushValue("null");
+ break;
+ case intValue:
+ pushValue(valueToString(value.asLargestInt()));
+ break;
+ case uintValue:
+ pushValue(valueToString(value.asLargestUInt()));
+ break;
+ case realValue:
+ pushValue(valueToString(value.asDouble()));
+ break;
+ case stringValue:
+ {
+ // Is NULL possible for value.string_?
+ char const* str;
+ char const* end;
+ bool ok = value.getString(&str, &end);
+ if (ok) pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end-str)));
+ else pushValue("");
+ break;
+ }
+ case booleanValue:
+ pushValue(valueToString(value.asBool()));
+ break;
+ case arrayValue:
+ writeArrayValue(value);
+ break;
+ case objectValue: {
+ Value::Members members(value.getMemberNames());
+ if (members.empty())
+ pushValue("{}");
+ else {
+ writeWithIndent("{");
+ indent();
+ Value::Members::iterator it = members.begin();
+ for (;;) {
+ const std::string& name = *it;
+ const Value& childValue = value[name];
+ writeCommentBeforeValue(childValue);
+ writeWithIndent(valueToQuotedString(name.c_str()));
+ document_ += " : ";
+ writeValue(childValue);
+ if (++it == members.end()) {
+ writeCommentAfterValueOnSameLine(childValue);
+ break;
+ }
+ document_ += ',';
+ writeCommentAfterValueOnSameLine(childValue);
+ }
+ unindent();
+ writeWithIndent("}");
+ }
+ } break;
+ }
+}
+
+void StyledWriter::writeArrayValue(const Value& value) {
+ unsigned size = value.size();
+ if (size == 0)
+ pushValue("[]");
+ else {
+ bool isArrayMultiLine = isMultineArray(value);
+ if (isArrayMultiLine) {
+ writeWithIndent("[");
+ indent();
+ bool hasChildValue = !childValues_.empty();
+ unsigned index = 0;
+ for (;;) {
+ const Value& childValue = value[index];
+ writeCommentBeforeValue(childValue);
+ if (hasChildValue)
+ writeWithIndent(childValues_[index]);
+ else {
+ writeIndent();
+ writeValue(childValue);
+ }
+ if (++index == size) {
+ writeCommentAfterValueOnSameLine(childValue);
+ break;
+ }
+ document_ += ',';
+ writeCommentAfterValueOnSameLine(childValue);
+ }
+ unindent();
+ writeWithIndent("]");
+ } else // output on a single line
+ {
+ assert(childValues_.size() == size);
+ document_ += "[ ";
+ for (unsigned index = 0; index < size; ++index) {
+ if (index > 0)
+ document_ += ", ";
+ document_ += childValues_[index];
+ }
+ document_ += " ]";
+ }
+ }
+}
+
+bool StyledWriter::isMultineArray(const Value& value) {
+ int size = value.size();
+ bool isMultiLine = size * 3 >= rightMargin_;
+ childValues_.clear();
+ for (int index = 0; index < size && !isMultiLine; ++index) {
+ const Value& childValue = value[index];
+ isMultiLine =
+ isMultiLine || ((childValue.isArray() || childValue.isObject()) &&
+ childValue.size() > 0);
+ }
+ if (!isMultiLine) // check if line length > max line length
+ {
+ childValues_.reserve(size);
+ addChildValues_ = true;
+ int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
+ for (int index = 0; index < size; ++index) {
+ if (hasCommentForValue(value[index])) {
+ isMultiLine = true;
+ }
+ writeValue(value[index]);
+ lineLength += int(childValues_[index].length());
+ }
+ addChildValues_ = false;
+ isMultiLine = isMultiLine || lineLength >= rightMargin_;
+ }
+ return isMultiLine;
+}
+
+void StyledWriter::pushValue(const std::string& value) {
+ if (addChildValues_)
+ childValues_.push_back(value);
+ else
+ document_ += value;
+}
+
+void StyledWriter::writeIndent() {
+ if (!document_.empty()) {
+ char last = document_[document_.length() - 1];
+ if (last == ' ') // already indented
+ return;
+ if (last != '\n') // Comments may add new-line
+ document_ += '\n';
+ }
+ document_ += indentString_;
+}
+
+void StyledWriter::writeWithIndent(const std::string& value) {
+ writeIndent();
+ document_ += value;
+}
+
+void StyledWriter::indent() { indentString_ += std::string(indentSize_, ' '); }
+
+void StyledWriter::unindent() {
+ assert(int(indentString_.size()) >= indentSize_);
+ indentString_.resize(indentString_.size() - indentSize_);
+}
+
+void StyledWriter::writeCommentBeforeValue(const Value& root) {
+ if (!root.hasComment(commentBefore))
+ return;
+
+ document_ += "\n";
+ writeIndent();
+ const std::string& comment = root.getComment(commentBefore);
+ std::string::const_iterator iter = comment.begin();
+ while (iter != comment.end()) {
+ document_ += *iter;
+ if (*iter == '\n' &&
+ (iter != comment.end() && *(iter + 1) == '/'))
+ writeIndent();
+ ++iter;
+ }
+
+ // Comments are stripped of trailing newlines, so add one here
+ document_ += "\n";
+}
+
+void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) {
+ if (root.hasComment(commentAfterOnSameLine))
+ document_ += " " + root.getComment(commentAfterOnSameLine);
+
+ if (root.hasComment(commentAfter)) {
+ document_ += "\n";
+ document_ += root.getComment(commentAfter);
+ document_ += "\n";
+ }
+}
+
+bool StyledWriter::hasCommentForValue(const Value& value) {
+ return value.hasComment(commentBefore) ||
+ value.hasComment(commentAfterOnSameLine) ||
+ value.hasComment(commentAfter);
+}
+
+// Class StyledStreamWriter
+// //////////////////////////////////////////////////////////////////
+
+StyledStreamWriter::StyledStreamWriter(std::string indentation)
+ : document_(NULL), rightMargin_(74), indentation_(indentation),
+ addChildValues_() {}
+
+void StyledStreamWriter::write(std::ostream& out, const Value& root) {
+ document_ = &out;
+ addChildValues_ = false;
+ indentString_ = "";
+ indented_ = true;
+ writeCommentBeforeValue(root);
+ if (!indented_) writeIndent();
+ indented_ = true;
+ writeValue(root);
+ writeCommentAfterValueOnSameLine(root);
+ *document_ << "\n";
+ document_ = NULL; // Forget the stream, for safety.
+}
+
+void StyledStreamWriter::writeValue(const Value& value) {
+ switch (value.type()) {
+ case nullValue:
+ pushValue("null");
+ break;
+ case intValue:
+ pushValue(valueToString(value.asLargestInt()));
+ break;
+ case uintValue:
+ pushValue(valueToString(value.asLargestUInt()));
+ break;
+ case realValue:
+ pushValue(valueToString(value.asDouble()));
+ break;
+ case stringValue:
+ {
+ // Is NULL possible for value.string_?
+ char const* str;
+ char const* end;
+ bool ok = value.getString(&str, &end);
+ if (ok) pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end-str)));
+ else pushValue("");
+ break;
+ }
+ case booleanValue:
+ pushValue(valueToString(value.asBool()));
+ break;
+ case arrayValue:
+ writeArrayValue(value);
+ break;
+ case objectValue: {
+ Value::Members members(value.getMemberNames());
+ if (members.empty())
+ pushValue("{}");
+ else {
+ writeWithIndent("{");
+ indent();
+ Value::Members::iterator it = members.begin();
+ for (;;) {
+ const std::string& name = *it;
+ const Value& childValue = value[name];
+ writeCommentBeforeValue(childValue);
+ writeWithIndent(valueToQuotedString(name.c_str()));
+ *document_ << " : ";
+ writeValue(childValue);
+ if (++it == members.end()) {
+ writeCommentAfterValueOnSameLine(childValue);
+ break;
+ }
+ *document_ << ",";
+ writeCommentAfterValueOnSameLine(childValue);
+ }
+ unindent();
+ writeWithIndent("}");
+ }
+ } break;
+ }
+}
+
+void StyledStreamWriter::writeArrayValue(const Value& value) {
+ unsigned size = value.size();
+ if (size == 0)
+ pushValue("[]");
+ else {
+ bool isArrayMultiLine = isMultineArray(value);
+ if (isArrayMultiLine) {
+ writeWithIndent("[");
+ indent();
+ bool hasChildValue = !childValues_.empty();
+ unsigned index = 0;
+ for (;;) {
+ const Value& childValue = value[index];
+ writeCommentBeforeValue(childValue);
+ if (hasChildValue)
+ writeWithIndent(childValues_[index]);
+ else {
+ if (!indented_) writeIndent();
+ indented_ = true;
+ writeValue(childValue);
+ indented_ = false;
+ }
+ if (++index == size) {
+ writeCommentAfterValueOnSameLine(childValue);
+ break;
+ }
+ *document_ << ",";
+ writeCommentAfterValueOnSameLine(childValue);
+ }
+ unindent();
+ writeWithIndent("]");
+ } else // output on a single line
+ {
+ assert(childValues_.size() == size);
+ *document_ << "[ ";
+ for (unsigned index = 0; index < size; ++index) {
+ if (index > 0)
+ *document_ << ", ";
+ *document_ << childValues_[index];
+ }
+ *document_ << " ]";
+ }
+ }
+}
+
+bool StyledStreamWriter::isMultineArray(const Value& value) {
+ int size = value.size();
+ bool isMultiLine = size * 3 >= rightMargin_;
+ childValues_.clear();
+ for (int index = 0; index < size && !isMultiLine; ++index) {
+ const Value& childValue = value[index];
+ isMultiLine =
+ isMultiLine || ((childValue.isArray() || childValue.isObject()) &&
+ childValue.size() > 0);
+ }
+ if (!isMultiLine) // check if line length > max line length
+ {
+ childValues_.reserve(size);
+ addChildValues_ = true;
+ int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
+ for (int index = 0; index < size; ++index) {
+ if (hasCommentForValue(value[index])) {
+ isMultiLine = true;
+ }
+ writeValue(value[index]);
+ lineLength += int(childValues_[index].length());
+ }
+ addChildValues_ = false;
+ isMultiLine = isMultiLine || lineLength >= rightMargin_;
+ }
+ return isMultiLine;
+}
+
+void StyledStreamWriter::pushValue(const std::string& value) {
+ if (addChildValues_)
+ childValues_.push_back(value);
+ else
+ *document_ << value;
+}
+
+void StyledStreamWriter::writeIndent() {
+ // blep intended this to look at the so-far-written string
+ // to determine whether we are already indented, but
+ // with a stream we cannot do that. So we rely on some saved state.
+ // The caller checks indented_.
+ *document_ << '\n' << indentString_;
+}
+
+void StyledStreamWriter::writeWithIndent(const std::string& value) {
+ if (!indented_) writeIndent();
+ *document_ << value;
+ indented_ = false;
+}
+
+void StyledStreamWriter::indent() { indentString_ += indentation_; }
+
+void StyledStreamWriter::unindent() {
+ assert(indentString_.size() >= indentation_.size());
+ indentString_.resize(indentString_.size() - indentation_.size());
+}
+
+void StyledStreamWriter::writeCommentBeforeValue(const Value& root) {
+ if (!root.hasComment(commentBefore))
+ return;
+
+ if (!indented_) writeIndent();
+ const std::string& comment = root.getComment(commentBefore);
+ std::string::const_iterator iter = comment.begin();
+ while (iter != comment.end()) {
+ *document_ << *iter;
+ if (*iter == '\n' &&
+ (iter != comment.end() && *(iter + 1) == '/'))
+ // writeIndent(); // would include newline
+ *document_ << indentString_;
+ ++iter;
+ }
+ indented_ = false;
+}
+
+void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) {
+ if (root.hasComment(commentAfterOnSameLine))
+ *document_ << ' ' << root.getComment(commentAfterOnSameLine);
+
+ if (root.hasComment(commentAfter)) {
+ writeIndent();
+ *document_ << root.getComment(commentAfter);
+ }
+ indented_ = false;
+}
+
+bool StyledStreamWriter::hasCommentForValue(const Value& value) {
+ return value.hasComment(commentBefore) ||
+ value.hasComment(commentAfterOnSameLine) ||
+ value.hasComment(commentAfter);
+}
+
+//////////////////////////
+// BuiltStyledStreamWriter
+
+/// Scoped enums are not available until C++11.
+struct CommentStyle {
+ /// Decide whether to write comments.
+ enum Enum {
+ None, ///< Drop all comments.
+ Most, ///< Recover odd behavior of previous versions (not implemented yet).
+ All ///< Keep all comments.
+ };
+};
+
+struct BuiltStyledStreamWriter : public StreamWriter
+{
+ BuiltStyledStreamWriter(
+ std::string const& indentation,
+ CommentStyle::Enum cs,
+ std::string const& colonSymbol,
+ std::string const& nullSymbol,
+ std::string const& endingLineFeedSymbol,
+ bool useSpecialFloats,
+ unsigned int precision);
+ virtual int write(Value const& root, std::ostream* sout);
+private:
+ void writeValue(Value const& value);
+ void writeArrayValue(Value const& value);
+ bool isMultineArray(Value const& value);
+ void pushValue(std::string const& value);
+ void writeIndent();
+ void writeWithIndent(std::string const& value);
+ void indent();
+ void unindent();
+ void writeCommentBeforeValue(Value const& root);
+ void writeCommentAfterValueOnSameLine(Value const& root);
+ static bool hasCommentForValue(const Value& value);
+
+ typedef std::vector<std::string> ChildValues;
+
+ ChildValues childValues_;
+ std::string indentString_;
+ int rightMargin_;
+ std::string indentation_;
+ CommentStyle::Enum cs_;
+ std::string colonSymbol_;
+ std::string nullSymbol_;
+ std::string endingLineFeedSymbol_;
+ bool addChildValues_ : 1;
+ bool indented_ : 1;
+ bool useSpecialFloats_ : 1;
+ unsigned int precision_;
+};
+BuiltStyledStreamWriter::BuiltStyledStreamWriter(
+ std::string const& indentation,
+ CommentStyle::Enum cs,
+ std::string const& colonSymbol,
+ std::string const& nullSymbol,
+ std::string const& endingLineFeedSymbol,
+ bool useSpecialFloats,
+ unsigned int precision)
+ : rightMargin_(74)
+ , indentation_(indentation)
+ , cs_(cs)
+ , colonSymbol_(colonSymbol)
+ , nullSymbol_(nullSymbol)
+ , endingLineFeedSymbol_(endingLineFeedSymbol)
+ , addChildValues_(false)
+ , indented_(false)
+ , useSpecialFloats_(useSpecialFloats)
+ , precision_(precision)
+{
+}
+int BuiltStyledStreamWriter::write(Value const& root, std::ostream* sout)
+{
+ sout_ = sout;
+ addChildValues_ = false;
+ indented_ = true;
+ indentString_ = "";
+ writeCommentBeforeValue(root);
+ if (!indented_) writeIndent();
+ indented_ = true;
+ writeValue(root);
+ writeCommentAfterValueOnSameLine(root);
+ *sout_ << endingLineFeedSymbol_;
+ sout_ = NULL;
+ return 0;
+}
+void BuiltStyledStreamWriter::writeValue(Value const& value) {
+ switch (value.type()) {
+ case nullValue:
+ pushValue(nullSymbol_);
+ break;
+ case intValue:
+ pushValue(valueToString(value.asLargestInt()));
+ break;
+ case uintValue:
+ pushValue(valueToString(value.asLargestUInt()));
+ break;
+ case realValue:
+ pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_));
+ break;
+ case stringValue:
+ {
+ // Is NULL is possible for value.string_?
+ char const* str;
+ char const* end;
+ bool ok = value.getString(&str, &end);
+ if (ok) pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end-str)));
+ else pushValue("");
+ break;
+ }
+ case booleanValue:
+ pushValue(valueToString(value.asBool()));
+ break;
+ case arrayValue:
+ writeArrayValue(value);
+ break;
+ case objectValue: {
+ Value::Members members(value.getMemberNames());
+ if (members.empty())
+ pushValue("{}");
+ else {
+ writeWithIndent("{");
+ indent();
+ Value::Members::iterator it = members.begin();
+ for (;;) {
+ std::string const& name = *it;
+ Value const& childValue = value[name];
+ writeCommentBeforeValue(childValue);
+ writeWithIndent(valueToQuotedStringN(name.data(), static_cast<unsigned>(name.length())));
+ *sout_ << colonSymbol_;
+ writeValue(childValue);
+ if (++it == members.end()) {
+ writeCommentAfterValueOnSameLine(childValue);
+ break;
+ }
+ *sout_ << ",";
+ writeCommentAfterValueOnSameLine(childValue);
+ }
+ unindent();
+ writeWithIndent("}");
+ }
+ } break;
+ }
+}
+
+void BuiltStyledStreamWriter::writeArrayValue(Value const& value) {
+ unsigned size = value.size();
+ if (size == 0)
+ pushValue("[]");
+ else {
+ bool isMultiLine = (cs_ == CommentStyle::All) || isMultineArray(value);
+ if (isMultiLine) {
+ writeWithIndent("[");
+ indent();
+ bool hasChildValue = !childValues_.empty();
+ unsigned index = 0;
+ for (;;) {
+ Value const& childValue = value[index];
+ writeCommentBeforeValue(childValue);
+ if (hasChildValue)
+ writeWithIndent(childValues_[index]);
+ else {
+ if (!indented_) writeIndent();
+ indented_ = true;
+ writeValue(childValue);
+ indented_ = false;
+ }
+ if (++index == size) {
+ writeCommentAfterValueOnSameLine(childValue);
+ break;
+ }
+ *sout_ << ",";
+ writeCommentAfterValueOnSameLine(childValue);
+ }
+ unindent();
+ writeWithIndent("]");
+ } else // output on a single line
+ {
+ assert(childValues_.size() == size);
+ *sout_ << "[";
+ if (!indentation_.empty()) *sout_ << " ";
+ for (unsigned index = 0; index < size; ++index) {
+ if (index > 0)
+ *sout_ << ", ";
+ *sout_ << childValues_[index];
+ }
+ if (!indentation_.empty()) *sout_ << " ";
+ *sout_ << "]";
+ }
+ }
+}
+
+bool BuiltStyledStreamWriter::isMultineArray(Value const& value) {
+ int size = value.size();
+ bool isMultiLine = size * 3 >= rightMargin_;
+ childValues_.clear();
+ for (int index = 0; index < size && !isMultiLine; ++index) {
+ Value const& childValue = value[index];
+ isMultiLine =
+ isMultiLine || ((childValue.isArray() || childValue.isObject()) &&
+ childValue.size() > 0);
+ }
+ if (!isMultiLine) // check if line length > max line length
+ {
+ childValues_.reserve(size);
+ addChildValues_ = true;
+ int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
+ for (int index = 0; index < size; ++index) {
+ if (hasCommentForValue(value[index])) {
+ isMultiLine = true;
+ }
+ writeValue(value[index]);
+ lineLength += int(childValues_[index].length());
+ }
+ addChildValues_ = false;
+ isMultiLine = isMultiLine || lineLength >= rightMargin_;
+ }
+ return isMultiLine;
+}
+
+void BuiltStyledStreamWriter::pushValue(std::string const& value) {
+ if (addChildValues_)
+ childValues_.push_back(value);
+ else
+ *sout_ << value;
+}
+
+void BuiltStyledStreamWriter::writeIndent() {
+ // blep intended this to look at the so-far-written string
+ // to determine whether we are already indented, but
+ // with a stream we cannot do that. So we rely on some saved state.
+ // The caller checks indented_.
+
+ if (!indentation_.empty()) {
+ // In this case, drop newlines too.
+ *sout_ << '\n' << indentString_;
+ }
+}
+
+void BuiltStyledStreamWriter::writeWithIndent(std::string const& value) {
+ if (!indented_) writeIndent();
+ *sout_ << value;
+ indented_ = false;
+}
+
+void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; }
+
+void BuiltStyledStreamWriter::unindent() {
+ assert(indentString_.size() >= indentation_.size());
+ indentString_.resize(indentString_.size() - indentation_.size());
+}
+
+void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) {
+ if (cs_ == CommentStyle::None) return;
+ if (!root.hasComment(commentBefore))
+ return;
+
+ if (!indented_) writeIndent();
+ const std::string& comment = root.getComment(commentBefore);
+ std::string::const_iterator iter = comment.begin();
+ while (iter != comment.end()) {
+ *sout_ << *iter;
+ if (*iter == '\n' &&
+ (iter != comment.end() && *(iter + 1) == '/'))
+ // writeIndent(); // would write extra newline
+ *sout_ << indentString_;
+ ++iter;
+ }
+ indented_ = false;
+}
+
+void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(Value const& root) {
+ if (cs_ == CommentStyle::None) return;
+ if (root.hasComment(commentAfterOnSameLine))
+ *sout_ << " " + root.getComment(commentAfterOnSameLine);
+
+ if (root.hasComment(commentAfter)) {
+ writeIndent();
+ *sout_ << root.getComment(commentAfter);
+ }
+}
+
+// static
+bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) {
+ return value.hasComment(commentBefore) ||
+ value.hasComment(commentAfterOnSameLine) ||
+ value.hasComment(commentAfter);
+}
+
+///////////////
+// StreamWriter
+
+StreamWriter::StreamWriter()
+ : sout_(NULL)
+{
+}
+StreamWriter::~StreamWriter()
+{
+}
+StreamWriter::Factory::~Factory()
+{}
+StreamWriterBuilder::StreamWriterBuilder()
+{
+ setDefaults(&settings_);
+}
+StreamWriterBuilder::~StreamWriterBuilder()
+{}
+StreamWriter* StreamWriterBuilder::newStreamWriter() const
+{
+ std::string indentation = settings_["indentation"].asString();
+ std::string cs_str = settings_["commentStyle"].asString();
+ bool eyc = settings_["enableYAMLCompatibility"].asBool();
+ bool dnp = settings_["dropNullPlaceholders"].asBool();
+ bool usf = settings_["useSpecialFloats"].asBool();
+ unsigned int pre = settings_["precision"].asUInt();
+ CommentStyle::Enum cs = CommentStyle::All;
+ if (cs_str == "All") {
+ cs = CommentStyle::All;
+ } else if (cs_str == "None") {
+ cs = CommentStyle::None;
+ } else {
+ throwRuntimeError("commentStyle must be 'All' or 'None'");
+ }
+ std::string colonSymbol = " : ";
+ if (eyc) {
+ colonSymbol = ": ";
+ } else if (indentation.empty()) {
+ colonSymbol = ":";
+ }
+ std::string nullSymbol = "null";
+ if (dnp) {
+ nullSymbol = "";
+ }
+ if (pre > 17) pre = 17;
+ std::string endingLineFeedSymbol = "";
+ return new BuiltStyledStreamWriter(
+ indentation, cs,
+ colonSymbol, nullSymbol, endingLineFeedSymbol, usf, pre);
+}
+static void getValidWriterKeys(std::set<std::string>* valid_keys)
+{
+ valid_keys->clear();
+ valid_keys->insert("indentation");
+ valid_keys->insert("commentStyle");
+ valid_keys->insert("enableYAMLCompatibility");
+ valid_keys->insert("dropNullPlaceholders");
+ valid_keys->insert("useSpecialFloats");
+ valid_keys->insert("precision");
+}
+bool StreamWriterBuilder::validate(Json::Value* invalid) const
+{
+ Json::Value my_invalid;
+ if (!invalid) invalid = &my_invalid; // so we do not need to test for NULL
+ Json::Value& inv = *invalid;
+ std::set<std::string> valid_keys;
+ getValidWriterKeys(&valid_keys);
+ Value::Members keys = settings_.getMemberNames();
+ size_t n = keys.size();
+ for (size_t i = 0; i < n; ++i) {
+ std::string const& key = keys[i];
+ if (valid_keys.find(key) == valid_keys.end()) {
+ inv[key] = settings_[key];
+ }
+ }
+ return 0u == inv.size();
+}
+Value& StreamWriterBuilder::operator[](std::string key)
+{
+ return settings_[key];
+}
+// static
+void StreamWriterBuilder::setDefaults(Json::Value* settings)
+{
+ //! [StreamWriterBuilderDefaults]
+ (*settings)["commentStyle"] = "All";
+ (*settings)["indentation"] = "\t";
+ (*settings)["enableYAMLCompatibility"] = false;
+ (*settings)["dropNullPlaceholders"] = false;
+ (*settings)["useSpecialFloats"] = false;
+ (*settings)["precision"] = 17;
+ //! [StreamWriterBuilderDefaults]
+}
+
+std::string writeString(StreamWriter::Factory const& builder, Value const& root) {
+ std::ostringstream sout;
+ StreamWriterPtr const writer(builder.newStreamWriter());
+ writer->write(root, &sout);
+ return sout.str();
+}
+
+std::ostream& operator<<(std::ostream& sout, Value const& root) {
+ StreamWriterBuilder builder;
+ StreamWriterPtr const writer(builder.newStreamWriter());
+ writer->write(root, &sout);
+ return sout;
+}
+
+} // namespace Json
+
+// //////////////////////////////////////////////////////////////////////
+// End of content of file: src/lib_json/json_writer.cpp
+// //////////////////////////////////////////////////////////////////////
+
+
+
+
+
diff --git a/lib/lua/CMakeLists.txt b/lib/lua/CMakeLists.txt
new file mode 100644
index 000000000..119dd6302
--- /dev/null
+++ b/lib/lua/CMakeLists.txt
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 2.4 FATAL_ERROR)
+
+project(lua C)
+
+set(LUA_VERSION_MAJOR 5)
+set(LUA_VERSION_MINOR 1)
+set(LUA_VERSION_PATCH 4)
+set(LUA_VERSION "${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}.${LUA_VERSION_PATCH}")
+
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
+
+set(COMMON_CFLAGS)
+set(COMMON_LDFLAGS)
+set(LIBS)
+
+if(APPLE)
+ set(DEFAULT_POSIX TRUE)
+ set(DEFAULT_DLOPEN ON)
+ # use this on Mac OS X 10.3-
+ option(LUA_USE_MACOSX "Mac OS X 10.3-" OFF)
+elseif(UNIX OR CYGWIN)
+ set(DEFAULT_POSIX TRUE)
+elseif(WIN32)
+ set(LUA_WIN TRUE)
+ set(COMMON_CFLAGS "${COMMON_CFLAGS} -DLUA_BUILD_AS_DLL")
+else()
+ set(DEFAULT_ANSI TRUE)
+endif()
+
+if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ set(COMMON_LDFLAGS "${COMMON_LDFLAGS} -Wl,-E -lm")
+ set(DEFAULT_DLOPEN ON)
+endif()
+
+# For "Mac OS X 10.3-"
+if(LUA_USE_MACOSX)
+ set(COMMON_CFLAGS "${COMMON_CFLAGS} -DLUA_USE_MACOSX")
+ set(LUA_USE_DLOPEN FALSE)
+endif(LUA_USE_MACOSX)
+
+option(LUA_USE_DLOPEN "Enable dlopen support." ${DEFAULT_DLOPEN})
+mark_as_advanced(LUA_USE_DLOPEN)
+
+option(LUA_ANSI "Disable non-ANSI features." ${DEFAULT_ANSI})
+mark_as_advanced(LUA_ANSI)
+
+if(LUA_USE_DLOPEN)
+ set(COMMON_CFLAGS "${COMMON_CFLAGS} -DLUA_USE_DLOPEN")
+ if(NOT APPLE)
+ set(COMMON_LDFLAGS "${COMMON_LDFLAGS} -ldl ")
+ endif(NOT APPLE)
+endif(LUA_USE_DLOPEN)
+
+if(DEFAULT_POSIX)
+ set(COMMON_CFLAGS "${COMMON_CFLAGS} -DLUA_USE_POSIX")
+endif(DEFAULT_POSIX)
+
+if(LUA_ANSI)
+ set(COMMON_CFLAGS "${COMMON_CFLAGS} -DLUA_ANSI")
+endif(LUA_ANSI)
+
+# COMMON_CFLAGS has no effect without this line
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_CFLAGS}")
+
+
+# Standard flags to use for each build type.
+if(CMAKE_COMPILER_IS_GNUCC)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe -Wall -Wextra -Wshadow -W -pedantic -std=gnu99")
+ set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2")
+ set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0 -g")
+ set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_PROFILE} -O1 -g")
+ set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_WITHDEBINFO} -O2 -g")
+endif(CMAKE_COMPILER_IS_GNUCC)
+
+
+add_subdirectory(src build)
+
diff --git a/lib/lua/COPYRIGHT b/lib/lua/COPYRIGHT
new file mode 100644
index 000000000..3a53e741e
--- /dev/null
+++ b/lib/lua/COPYRIGHT
@@ -0,0 +1,34 @@
+Lua License
+-----------
+
+Lua is licensed under the terms of the MIT license reproduced below.
+This means that Lua is free software and can be used for both academic
+and commercial purposes at absolutely no cost.
+
+For details and rationale, see http://www.lua.org/license.html .
+
+===============================================================================
+
+Copyright (C) 1994-2008 Lua.org, PUC-Rio.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+===============================================================================
+
+(end of COPYRIGHT)
diff --git a/lib/lua/src/CMakeLists.txt b/lib/lua/src/CMakeLists.txt
new file mode 100644
index 000000000..8f6cc1213
--- /dev/null
+++ b/lib/lua/src/CMakeLists.txt
@@ -0,0 +1,54 @@
+
+# Lua core source files.
+set(LUA_CORE_SRC
+ lapi.c
+ lauxlib.c
+ lbaselib.c
+ lcode.c
+ ldblib.c
+ ldebug.c
+ ldo.c
+ ldump.c
+ lfunc.c
+ lgc.c
+ linit.c
+ liolib.c
+ llex.c
+ lmathlib.c
+ lmem.c
+ loadlib.c
+ lobject.c
+ lopcodes.c
+ loslib.c
+ lparser.c
+ lstate.c
+ lstring.c
+ lstrlib.c
+ ltable.c
+ ltablib.c
+ ltm.c
+ lundump.c
+ lvm.c
+ lzio.c
+)
+set(LUA_LIB_HEADERS
+ lua.h
+ lualib.h
+ lauxlib.h
+ luaconf.h
+)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR})
+
+# Lua library.
+add_library(lua STATIC ${LUA_CORE_SRC})
+target_link_libraries(lua ${LIBS})
+set(LUA_STATIC_LIB lua)
+set(LUA_LIBS lua)
+
+set_target_properties(${LUA_LIBS} PROPERTIES
+ VERSION ${LUA_VERSION}
+ CLEAN_DIRECT_OUTPUT 1
+)
+
diff --git a/lib/lua/src/lapi.c b/lib/lua/src/lapi.c
new file mode 100644
index 000000000..5d5145d2e
--- /dev/null
+++ b/lib/lua/src/lapi.c
@@ -0,0 +1,1087 @@
+/*
+** $Id: lapi.c,v 2.55.1.5 2008/07/04 18:41:18 roberto Exp $
+** Lua API
+** See Copyright Notice in lua.h
+*/
+
+
+#include <assert.h>
+#include <math.h>
+#include <stdarg.h>
+#include <string.h>
+
+#define lapi_c
+#define LUA_CORE
+
+#include "lua.h"
+
+#include "lapi.h"
+#include "ldebug.h"
+#include "ldo.h"
+#include "lfunc.h"
+#include "lgc.h"
+#include "lmem.h"
+#include "lobject.h"
+#include "lstate.h"
+#include "lstring.h"
+#include "ltable.h"
+#include "ltm.h"
+#include "lundump.h"
+#include "lvm.h"
+
+
+
+const char lua_ident[] =
+ "$Lua: " LUA_RELEASE " " LUA_COPYRIGHT " $\n"
+ "$Authors: " LUA_AUTHORS " $\n"
+ "$URL: www.lua.org $\n";
+
+
+
+#define api_checknelems(L, n) api_check(L, (n) <= (L->top - L->base))
+
+#define api_checkvalidindex(L, i) api_check(L, (i) != luaO_nilobject)
+
+#define api_incr_top(L) {api_check(L, L->top < L->ci->top); L->top++;}
+
+
+
+static TValue *index2adr (lua_State *L, int idx) {
+ if (idx > 0) {
+ TValue *o = L->base + (idx - 1);
+ api_check(L, idx <= L->ci->top - L->base);
+ if (o >= L->top) return cast(TValue *, luaO_nilobject);
+ else return o;
+ }
+ else if (idx > LUA_REGISTRYINDEX) {
+ api_check(L, idx != 0 && -idx <= L->top - L->base);
+ return L->top + idx;
+ }
+ else switch (idx) { /* pseudo-indices */
+ case LUA_REGISTRYINDEX: return registry(L);
+ case LUA_ENVIRONINDEX: {
+ Closure *func = curr_func(L);
+ sethvalue(L, &L->env, func->c.env);
+ return &L->env;
+ }
+ case LUA_GLOBALSINDEX: return gt(L);
+ default: {
+ Closure *func = curr_func(L);
+ idx = LUA_GLOBALSINDEX - idx;
+ return (idx <= func->c.nupvalues)
+ ? &func->c.upvalue[idx-1]
+ : cast(TValue *, luaO_nilobject);
+ }
+ }
+}
+
+
+static Table *getcurrenv (lua_State *L) {
+ if (L->ci == L->base_ci) /* no enclosing function? */
+ return hvalue(gt(L)); /* use global table as environment */
+ else {
+ Closure *func = curr_func(L);
+ return func->c.env;
+ }
+}
+
+
+void luaA_pushobject (lua_State *L, const TValue *o) {
+ setobj2s(L, L->top, o);
+ api_incr_top(L);
+}
+
+
+LUA_API int lua_checkstack (lua_State *L, int size) {
+ int res = 1;
+ lua_lock(L);
+ if (size > LUAI_MAXCSTACK || (L->top - L->base + size) > LUAI_MAXCSTACK)
+ res = 0; /* stack overflow */
+ else if (size > 0) {
+ luaD_checkstack(L, size);
+ if (L->ci->top < L->top + size)
+ L->ci->top = L->top + size;
+ }
+ lua_unlock(L);
+ return res;
+}
+
+
+LUA_API void lua_xmove (lua_State *from, lua_State *to, int n) {
+ int i;
+ if (from == to) return;
+ lua_lock(to);
+ api_checknelems(from, n);
+ api_check(from, G(from) == G(to));
+ api_check(from, to->ci->top - to->top >= n);
+ from->top -= n;
+ for (i = 0; i < n; i++) {
+ setobj2s(to, to->top++, from->top + i);
+ }
+ lua_unlock(to);
+}
+
+
+LUA_API void lua_setlevel (lua_State *from, lua_State *to) {
+ to->nCcalls = from->nCcalls;
+}
+
+
+LUA_API lua_CFunction lua_atpanic (lua_State *L, lua_CFunction panicf) {
+ lua_CFunction old;
+ lua_lock(L);
+ old = G(L)->panic;
+ G(L)->panic = panicf;
+ lua_unlock(L);
+ return old;
+}
+
+
+LUA_API lua_State *lua_newthread (lua_State *L) {
+ lua_State *L1;
+ lua_lock(L);
+ luaC_checkGC(L);
+ L1 = luaE_newthread(L);
+ setthvalue(L, L->top, L1);
+ api_incr_top(L);
+ lua_unlock(L);
+ luai_userstatethread(L, L1);
+ return L1;
+}
+
+
+
+/*
+** basic stack manipulation
+*/
+
+
+LUA_API int lua_gettop (lua_State *L) {
+ return cast_int(L->top - L->base);
+}
+
+
+LUA_API void lua_settop (lua_State *L, int idx) {
+ lua_lock(L);
+ if (idx >= 0) {
+ api_check(L, idx <= L->stack_last - L->base);
+ while (L->top < L->base + idx)
+ setnilvalue(L->top++);
+ L->top = L->base + idx;
+ }
+ else {
+ api_check(L, -(idx+1) <= (L->top - L->base));
+ L->top += idx+1; /* `subtract' index (index is negative) */
+ }
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_remove (lua_State *L, int idx) {
+ StkId p;
+ lua_lock(L);
+ p = index2adr(L, idx);
+ api_checkvalidindex(L, p);
+ while (++p < L->top) setobjs2s(L, p-1, p);
+ L->top--;
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_insert (lua_State *L, int idx) {
+ StkId p;
+ StkId q;
+ lua_lock(L);
+ p = index2adr(L, idx);
+ api_checkvalidindex(L, p);
+ for (q = L->top; q>p; q--) setobjs2s(L, q, q-1);
+ setobjs2s(L, p, L->top);
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_replace (lua_State *L, int idx) {
+ StkId o;
+ lua_lock(L);
+ /* explicit test for incompatible code */
+ if (idx == LUA_ENVIRONINDEX && L->ci == L->base_ci)
+ luaG_runerror(L, "no calling environment");
+ api_checknelems(L, 1);
+ o = index2adr(L, idx);
+ api_checkvalidindex(L, o);
+ if (idx == LUA_ENVIRONINDEX) {
+ Closure *func = curr_func(L);
+ api_check(L, ttistable(L->top - 1));
+ func->c.env = hvalue(L->top - 1);
+ luaC_barrier(L, func, L->top - 1);
+ }
+ else {
+ setobj(L, o, L->top - 1);
+ if (idx < LUA_GLOBALSINDEX) /* function upvalue? */
+ luaC_barrier(L, curr_func(L), L->top - 1);
+ }
+ L->top--;
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_pushvalue (lua_State *L, int idx) {
+ lua_lock(L);
+ setobj2s(L, L->top, index2adr(L, idx));
+ api_incr_top(L);
+ lua_unlock(L);
+}
+
+
+
+/*
+** access functions (stack -> C)
+*/
+
+
+LUA_API int lua_type (lua_State *L, int idx) {
+ StkId o = index2adr(L, idx);
+ return (o == luaO_nilobject) ? LUA_TNONE : ttype(o);
+}
+
+
+LUA_API const char *lua_typename (lua_State *L, int t) {
+ UNUSED(L);
+ return (t == LUA_TNONE) ? "no value" : luaT_typenames[t];
+}
+
+
+LUA_API int lua_iscfunction (lua_State *L, int idx) {
+ StkId o = index2adr(L, idx);
+ return iscfunction(o);
+}
+
+
+LUA_API int lua_isnumber (lua_State *L, int idx) {
+ TValue n;
+ const TValue *o = index2adr(L, idx);
+ return tonumber(o, &n);
+}
+
+
+LUA_API int lua_isstring (lua_State *L, int idx) {
+ int t = lua_type(L, idx);
+ return (t == LUA_TSTRING || t == LUA_TNUMBER);
+}
+
+
+LUA_API int lua_isuserdata (lua_State *L, int idx) {
+ const TValue *o = index2adr(L, idx);
+ return (ttisuserdata(o) || ttislightuserdata(o));
+}
+
+
+LUA_API int lua_rawequal (lua_State *L, int index1, int index2) {
+ StkId o1 = index2adr(L, index1);
+ StkId o2 = index2adr(L, index2);
+ return (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0
+ : luaO_rawequalObj(o1, o2);
+}
+
+
+LUA_API int lua_equal (lua_State *L, int index1, int index2) {
+ StkId o1, o2;
+ int i;
+ lua_lock(L); /* may call tag method */
+ o1 = index2adr(L, index1);
+ o2 = index2adr(L, index2);
+ i = (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0 : equalobj(L, o1, o2);
+ lua_unlock(L);
+ return i;
+}
+
+
+LUA_API int lua_lessthan (lua_State *L, int index1, int index2) {
+ StkId o1, o2;
+ int i;
+ lua_lock(L); /* may call tag method */
+ o1 = index2adr(L, index1);
+ o2 = index2adr(L, index2);
+ i = (o1 == luaO_nilobject || o2 == luaO_nilobject) ? 0
+ : luaV_lessthan(L, o1, o2);
+ lua_unlock(L);
+ return i;
+}
+
+
+
+LUA_API lua_Number lua_tonumber (lua_State *L, int idx) {
+ TValue n;
+ const TValue *o = index2adr(L, idx);
+ if (tonumber(o, &n))
+ return nvalue(o);
+ else
+ return 0;
+}
+
+
+LUA_API lua_Integer lua_tointeger (lua_State *L, int idx) {
+ TValue n;
+ const TValue *o = index2adr(L, idx);
+ if (tonumber(o, &n)) {
+ lua_Integer res;
+ lua_Number num = nvalue(o);
+ lua_number2integer(res, num);
+ return res;
+ }
+ else
+ return 0;
+}
+
+
+LUA_API int lua_toboolean (lua_State *L, int idx) {
+ const TValue *o = index2adr(L, idx);
+ return !l_isfalse(o);
+}
+
+
+LUA_API const char *lua_tolstring (lua_State *L, int idx, size_t *len) {
+ StkId o = index2adr(L, idx);
+ if (!ttisstring(o)) {
+ lua_lock(L); /* `luaV_tostring' may create a new string */
+ if (!luaV_tostring(L, o)) { /* conversion failed? */
+ if (len != NULL) *len = 0;
+ lua_unlock(L);
+ return NULL;
+ }
+ luaC_checkGC(L);
+ o = index2adr(L, idx); /* previous call may reallocate the stack */
+ lua_unlock(L);
+ }
+ if (len != NULL) *len = tsvalue(o)->len;
+ return svalue(o);
+}
+
+
+LUA_API size_t lua_objlen (lua_State *L, int idx) {
+ StkId o = index2adr(L, idx);
+ switch (ttype(o)) {
+ case LUA_TSTRING: return tsvalue(o)->len;
+ case LUA_TUSERDATA: return uvalue(o)->len;
+ case LUA_TTABLE: return luaH_getn(hvalue(o));
+ case LUA_TNUMBER: {
+ size_t l;
+ lua_lock(L); /* `luaV_tostring' may create a new string */
+ l = (luaV_tostring(L, o) ? tsvalue(o)->len : 0);
+ lua_unlock(L);
+ return l;
+ }
+ default: return 0;
+ }
+}
+
+
+LUA_API lua_CFunction lua_tocfunction (lua_State *L, int idx) {
+ StkId o = index2adr(L, idx);
+ return (!iscfunction(o)) ? NULL : clvalue(o)->c.f;
+}
+
+
+LUA_API void *lua_touserdata (lua_State *L, int idx) {
+ StkId o = index2adr(L, idx);
+ switch (ttype(o)) {
+ case LUA_TUSERDATA: return (rawuvalue(o) + 1);
+ case LUA_TLIGHTUSERDATA: return pvalue(o);
+ default: return NULL;
+ }
+}
+
+
+LUA_API lua_State *lua_tothread (lua_State *L, int idx) {
+ StkId o = index2adr(L, idx);
+ return (!ttisthread(o)) ? NULL : thvalue(o);
+}
+
+
+LUA_API const void *lua_topointer (lua_State *L, int idx) {
+ StkId o = index2adr(L, idx);
+ switch (ttype(o)) {
+ case LUA_TTABLE: return hvalue(o);
+ case LUA_TFUNCTION: return clvalue(o);
+ case LUA_TTHREAD: return thvalue(o);
+ case LUA_TUSERDATA:
+ case LUA_TLIGHTUSERDATA:
+ return lua_touserdata(L, idx);
+ default: return NULL;
+ }
+}
+
+
+
+/*
+** push functions (C -> stack)
+*/
+
+
+LUA_API void lua_pushnil (lua_State *L) {
+ lua_lock(L);
+ setnilvalue(L->top);
+ api_incr_top(L);
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_pushnumber (lua_State *L, lua_Number n) {
+ lua_lock(L);
+ setnvalue(L->top, n);
+ api_incr_top(L);
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_pushinteger (lua_State *L, lua_Integer n) {
+ lua_lock(L);
+ setnvalue(L->top, cast_num(n));
+ api_incr_top(L);
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_pushlstring (lua_State *L, const char *s, size_t len) {
+ lua_lock(L);
+ luaC_checkGC(L);
+ setsvalue2s(L, L->top, luaS_newlstr(L, s, len));
+ api_incr_top(L);
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_pushstring (lua_State *L, const char *s) {
+ if (s == NULL)
+ lua_pushnil(L);
+ else
+ lua_pushlstring(L, s, strlen(s));
+}
+
+
+LUA_API const char *lua_pushvfstring (lua_State *L, const char *fmt,
+ va_list argp) {
+ const char *ret;
+ lua_lock(L);
+ luaC_checkGC(L);
+ ret = luaO_pushvfstring(L, fmt, argp);
+ lua_unlock(L);
+ return ret;
+}
+
+
+LUA_API const char *lua_pushfstring (lua_State *L, const char *fmt, ...) {
+ const char *ret;
+ va_list argp;
+ lua_lock(L);
+ luaC_checkGC(L);
+ va_start(argp, fmt);
+ ret = luaO_pushvfstring(L, fmt, argp);
+ va_end(argp);
+ lua_unlock(L);
+ return ret;
+}
+
+
+LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) {
+ Closure *cl;
+ lua_lock(L);
+ luaC_checkGC(L);
+ api_checknelems(L, n);
+ cl = luaF_newCclosure(L, n, getcurrenv(L));
+ cl->c.f = fn;
+ L->top -= n;
+ while (n--)
+ setobj2n(L, &cl->c.upvalue[n], L->top+n);
+ setclvalue(L, L->top, cl);
+ lua_assert(iswhite(obj2gco(cl)));
+ api_incr_top(L);
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_pushboolean (lua_State *L, int b) {
+ lua_lock(L);
+ setbvalue(L->top, (b != 0)); /* ensure that true is 1 */
+ api_incr_top(L);
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_pushlightuserdata (lua_State *L, void *p) {
+ lua_lock(L);
+ setpvalue(L->top, p);
+ api_incr_top(L);
+ lua_unlock(L);
+}
+
+
+LUA_API int lua_pushthread (lua_State *L) {
+ lua_lock(L);
+ setthvalue(L, L->top, L);
+ api_incr_top(L);
+ lua_unlock(L);
+ return (G(L)->mainthread == L);
+}
+
+
+
+/*
+** get functions (Lua -> stack)
+*/
+
+
+LUA_API void lua_gettable (lua_State *L, int idx) {
+ StkId t;
+ lua_lock(L);
+ t = index2adr(L, idx);
+ api_checkvalidindex(L, t);
+ luaV_gettable(L, t, L->top - 1, L->top - 1);
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_getfield (lua_State *L, int idx, const char *k) {
+ StkId t;
+ TValue key;
+ lua_lock(L);
+ t = index2adr(L, idx);
+ api_checkvalidindex(L, t);
+ setsvalue(L, &key, luaS_new(L, k));
+ luaV_gettable(L, t, &key, L->top);
+ api_incr_top(L);
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_rawget (lua_State *L, int idx) {
+ StkId t;
+ lua_lock(L);
+ t = index2adr(L, idx);
+ api_check(L, ttistable(t));
+ setobj2s(L, L->top - 1, luaH_get(hvalue(t), L->top - 1));
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_rawgeti (lua_State *L, int idx, int n) {
+ StkId o;
+ lua_lock(L);
+ o = index2adr(L, idx);
+ api_check(L, ttistable(o));
+ setobj2s(L, L->top, luaH_getnum(hvalue(o), n));
+ api_incr_top(L);
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_createtable (lua_State *L, int narray, int nrec) {
+ lua_lock(L);
+ luaC_checkGC(L);
+ sethvalue(L, L->top, luaH_new(L, narray, nrec));
+ api_incr_top(L);
+ lua_unlock(L);
+}
+
+
+LUA_API int lua_getmetatable (lua_State *L, int objindex) {
+ const TValue *obj;
+ Table *mt = NULL;
+ int res;
+ lua_lock(L);
+ obj = index2adr(L, objindex);
+ switch (ttype(obj)) {
+ case LUA_TTABLE:
+ mt = hvalue(obj)->metatable;
+ break;
+ case LUA_TUSERDATA:
+ mt = uvalue(obj)->metatable;
+ break;
+ default:
+ mt = G(L)->mt[ttype(obj)];
+ break;
+ }
+ if (mt == NULL)
+ res = 0;
+ else {
+ sethvalue(L, L->top, mt);
+ api_incr_top(L);
+ res = 1;
+ }
+ lua_unlock(L);
+ return res;
+}
+
+
+LUA_API void lua_getfenv (lua_State *L, int idx) {
+ StkId o;
+ lua_lock(L);
+ o = index2adr(L, idx);
+ api_checkvalidindex(L, o);
+ switch (ttype(o)) {
+ case LUA_TFUNCTION:
+ sethvalue(L, L->top, clvalue(o)->c.env);
+ break;
+ case LUA_TUSERDATA:
+ sethvalue(L, L->top, uvalue(o)->env);
+ break;
+ case LUA_TTHREAD:
+ setobj2s(L, L->top, gt(thvalue(o)));
+ break;
+ default:
+ setnilvalue(L->top);
+ break;
+ }
+ api_incr_top(L);
+ lua_unlock(L);
+}
+
+
+/*
+** set functions (stack -> Lua)
+*/
+
+
+LUA_API void lua_settable (lua_State *L, int idx) {
+ StkId t;
+ lua_lock(L);
+ api_checknelems(L, 2);
+ t = index2adr(L, idx);
+ api_checkvalidindex(L, t);
+ luaV_settable(L, t, L->top - 2, L->top - 1);
+ L->top -= 2; /* pop index and value */
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_setfield (lua_State *L, int idx, const char *k) {
+ StkId t;
+ TValue key;
+ lua_lock(L);
+ api_checknelems(L, 1);
+ t = index2adr(L, idx);
+ api_checkvalidindex(L, t);
+ setsvalue(L, &key, luaS_new(L, k));
+ luaV_settable(L, t, &key, L->top - 1);
+ L->top--; /* pop value */
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_rawset (lua_State *L, int idx) {
+ StkId t;
+ lua_lock(L);
+ api_checknelems(L, 2);
+ t = index2adr(L, idx);
+ api_check(L, ttistable(t));
+ setobj2t(L, luaH_set(L, hvalue(t), L->top-2), L->top-1);
+ luaC_barriert(L, hvalue(t), L->top-1);
+ L->top -= 2;
+ lua_unlock(L);
+}
+
+
+LUA_API void lua_rawseti (lua_State *L, int idx, int n) {
+ StkId o;
+ lua_lock(L);
+ api_checknelems(L, 1);
+ o = index2adr(L, idx);
+ api_check(L, ttistable(o));
+ setobj2t(L, luaH_setnum(L, hvalue(o), n), L->top-1);
+ luaC_barriert(L, hvalue(o), L->top-1);
+ L->top--;
+ lua_unlock(L);
+}
+
+
+LUA_API int lua_setmetatable (lua_State *L, int objindex) {
+ TValue *obj;
+ Table *mt;
+ lua_lock(L);
+ api_checknelems(L, 1);
+ obj = index2adr(L, objindex);
+ api_checkvalidindex(L, obj);
+ if (ttisnil(L->top - 1))
+ mt = NULL;
+ else {
+ api_check(L, ttistable(L->top - 1));
+ mt = hvalue(L->top - 1);
+ }
+ switch (ttype(obj)) {
+ case LUA_TTABLE: {
+ hvalue(obj)->metatable = mt;
+ if (mt)
+ luaC_objbarriert(L, hvalue(obj), mt);
+ break;
+ }
+ case LUA_TUSERDATA: {
+ uvalue(obj)->metatable = mt;
+ if (mt)
+ luaC_objbarrier(L, rawuvalue(obj), mt);
+ break;
+ }
+ default: {
+ G(L)->mt[ttype(obj)] = mt;
+ break;
+ }
+ }
+ L->top--;
+ lua_unlock(L);
+ return 1;
+}
+
+
+LUA_API int lua_setfenv (lua_State *L, int idx) {
+ StkId o;
+ int res = 1;
+ lua_lock(L);
+ api_checknelems(L, 1);
+ o = index2adr(L, idx);
+ api_checkvalidindex(L, o);
+ api_check(L, ttistable(L->top - 1));
+ switch (ttype(o)) {
+ case LUA_TFUNCTION:
+ clvalue(o)->c.env = hvalue(L->top - 1);
+ break;
+ case LUA_TUSERDATA:
+ uvalue(o)->env = hvalue(L->top - 1);
+ break;
+ case LUA_TTHREAD:
+ sethvalue(L, gt(thvalue(o)), hvalue(L->top - 1));
+ break;
+ default:
+ res = 0;
+ break;
+ }
+ if (res) luaC_objbarrier(L, gcvalue(o), hvalue(L->top - 1));
+ L->top--;
+ lua_unlock(L);
+ return res;
+}
+
+
+/*
+** `load' and `call' functions (run Lua code)
+*/
+
+
+#define adjustresults(L,nres) \
+ { if (nres == LUA_MULTRET && L->top >= L->ci->top) L->ci->top = L->top; }
+
+
+#define checkresults(L,na,nr) \
+ api_check(L, (nr) == LUA_MULTRET || (L->ci->top - L->top >= (nr) - (na)))
+
+
+LUA_API void lua_call (lua_State *L, int nargs, int nresults) {
+ StkId func;
+ lua_lock(L);
+ api_checknelems(L, nargs+1);
+ checkresults(L, nargs, nresults);
+ func = L->top - (nargs+1);
+ luaD_call(L, func, nresults);
+ adjustresults(L, nresults);
+ lua_unlock(L);
+}
+
+
+
+/*
+** Execute a protected call.
+*/
+struct CallS { /* data to `f_call' */
+ StkId func;
+ int nresults;
+};
+
+
+static void f_call (lua_State *L, void *ud) {
+ struct CallS *c = cast(struct CallS *, ud);
+ luaD_call(L, c->func, c->nresults);
+}
+
+
+
+LUA_API int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc) {
+ struct CallS c;
+ int status;
+ ptrdiff_t func;
+ lua_lock(L);
+ api_checknelems(L, nargs+1);
+ checkresults(L, nargs, nresults);
+ if (errfunc == 0)
+ func = 0;
+ else {
+ StkId o = index2adr(L, errfunc);
+ api_checkvalidindex(L, o);
+ func = savestack(L, o);
+ }
+ c.func = L->top - (nargs+1); /* function to be called */
+ c.nresults = nresults;
+ status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
+ adjustresults(L, nresults);
+ lua_unlock(L);
+ return status;
+}
+
+
+/*
+** Execute a protected C call.
+*/
+struct CCallS { /* data to `f_Ccall' */
+ lua_CFunction func;
+ void *ud;
+};
+
+
+static void f_Ccall (lua_State *L, void *ud) {
+ struct CCallS *c = cast(struct CCallS *, ud);
+ Closure *cl;
+ cl = luaF_newCclosure(L, 0, getcurrenv(L));
+ cl->c.f = c->func;
+ setclvalue(L, L->top, cl); /* push function */
+ api_incr_top(L);
+ setpvalue(L->top, c->ud); /* push only argument */
+ api_incr_top(L);
+ luaD_call(L, L->top - 2, 0);
+}
+
+
+LUA_API int lua_cpcall (lua_State *L, lua_CFunction func, void *ud) {
+ struct CCallS c;
+ int status;
+ lua_lock(L);
+ c.func = func;
+ c.ud = ud;
+ status = luaD_pcall(L, f_Ccall, &c, savestack(L, L->top), 0);
+ lua_unlock(L);
+ return status;
+}
+
+
+LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data,
+ const char *chunkname) {
+ ZIO z;
+ int status;
+ lua_lock(L);
+ if (!chunkname) chunkname = "?";
+ luaZ_init(L, &z, reader, data);
+ status = luaD_protectedparser(L, &z, chunkname);
+ lua_unlock(L);
+ return status;
+}
+
+
+LUA_API int lua_dump (lua_State *L, lua_Writer writer, void *data) {
+ int status;
+ TValue *o;
+ lua_lock(L);
+ api_checknelems(L, 1);
+ o = L->top - 1;
+ if (isLfunction(o))
+ status = luaU_dump(L, clvalue(o)->l.p, writer, data, 0);
+ else
+ status = 1;
+ lua_unlock(L);
+ return status;
+}
+
+
+LUA_API int lua_status (lua_State *L) {
+ return L->status;
+}
+
+
+/*
+** Garbage-collection function
+*/
+
+LUA_API int lua_gc (lua_State *L, int what, int data) {
+ int res = 0;
+ global_State *g;
+ lua_lock(L);
+ g = G(L);
+ switch (what) {
+ case LUA_GCSTOP: {
+ g->GCthreshold = MAX_LUMEM;
+ break;
+ }
+ case LUA_GCRESTART: {
+ g->GCthreshold = g->totalbytes;
+ break;
+ }
+ case LUA_GCCOLLECT: {
+ luaC_fullgc(L);
+ break;
+ }
+ case LUA_GCCOUNT: {
+ /* GC values are expressed in Kbytes: #bytes/2^10 */
+ res = cast_int(g->totalbytes >> 10);
+ break;
+ }
+ case LUA_GCCOUNTB: {
+ res = cast_int(g->totalbytes & 0x3ff);
+ break;
+ }
+ case LUA_GCSTEP: {
+ lu_mem a = (cast(lu_mem, data) << 10);
+ if (a <= g->totalbytes)
+ g->GCthreshold = g->totalbytes - a;
+ else
+ g->GCthreshold = 0;
+ while (g->GCthreshold <= g->totalbytes) {
+ luaC_step(L);
+ if (g->gcstate == GCSpause) { /* end of cycle? */
+ res = 1; /* signal it */
+ break;
+ }
+ }
+ break;
+ }
+ case LUA_GCSETPAUSE: {
+ res = g->gcpause;
+ g->gcpause = data;
+ break;
+ }
+ case LUA_GCSETSTEPMUL: {
+ res = g->gcstepmul;
+ g->gcstepmul = data;
+ break;
+ }
+ default: res = -1; /* invalid option */
+ }
+ lua_unlock(L);
+ return res;
+}
+
+
+
+/*
+** miscellaneous functions
+*/
+
+
+LUA_API int lua_error (lua_State *L) {
+ lua_lock(L);
+ api_checknelems(L, 1);
+ luaG_errormsg(L);
+ lua_unlock(L);
+ return 0; /* to avoid warnings */
+}
+
+
+LUA_API int lua_next (lua_State *L, int idx) {
+ StkId t;
+ int more;
+ lua_lock(L);
+ t = index2adr(L, idx);
+ api_check(L, ttistable(t));
+ more = luaH_next(L, hvalue(t), L->top - 1);
+ if (more) {
+ api_incr_top(L);
+ }
+ else /* no more elements */
+ L->top -= 1; /* remove key */
+ lua_unlock(L);
+ return more;
+}
+
+
+LUA_API void lua_concat (lua_State *L, int n) {
+ lua_lock(L);
+ api_checknelems(L, n);
+ if (n >= 2) {
+ luaC_checkGC(L);
+ luaV_concat(L, n, cast_int(L->top - L->base) - 1);
+ L->top -= (n-1);
+ }
+ else if (n == 0) { /* push empty string */
+ setsvalue2s(L, L->top, luaS_newlstr(L, "", 0));
+ api_incr_top(L);
+ }
+ /* else n == 1; nothing to do */
+ lua_unlock(L);
+}
+
+
+LUA_API lua_Alloc lua_getallocf (lua_State *L, void **ud) {
+ lua_Alloc f;
+ lua_lock(L);
+ if (ud) *ud = G(L)->ud;
+ f = G(L)->frealloc;
+ lua_unlock(L);
+ return f;
+}
+
+
+LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud) {
+ lua_lock(L);
+ G(L)->ud = ud;
+ G(L)->frealloc = f;
+ lua_unlock(L);
+}
+
+
+LUA_API void *lua_newuserdata (lua_State *L, size_t size) {
+ Udata *u;
+ lua_lock(L);
+ luaC_checkGC(L);
+ u = luaS_newudata(L, size, getcurrenv(L));
+ setuvalue(L, L->top, u);
+ api_incr_top(L);
+ lua_unlock(L);
+ return u + 1;
+}
+
+
+
+
+static const char *aux_upvalue (StkId fi, int n, TValue **val) {
+ Closure *f;
+ if (!ttisfunction(fi)) return NULL;
+ f = clvalue(fi);
+ if (f->c.isC) {
+ if (!(1 <= n && n <= f->c.nupvalues)) return NULL;
+ *val = &f->c.upvalue[n-1];
+ return "";
+ }
+ else {
+ Proto *p = f->l.p;
+ if (!(1 <= n && n <= p->sizeupvalues)) return NULL;
+ *val = f->l.upvals[n-1]->v;
+ return getstr(p->upvalues[n-1]);
+ }
+}
+
+
+LUA_API const char *lua_getupvalue (lua_State *L, int funcindex, int n) {
+ const char *name;
+ TValue *val;
+ lua_lock(L);
+ name = aux_upvalue(index2adr(L, funcindex), n, &val);
+ if (name) {
+ setobj2s(L, L->top, val);
+ api_incr_top(L);
+ }
+ lua_unlock(L);
+ return name;
+}
+
+
+LUA_API const char *lua_setupvalue (lua_State *L, int funcindex, int n) {
+ const char *name;
+ TValue *val;
+ StkId fi;
+ lua_lock(L);
+ fi = index2adr(L, funcindex);
+ api_checknelems(L, 1);
+ name = aux_upvalue(fi, n, &val);
+ if (name) {
+ L->top--;
+ setobj(L, val, L->top);
+ luaC_barrier(L, clvalue(fi), L->top);
+ }
+ lua_unlock(L);
+ return name;
+}
+
diff --git a/lib/lua/src/lapi.h b/lib/lua/src/lapi.h
new file mode 100644
index 000000000..2c3fab244
--- /dev/null
+++ b/lib/lua/src/lapi.h
@@ -0,0 +1,16 @@
+/*
+** $Id: lapi.h,v 2.2.1.1 2007/12/27 13:02:25 roberto Exp $
+** Auxiliary functions from Lua API
+** See Copyright Notice in lua.h
+*/
+
+#ifndef lapi_h
+#define lapi_h
+
+
+#include "lobject.h"
+
+
+LUAI_FUNC void luaA_pushobject (lua_State *L, const TValue *o);
+
+#endif
diff --git a/lib/lua/src/lauxlib.c b/lib/lua/src/lauxlib.c
new file mode 100644
index 000000000..be41ebcd3
--- /dev/null
+++ b/lib/lua/src/lauxlib.c
@@ -0,0 +1,653 @@
+/*
+** $Id: lauxlib.c,v 1.159.1.3 2008/01/21 13:20:51 roberto Exp $
+** Auxiliary functions for building Lua libraries
+** See Copyright Notice in lua.h
+*/
+
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/* This file uses only the official API of Lua.
+** Any function declared here could be written as an application function.
+*/
+
+#define lauxlib_c
+#define LUA_LIB
+
+#include "lua.h"
+
+#include "lauxlib.h"
+
+
+#define FREELIST_REF 0 /* free list of references */
+
+
+/* convert a stack index to positive */
+#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : \
+ lua_gettop(L) + (i) + 1)
+
+
+/*
+** {======================================================
+** Error-report functions
+** =======================================================
+*/
+
+
+LUALIB_API int luaL_argerror (lua_State *L, int narg, const char *extramsg) {
+ lua_Debug ar;
+ if (!lua_getstack(L, 0, &ar)) /* no stack frame? */
+ return luaL_error(L, "bad argument #%d (%s)", narg, extramsg);
+ lua_getinfo(L, "n", &ar);
+ if (strcmp(ar.namewhat, "method") == 0) {
+ narg--; /* do not count `self' */
+ if (narg == 0) /* error is in the self argument itself? */
+ return luaL_error(L, "calling " LUA_QS " on bad self (%s)",
+ ar.name, extramsg);
+ }
+ if (ar.name == NULL)
+ ar.name = "?";
+ return luaL_error(L, "bad argument #%d to " LUA_QS " (%s)",
+ narg, ar.name, extramsg);
+}
+
+
+LUALIB_API int luaL_typerror (lua_State *L, int narg, const char *tname) {
+ const char *msg = lua_pushfstring(L, "%s expected, got %s",
+ tname, luaL_typename(L, narg));
+ return luaL_argerror(L, narg, msg);
+}
+
+
+static void tag_error (lua_State *L, int narg, int tag) {
+ luaL_typerror(L, narg, lua_typename(L, tag));
+}
+
+
+LUALIB_API void luaL_where (lua_State *L, int level) {
+ lua_Debug ar;
+ if (lua_getstack(L, level, &ar)) { /* check function at level */
+ lua_getinfo(L, "Sl", &ar); /* get info about it */
+ if (ar.currentline > 0) { /* is there info? */
+ lua_pushfstring(L, "%s:%d: ", ar.short_src, ar.currentline);
+ return;
+ }
+ }
+ lua_pushliteral(L, ""); /* else, no information available... */
+}
+
+
+LUALIB_API int luaL_error (lua_State *L, const char *fmt, ...) {
+ va_list argp;
+ va_start(argp, fmt);
+ luaL_where(L, 1);
+ lua_pushvfstring(L, fmt, argp);
+ va_end(argp);
+ lua_concat(L, 2);
+ return lua_error(L);
+}
+
+/* }====================================================== */
+
+
+LUALIB_API int luaL_checkoption (lua_State *L, int narg, const char *def,
+ const char *const lst[]) {
+ const char *name = (def) ? luaL_optstring(L, narg, def) :
+ luaL_checkstring(L, narg);
+ int i;
+ for (i=0; lst[i]; i++)
+ if (strcmp(lst[i], name) == 0)
+ return i;
+ return luaL_argerror(L, narg,
+ lua_pushfstring(L, "invalid option " LUA_QS, name));
+}
+
+
+LUALIB_API int luaL_newmetatable (lua_State *L, const char *tname) {
+ lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get registry.name */
+ if (!lua_isnil(L, -1)) /* name already in use? */
+ return 0; /* leave previous value on top, but return 0 */
+ lua_pop(L, 1);
+ lua_newtable(L); /* create metatable */
+ lua_pushvalue(L, -1);
+ lua_setfield(L, LUA_REGISTRYINDEX, tname); /* registry.name = metatable */
+ return 1;
+}
+
+
+LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) {
+ void *p = lua_touserdata(L, ud);
+ if (p != NULL) { /* value is a userdata? */
+ if (lua_getmetatable(L, ud)) { /* does it have a metatable? */
+ lua_getfield(L, LUA_REGISTRYINDEX, tname); /* get correct metatable */
+ if (lua_rawequal(L, -1, -2)) { /* does it have the correct mt? */
+ lua_pop(L, 2); /* remove both metatables */
+ return p;
+ }
+ }
+ }
+ luaL_typerror(L, ud, tname); /* else error */
+ return NULL; /* to avoid warnings */
+}
+
+
+LUALIB_API void luaL_checkstack (lua_State *L, int space, const char *mes) {
+ if (!lua_checkstack(L, space))
+ luaL_error(L, "stack overflow (%s)", mes);
+}
+
+
+LUALIB_API void luaL_checktype (lua_State *L, int narg, int t) {
+ if (lua_type(L, narg) != t)
+ tag_error(L, narg, t);
+}
+
+
+LUALIB_API void luaL_checkany (lua_State *L, int narg) {
+ if (lua_type(L, narg) == LUA_TNONE)
+ luaL_argerror(L, narg, "value expected");
+}
+
+
+LUALIB_API const char *luaL_checklstring (lua_State *L, int narg, size_t *len) {
+ const char *s = lua_tolstring(L, narg, len);
+ if (!s) tag_error(L, narg, LUA_TSTRING);
+ return s;
+}
+
+
+LUALIB_API const char *luaL_optlstring (lua_State *L, int narg,
+ const char *def, size_t *len) {
+ if (lua_isnoneornil(L, narg)) {
+ if (len)
+ *len = (def ? strlen(def) : 0);
+ return def;
+ }
+ else return luaL_checklstring(L, narg, len);
+}
+
+
+LUALIB_API lua_Number luaL_checknumber (lua_State *L, int narg) {
+ lua_Number d = lua_tonumber(L, narg);
+ if (d == 0 && !lua_isnumber(L, narg)) /* avoid extra test when d is not 0 */
+ tag_error(L, narg, LUA_TNUMBER);
+ return d;
+}
+
+
+LUALIB_API lua_Number luaL_optnumber (lua_State *L, int narg, lua_Number def) {
+ return luaL_opt(L, luaL_checknumber, narg, def);
+}
+
+
+LUALIB_API lua_Integer luaL_checkinteger (lua_State *L, int narg) {
+ lua_Integer d = lua_tointeger(L, narg);
+ if (d == 0 && !lua_isnumber(L, narg)) /* avoid extra test when d is not 0 */
+ tag_error(L, narg, LUA_TNUMBER);
+ return d;
+}
+
+
+LUALIB_API lua_Integer luaL_optinteger (lua_State *L, int narg,
+ lua_Integer def) {
+ return luaL_opt(L, luaL_checkinteger, narg, def);
+}
+
+
+LUALIB_API int luaL_getmetafield (lua_State *L, int obj, const char *event) {
+ if (!lua_getmetatable(L, obj)) /* no metatable? */
+ return 0;
+ lua_pushstring(L, event);
+ lua_rawget(L, -2);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 2); /* remove metatable and metafield */
+ return 0;
+ }
+ else {
+ lua_remove(L, -2); /* remove only metatable */
+ return 1;
+ }
+}
+
+
+LUALIB_API int luaL_callmeta (lua_State *L, int obj, const char *event) {
+ obj = abs_index(L, obj);
+ if (!luaL_getmetafield(L, obj, event)) /* no metafield? */
+ return 0;
+ lua_pushvalue(L, obj);
+ lua_call(L, 1, 1);
+ return 1;
+}
+
+
+LUALIB_API void (luaL_register) (lua_State *L, const char *libname,
+ const luaL_Reg *l) {
+ luaI_openlib(L, libname, l, 0);
+}
+
+
+static int libsize (const luaL_Reg *l) {
+ int size = 0;
+ for (; l->name; l++) size++;
+ return size;
+}
+
+
+LUALIB_API void luaI_openlib (lua_State *L, const char *libname,
+ const luaL_Reg *l, int nup) {
+ if (libname) {
+ int size = libsize(l);
+ /* check whether lib already exists */
+ luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 1);
+ lua_getfield(L, -1, libname); /* get _LOADED[libname] */
+ if (!lua_istable(L, -1)) { /* not found? */
+ lua_pop(L, 1); /* remove previous result */
+ /* try global variable (and create one if it does not exist) */
+ if (luaL_findtable(L, LUA_GLOBALSINDEX, libname, size) != NULL)
+ luaL_error(L, "name conflict for module " LUA_QS, libname);
+ lua_pushvalue(L, -1);
+ lua_setfield(L, -3, libname); /* _LOADED[libname] = new table */
+ }
+ lua_remove(L, -2); /* remove _LOADED table */
+ lua_insert(L, -(nup+1)); /* move library table to below upvalues */
+ }
+ for (; l->name; l++) {
+ int i;
+ for (i=0; i<nup; i++) /* copy upvalues to the top */
+ lua_pushvalue(L, -nup);
+ lua_pushcclosure(L, l->func, nup);
+ lua_setfield(L, -(nup+2), l->name);
+ }
+ lua_pop(L, nup); /* remove upvalues */
+}
+
+
+
+/*
+** {======================================================
+** getn-setn: size for arrays
+** =======================================================
+*/
+
+#if defined(LUA_COMPAT_GETN)
+
+static int checkint (lua_State *L, int topop) {
+ int n = (lua_type(L, -1) == LUA_TNUMBER) ? lua_tointeger(L, -1) : -1;
+ lua_pop(L, topop);
+ return n;
+}
+
+
+static void getsizes (lua_State *L) {
+ lua_getfield(L, LUA_REGISTRYINDEX, "LUA_SIZES");
+ if (lua_isnil(L, -1)) { /* no `size' table? */
+ lua_pop(L, 1); /* remove nil */
+ lua_newtable(L); /* create it */
+ lua_pushvalue(L, -1); /* `size' will be its own metatable */
+ lua_setmetatable(L, -2);
+ lua_pushliteral(L, "kv");
+ lua_setfield(L, -2, "__mode"); /* metatable(N).__mode = "kv" */
+ lua_pushvalue(L, -1);
+ lua_setfield(L, LUA_REGISTRYINDEX, "LUA_SIZES"); /* store in register */
+ }
+}
+
+
+LUALIB_API void luaL_setn (lua_State *L, int t, int n) {
+ t = abs_index(L, t);
+ lua_pushliteral(L, "n");
+ lua_rawget(L, t);
+ if (checkint(L, 1) >= 0) { /* is there a numeric field `n'? */
+ lua_pushliteral(L, "n"); /* use it */
+ lua_pushinteger(L, n);
+ lua_rawset(L, t);
+ }
+ else { /* use `sizes' */
+ getsizes(L);
+ lua_pushvalue(L, t);
+ lua_pushinteger(L, n);
+ lua_rawset(L, -3); /* sizes[t] = n */
+ lua_pop(L, 1); /* remove `sizes' */
+ }
+}
+
+
+LUALIB_API int luaL_getn (lua_State *L, int t) {
+ int n;
+ t = abs_index(L, t);
+ lua_pushliteral(L, "n"); /* try t.n */
+ lua_rawget(L, t);
+ if ((n = checkint(L, 1)) >= 0) return n;
+ getsizes(L); /* else try sizes[t] */
+ lua_pushvalue(L, t);
+ lua_rawget(L, -2);
+ if ((n = checkint(L, 2)) >= 0) return n;
+ return (int)lua_objlen(L, t);
+}
+
+#endif
+
+/* }====================================================== */
+
+
+
+LUALIB_API const char *luaL_gsub (lua_State *L, const char *s, const char *p,
+ const char *r) {
+ const char *wild;
+ size_t l = strlen(p);
+ luaL_Buffer b;
+ luaL_buffinit(L, &b);
+ while ((wild = strstr(s, p)) != NULL) {
+ luaL_addlstring(&b, s, wild - s); /* push prefix */
+ luaL_addstring(&b, r); /* push replacement in place of pattern */
+ s = wild + l; /* continue after `p' */
+ }
+ luaL_addstring(&b, s); /* push last suffix */
+ luaL_pushresult(&b);
+ return lua_tostring(L, -1);
+}
+
+
+LUALIB_API const char *luaL_findtable (lua_State *L, int idx,
+ const char *fname, int szhint) {
+ const char *e;
+ lua_pushvalue(L, idx);
+ do {
+ e = strchr(fname, '.');
+ if (e == NULL) e = fname + strlen(fname);
+ lua_pushlstring(L, fname, e - fname);
+ lua_rawget(L, -2);
+ if (lua_isnil(L, -1)) { /* no such field? */
+ lua_pop(L, 1); /* remove this nil */
+ lua_createtable(L, 0, (*e == '.' ? 1 : szhint)); /* new table for field */
+ lua_pushlstring(L, fname, e - fname);
+ lua_pushvalue(L, -2);
+ lua_settable(L, -4); /* set new table into field */
+ }
+ else if (!lua_istable(L, -1)) { /* field has a non-table value? */
+ lua_pop(L, 2); /* remove table and value */
+ return fname; /* return problematic part of the name */
+ }
+ lua_remove(L, -2); /* remove previous table */
+ fname = e + 1;
+ } while (*e == '.');
+ return NULL;
+}
+
+
+
+/*
+** {======================================================
+** Generic Buffer manipulation
+** =======================================================
+*/
+
+
+#define bufflen(B) ((B)->p - (B)->buffer)
+#define bufffree(B) ((size_t)(LUAL_BUFFERSIZE - bufflen(B)))
+
+#define LIMIT (LUA_MINSTACK/2)
+
+
+static int emptybuffer (luaL_Buffer *B) {
+ size_t l = bufflen(B);
+ if (l == 0) return 0; /* put nothing on stack */
+ else {
+ lua_pushlstring(B->L, B->buffer, l);
+ B->p = B->buffer;
+ B->lvl++;
+ return 1;
+ }
+}
+
+
+static void adjuststack (luaL_Buffer *B) {
+ if (B->lvl > 1) {
+ lua_State *L = B->L;
+ int toget = 1; /* number of levels to concat */
+ size_t toplen = lua_strlen(L, -1);
+ do {
+ size_t l = lua_strlen(L, -(toget+1));
+ if (B->lvl - toget + 1 >= LIMIT || toplen > l) {
+ toplen += l;
+ toget++;
+ }
+ else break;
+ } while (toget < B->lvl);
+ lua_concat(L, toget);
+ B->lvl = B->lvl - toget + 1;
+ }
+}
+
+
+LUALIB_API char *luaL_prepbuffer (luaL_Buffer *B) {
+ if (emptybuffer(B))
+ adjuststack(B);
+ return B->buffer;
+}
+
+
+LUALIB_API void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l) {
+ while (l--)
+ luaL_addchar(B, *s++);
+}
+
+
+LUALIB_API void luaL_addstring (luaL_Buffer *B, const char *s) {
+ luaL_addlstring(B, s, strlen(s));
+}
+
+
+LUALIB_API void luaL_pushresult (luaL_Buffer *B) {
+ emptybuffer(B);
+ lua_concat(B->L, B->lvl);
+ B->lvl = 1;
+}
+
+
+LUALIB_API void luaL_addvalue (luaL_Buffer *B) {
+ lua_State *L = B->L;
+ size_t vl;
+ const char *s = lua_tolstring(L, -1, &vl);
+ if (vl <= bufffree(B)) { /* fit into buffer? */
+ memcpy(B->p, s, vl); /* put it there */
+ B->p += vl;
+ lua_pop(L, 1); /* remove from stack */
+ }
+ else {
+ if (emptybuffer(B))
+ lua_insert(L, -2); /* put buffer before new value */
+ B->lvl++; /* add new value into B stack */
+ adjuststack(B);
+ }
+}
+
+
+LUALIB_API void luaL_buffinit (lua_State *L, luaL_Buffer *B) {
+ B->L = L;
+ B->p = B->buffer;
+ B->lvl = 0;
+}
+
+/* }====================================================== */
+
+
+LUALIB_API int luaL_ref (lua_State *L, int t) {
+ int ref;
+ t = abs_index(L, t);
+ if (lua_isnil(L, -1)) {
+ lua_pop(L, 1); /* remove from stack */
+ return LUA_REFNIL; /* `nil' has a unique fixed reference */
+ }
+ lua_rawgeti(L, t, FREELIST_REF); /* get first free element */
+ ref = (int)lua_tointeger(L, -1); /* ref = t[FREELIST_REF] */
+ lua_pop(L, 1); /* remove it from stack */
+ if (ref != 0) { /* any free element? */
+ lua_rawgeti(L, t, ref); /* remove it from list */
+ lua_rawseti(L, t, FREELIST_REF); /* (t[FREELIST_REF] = t[ref]) */
+ }
+ else { /* no free elements */
+ ref = (int)lua_objlen(L, t);
+ ref++; /* create new reference */
+ }
+ lua_rawseti(L, t, ref);
+ return ref;
+}
+
+
+LUALIB_API void luaL_unref (lua_State *L, int t, int ref) {
+ if (ref >= 0) {
+ t = abs_index(L, t);
+ lua_rawgeti(L, t, FREELIST_REF);
+ lua_rawseti(L, t, ref); /* t[ref] = t[FREELIST_REF] */
+ lua_pushinteger(L, ref);
+ lua_rawseti(L, t, FREELIST_REF); /* t[FREELIST_REF] = ref */
+ }
+}
+
+
+
+/*
+** {======================================================
+** Load functions
+** =======================================================
+*/
+
+typedef struct LoadF {
+ int extraline;
+ FILE *f;
+ char buff[LUAL_BUFFERSIZE];
+} LoadF;
+
+
+static const char *getF (lua_State *L, void *ud, size_t *size) {
+ LoadF *lf = (LoadF *)ud;
+ (void)L;
+ if (lf->extraline) {
+ lf->extraline = 0;
+ *size = 1;
+ return "\n";
+ }
+ if (feof(lf->f)) return NULL;
+ *size = fread(lf->buff, 1, sizeof(lf->buff), lf->f);
+ return (*size > 0) ? lf->buff : NULL;
+}
+
+
+static int errfile (lua_State *L, const char *what, int fnameindex) {
+ const char *serr = strerror(errno);
+ const char *filename = lua_tostring(L, fnameindex) + 1;
+ lua_pushfstring(L, "cannot %s %s: %s", what, filename, serr);
+ lua_remove(L, fnameindex);
+ return LUA_ERRFILE;
+}
+
+
+LUALIB_API int luaL_loadfile (lua_State *L, const char *filename) {
+ LoadF lf;
+ int status, readstatus;
+ int c;
+ int fnameindex = lua_gettop(L) + 1; /* index of filename on the stack */
+ lf.extraline = 0;
+ if (filename == NULL) {
+ lua_pushliteral(L, "=stdin");
+ lf.f = stdin;
+ }
+ else {
+ lua_pushfstring(L, "@%s", filename);
+ lf.f = fopen(filename, "r");
+ if (lf.f == NULL) return errfile(L, "open", fnameindex);
+ }
+ c = getc(lf.f);
+ if (c == '#') { /* Unix exec. file? */
+ lf.extraline = 1;
+ while ((c = getc(lf.f)) != EOF && c != '\n') ; /* skip first line */
+ if (c == '\n') c = getc(lf.f);
+ }
+ if (c == LUA_SIGNATURE[0] && filename) { /* binary file? */
+ lf.f = freopen(filename, "rb", lf.f); /* reopen in binary mode */
+ if (lf.f == NULL) return errfile(L, "reopen", fnameindex);
+ /* skip eventual `#!...' */
+ while ((c = getc(lf.f)) != EOF && c != LUA_SIGNATURE[0])
+ {}
+ lf.extraline = 0;
+ }
+ ungetc(c, lf.f);
+ status = lua_load(L, getF, &lf, lua_tostring(L, -1));
+ readstatus = ferror(lf.f);
+ if (filename) fclose(lf.f); /* close file (even in case of errors) */
+ if (readstatus) {
+ lua_settop(L, fnameindex); /* ignore results from `lua_load' */
+ return errfile(L, "read", fnameindex);
+ }
+ lua_remove(L, fnameindex);
+ return status;
+}
+
+
+typedef struct LoadS {
+ const char *s;
+ size_t size;
+} LoadS;
+
+
+static const char *getS (lua_State *L, void *ud, size_t *size) {
+ LoadS *ls = (LoadS *)ud;
+ (void)L;
+ if (ls->size == 0) return NULL;
+ *size = ls->size;
+ ls->size = 0;
+ return ls->s;
+}
+
+
+LUALIB_API int luaL_loadbuffer (lua_State *L, const char *buff, size_t size,
+ const char *name) {
+ LoadS ls;
+ ls.s = buff;
+ ls.size = size;
+ return lua_load(L, getS, &ls, name);
+}
+
+
+LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s) {
+ return luaL_loadbuffer(L, s, strlen(s), s);
+}
+
+
+
+/* }====================================================== */
+
+
+static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) {
+ (void)ud;
+ (void)osize;
+ if (nsize == 0) {
+ free(ptr);
+ return NULL;
+ }
+ else
+ return realloc(ptr, nsize);
+}
+
+
+static int panic (lua_State *L) {
+ (void)L; /* to avoid warnings */
+ fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n",
+ lua_tostring(L, -1));
+ return 0;
+}
+
+
+LUALIB_API lua_State *luaL_newstate (void) {
+ lua_State *L = lua_newstate(l_alloc, NULL);
+ if (L) lua_atpanic(L, &panic);
+ return L;
+}
+
diff --git a/lib/lua/src/lauxlib.h b/lib/lua/src/lauxlib.h
new file mode 100644
index 000000000..34258235d
--- /dev/null
+++ b/lib/lua/src/lauxlib.h
@@ -0,0 +1,174 @@
+/*
+** $Id: lauxlib.h,v 1.88.1.1 2007/12/27 13:02:25 roberto Exp $
+** Auxiliary functions for building Lua libraries
+** See Copyright Notice in lua.h
+*/
+
+
+#ifndef lauxlib_h
+#define lauxlib_h
+
+
+#include <stddef.h>
+#include <stdio.h>
+
+#include "lua.h"
+
+
+#if defined(LUA_COMPAT_GETN)
+LUALIB_API int (luaL_getn) (lua_State *L, int t);
+LUALIB_API void (luaL_setn) (lua_State *L, int t, int n);
+#else
+#define luaL_getn(L,i) ((int)lua_objlen(L, i))
+#define luaL_setn(L,i,j) ((void)0) /* no op! */
+#endif
+
+#if defined(LUA_COMPAT_OPENLIB)
+#define luaI_openlib luaL_openlib
+#endif
+
+
+/* extra error code for `luaL_load' */
+#define LUA_ERRFILE (LUA_ERRERR+1)
+
+
+typedef struct luaL_Reg {
+ const char *name;
+ lua_CFunction func;
+} luaL_Reg;
+
+
+
+LUALIB_API void (luaI_openlib) (lua_State *L, const char *libname,
+ const luaL_Reg *l, int nup);
+LUALIB_API void (luaL_register) (lua_State *L, const char *libname,
+ const luaL_Reg *l);
+LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e);
+LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e);
+LUALIB_API int (luaL_typerror) (lua_State *L, int narg, const char *tname);
+LUALIB_API int (luaL_argerror) (lua_State *L, int numarg, const char *extramsg);
+LUALIB_API const char *(luaL_checklstring) (lua_State *L, int numArg,
+ size_t *l);
+LUALIB_API const char *(luaL_optlstring) (lua_State *L, int numArg,
+ const char *def, size_t *l);
+LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int numArg);
+LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int nArg, lua_Number def);
+
+LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int numArg);
+LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int nArg,
+ lua_Integer def);
+
+LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg);
+LUALIB_API void (luaL_checktype) (lua_State *L, int narg, int t);
+LUALIB_API void (luaL_checkany) (lua_State *L, int narg);
+
+LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname);
+LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname);
+
+LUALIB_API void (luaL_where) (lua_State *L, int lvl);
+LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...);
+
+LUALIB_API int (luaL_checkoption) (lua_State *L, int narg, const char *def,
+ const char *const lst[]);
+
+LUALIB_API int (luaL_ref) (lua_State *L, int t);
+LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref);
+
+LUALIB_API int (luaL_loadfile) (lua_State *L, const char *filename);
+LUALIB_API int (luaL_loadbuffer) (lua_State *L, const char *buff, size_t sz,
+ const char *name);
+LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s);
+
+LUALIB_API lua_State *(luaL_newstate) (void);
+
+
+LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p,
+ const char *r);
+
+LUALIB_API const char *(luaL_findtable) (lua_State *L, int idx,
+ const char *fname, int szhint);
+
+
+
+
+/*
+** ===============================================================
+** some useful macros
+** ===============================================================
+*/
+
+#define luaL_argcheck(L, cond,numarg,extramsg) \
+ ((void)((cond) || luaL_argerror(L, (numarg), (extramsg))))
+#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL))
+#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL))
+#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n)))
+#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d)))
+#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n)))
+#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d)))
+
+#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i)))
+
+#define luaL_dofile(L, fn) \
+ (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))
+
+#define luaL_dostring(L, s) \
+ (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0))
+
+#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n)))
+
+#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n)))
+
+/*
+** {======================================================
+** Generic Buffer manipulation
+** =======================================================
+*/
+
+
+
+typedef struct luaL_Buffer {
+ char *p; /* current position in buffer */
+ int lvl; /* number of strings in the stack (level) */
+ lua_State *L;
+ char buffer[LUAL_BUFFERSIZE];
+} luaL_Buffer;
+
+#define luaL_addchar(B,c) \
+ ((void)((B)->p < ((B)->buffer+LUAL_BUFFERSIZE) || luaL_prepbuffer(B)), \
+ (*(B)->p++ = (char)(c)))
+
+/* compatibility only */
+#define luaL_putchar(B,c) luaL_addchar(B,c)
+
+#define luaL_addsize(B,n) ((B)->p += (n))
+
+LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B);
+LUALIB_API char *(luaL_prepbuffer) (luaL_Buffer *B);
+LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l);
+LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s);
+LUALIB_API void (luaL_addvalue) (luaL_Buffer *B);
+LUALIB_API void (luaL_pushresult) (luaL_Buffer *B);
+
+
+/* }====================================================== */
+
+
+/* compatibility with ref system */
+
+/* pre-defined references */
+#define LUA_NOREF (-2)
+#define LUA_REFNIL (-1)
+
+#define lua_ref(L,lock) ((lock) ? luaL_ref(L, LUA_REGISTRYINDEX) : \
+ (lua_pushstring(L, "unlocked references are obsolete"), lua_error(L), 0))
+
+#define lua_unref(L,ref) luaL_unref(L, LUA_REGISTRYINDEX, (ref))
+
+#define lua_getref(L,ref) lua_rawgeti(L, LUA_REGISTRYINDEX, (ref))
+
+
+#define luaL_reg luaL_Reg
+
+#endif
+
+
diff --git a/lib/lua/src/lbaselib.c b/lib/lua/src/lbaselib.c
new file mode 100644
index 000000000..2a4c079d3
--- /dev/null
+++ b/lib/lua/src/lbaselib.c
@@ -0,0 +1,653 @@
+/*
+** $Id: lbaselib.c,v 1.191.1.6 2008/02/14 16:46:22 roberto Exp $
+** Basic library
+** See Copyright Notice in lua.h
+*/
+
+
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define lbaselib_c
+#define LUA_LIB
+
+#include "lua.h"
+
+#include "lauxlib.h"
+#include "lualib.h"
+
+
+
+
+/*
+** If your system does not support `stdout', you can just remove this function.
+** If you need, you can define your own `print' function, following this
+** model but changing `fputs' to put the strings at a proper place
+** (a console window or a log file, for instance).
+*/
+static int luaB_print (lua_State *L) {
+ int n = lua_gettop(L); /* number of arguments */
+ int i;
+ lua_getglobal(L, "tostring");
+ for (i=1; i<=n; i++) {
+ const char *s;
+ lua_pushvalue(L, -1); /* function to be called */
+ lua_pushvalue(L, i); /* value to print */
+ lua_call(L, 1, 1);
+ s = lua_tostring(L, -1); /* get result */
+ if (s == NULL)
+ return luaL_error(L, LUA_QL("tostring") " must return a string to "
+ LUA_QL("print"));
+ if (i>1) fputs("\t", stdout);
+ fputs(s, stdout);
+ lua_pop(L, 1); /* pop result */
+ }
+ fputs("\n", stdout);
+ return 0;
+}
+
+
+static int luaB_tonumber (lua_State *L) {
+ int base = luaL_optint(L, 2, 10);
+ if (base == 10) { /* standard conversion */
+ luaL_checkany(L, 1);
+ if (lua_isnumber(L, 1)) {
+ lua_pushnumber(L, lua_tonumber(L, 1));
+ return 1;
+ }
+ }
+ else {
+ const char *s1 = luaL_checkstring(L, 1);
+ char *s2;
+ unsigned long n;
+ luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range");
+ n = strtoul(s1, &s2, base);
+ if (s1 != s2) { /* at least one valid digit? */
+ while (isspace((unsigned char)(*s2))) s2++; /* skip trailing spaces */
+ if (*s2 == '\0') { /* no invalid trailing characters? */
+ lua_pushnumber(L, (lua_Number)n);
+ return 1;
+ }
+ }
+ }
+ lua_pushnil(L); /* else not a number */
+ return 1;
+}
+
+
+static int luaB_error (lua_State *L) {
+ int level = luaL_optint(L, 2, 1);
+ lua_settop(L, 1);
+ if (lua_isstring(L, 1) && level > 0) { /* add extra information? */
+ luaL_where(L, level);
+ lua_pushvalue(L, 1);
+ lua_concat(L, 2);
+ }
+ return lua_error(L);
+}
+
+
+static int luaB_getmetatable (lua_State *L) {
+ luaL_checkany(L, 1);
+ if (!lua_getmetatable(L, 1)) {
+ lua_pushnil(L);
+ return 1; /* no metatable */
+ }
+ luaL_getmetafield(L, 1, "__metatable");
+ return 1; /* returns either __metatable field (if present) or metatable */
+}
+
+
+static int luaB_setmetatable (lua_State *L) {
+ int t = lua_type(L, 2);
+ luaL_checktype(L, 1, LUA_TTABLE);
+ luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2,
+ "nil or table expected");
+ if (luaL_getmetafield(L, 1, "__metatable"))
+ luaL_error(L, "cannot change a protected metatable");
+ lua_settop(L, 2);
+ lua_setmetatable(L, 1);
+ return 1;
+}
+
+
+static void getfunc (lua_State *L, int opt) {
+ if (lua_isfunction(L, 1)) lua_pushvalue(L, 1);
+ else {
+ lua_Debug ar;
+ int level = opt ? luaL_optint(L, 1, 1) : luaL_checkint(L, 1);
+ luaL_argcheck(L, level >= 0, 1, "level must be non-negative");
+ if (lua_getstack(L, level, &ar) == 0)
+ luaL_argerror(L, 1, "invalid level");
+ lua_getinfo(L, "f", &ar);
+ if (lua_isnil(L, -1))
+ luaL_error(L, "no function environment for tail call at level %d",
+ level);
+ }
+}
+
+
+static int luaB_getfenv (lua_State *L) {
+ getfunc(L, 1);
+ if (lua_iscfunction(L, -1)) /* is a C function? */
+ lua_pushvalue(L, LUA_GLOBALSINDEX); /* return the thread's global env. */
+ else
+ lua_getfenv(L, -1);
+ return 1;
+}
+
+
+static int luaB_setfenv (lua_State *L) {
+ luaL_checktype(L, 2, LUA_TTABLE);
+ getfunc(L, 0);
+ lua_pushvalue(L, 2);
+ if (lua_isnumber(L, 1) && lua_tonumber(L, 1) == 0) {
+ /* change environment of current thread */
+ lua_pushthread(L);
+ lua_insert(L, -2);
+ lua_setfenv(L, -2);
+ return 0;
+ }
+ else if (lua_iscfunction(L, -2) || lua_setfenv(L, -2) == 0)
+ luaL_error(L,
+ LUA_QL("setfenv") " cannot change environment of given object");
+ return 1;
+}
+
+
+static int luaB_rawequal (lua_State *L) {
+ luaL_checkany(L, 1);
+ luaL_checkany(L, 2);
+ lua_pushboolean(L, lua_rawequal(L, 1, 2));
+ return 1;
+}
+
+
+static int luaB_rawget (lua_State *L) {
+ luaL_checktype(L, 1, LUA_TTABLE);
+ luaL_checkany(L, 2);
+ lua_settop(L, 2);
+ lua_rawget(L, 1);
+ return 1;
+}
+
+static int luaB_rawset (lua_State *L) {
+ luaL_checktype(L, 1, LUA_TTABLE);
+ luaL_checkany(L, 2);
+ luaL_checkany(L, 3);
+ lua_settop(L, 3);
+ lua_rawset(L, 1);
+ return 1;
+}
+
+
+static int luaB_gcinfo (lua_State *L) {
+ lua_pushinteger(L, lua_getgccount(L));
+ return 1;
+}
+
+
+static int luaB_collectgarbage (lua_State *L) {
+ static const char *const opts[] = {"stop", "restart", "collect",
+ "count", "step", "setpause", "setstepmul", NULL};
+ static const int optsnum[] = {LUA_GCSTOP, LUA_GCRESTART, LUA_GCCOLLECT,
+ LUA_GCCOUNT, LUA_GCSTEP, LUA_GCSETPAUSE, LUA_GCSETSTEPMUL};
+ int o = luaL_checkoption(L, 1, "collect", opts);
+ int ex = luaL_optint(L, 2, 0);
+ int res = lua_gc(L, optsnum[o], ex);
+ switch (optsnum[o]) {
+ case LUA_GCCOUNT: {
+ int b = lua_gc(L, LUA_GCCOUNTB, 0);
+ lua_pushnumber(L, res + ((lua_Number)b/1024));
+ return 1;
+ }
+ case LUA_GCSTEP: {
+ lua_pushboolean(L, res);
+ return 1;
+ }
+ default: {
+ lua_pushnumber(L, res);
+ return 1;
+ }
+ }
+}
+
+
+static int luaB_type (lua_State *L) {
+ luaL_checkany(L, 1);
+ lua_pushstring(L, luaL_typename(L, 1));
+ return 1;
+}
+
+
+static int luaB_next (lua_State *L) {
+ luaL_checktype(L, 1, LUA_TTABLE);
+ lua_settop(L, 2); /* create a 2nd argument if there isn't one */
+ if (lua_next(L, 1))
+ return 2;
+ else {
+ lua_pushnil(L);
+ return 1;
+ }
+}
+
+
+static int luaB_pairs (lua_State *L) {
+ luaL_checktype(L, 1, LUA_TTABLE);
+ lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */
+ lua_pushvalue(L, 1); /* state, */
+ lua_pushnil(L); /* and initial value */
+ return 3;
+}
+
+
+static int ipairsaux (lua_State *L) {
+ int i = luaL_checkint(L, 2);
+ luaL_checktype(L, 1, LUA_TTABLE);
+ i++; /* next value */
+ lua_pushinteger(L, i);
+ lua_rawgeti(L, 1, i);
+ return (lua_isnil(L, -1)) ? 0 : 2;
+}
+
+
+static int luaB_ipairs (lua_State *L) {
+ luaL_checktype(L, 1, LUA_TTABLE);
+ lua_pushvalue(L, lua_upvalueindex(1)); /* return generator, */
+ lua_pushvalue(L, 1); /* state, */
+ lua_pushinteger(L, 0); /* and initial value */
+ return 3;
+}
+
+
+static int load_aux (lua_State *L, int status) {
+ if (status == 0) /* OK? */
+ return 1;
+ else {
+ lua_pushnil(L);
+ lua_insert(L, -2); /* put before error message */
+ return 2; /* return nil plus error message */
+ }
+}
+
+
+static int luaB_loadstring (lua_State *L) {
+ size_t l;
+ const char *s = luaL_checklstring(L, 1, &l);
+ const char *chunkname = luaL_optstring(L, 2, s);
+ return load_aux(L, luaL_loadbuffer(L, s, l, chunkname));
+}
+
+
+static int luaB_loadfile (lua_State *L) {
+ const char *fname = luaL_optstring(L, 1, NULL);
+ return load_aux(L, luaL_loadfile(L, fname));
+}
+
+
+/*
+** Reader for generic `load' function: `lua_load' uses the
+** stack for internal stuff, so the reader cannot change the
+** stack top. Instead, it keeps its resulting string in a
+** reserved slot inside the stack.
+*/
+static const char *generic_reader (lua_State *L, void *ud, size_t *size) {
+ (void)ud; /* to avoid warnings */
+ luaL_checkstack(L, 2, "too many nested functions");
+ lua_pushvalue(L, 1); /* get function */
+ lua_call(L, 0, 1); /* call it */
+ if (lua_isnil(L, -1)) {
+ *size = 0;
+ return NULL;
+ }
+ else if (lua_isstring(L, -1)) {
+ lua_replace(L, 3); /* save string in a reserved stack slot */
+ return lua_tolstring(L, 3, size);
+ }
+ else luaL_error(L, "reader function must return a string");
+ return NULL; /* to avoid warnings */
+}
+
+
+static int luaB_load (lua_State *L) {
+ int status;
+ const char *cname = luaL_optstring(L, 2, "=(load)");
+ luaL_checktype(L, 1, LUA_TFUNCTION);
+ lua_settop(L, 3); /* function, eventual name, plus one reserved slot */
+ status = lua_load(L, generic_reader, NULL, cname);
+ return load_aux(L, status);
+}
+
+
+static int luaB_dofile (lua_State *L) {
+ const char *fname = luaL_optstring(L, 1, NULL);
+ int n = lua_gettop(L);
+ if (luaL_loadfile(L, fname) != 0) lua_error(L);
+ lua_call(L, 0, LUA_MULTRET);
+ return lua_gettop(L) - n;
+}
+
+
+static int luaB_assert (lua_State *L) {
+ luaL_checkany(L, 1);
+ if (!lua_toboolean(L, 1))
+ return luaL_error(L, "%s", luaL_optstring(L, 2, "assertion failed!"));
+ return lua_gettop(L);
+}
+
+
+static int luaB_unpack (lua_State *L) {
+ int i, e, n;
+ luaL_checktype(L, 1, LUA_TTABLE);
+ i = luaL_optint(L, 2, 1);
+ e = luaL_opt(L, luaL_checkint, 3, luaL_getn(L, 1));
+ if (i > e) return 0; /* empty range */
+ n = e - i + 1; /* number of elements */
+ if (n <= 0 || !lua_checkstack(L, n)) /* n <= 0 means arith. overflow */
+ return luaL_error(L, "too many results to unpack");
+ lua_rawgeti(L, 1, i); /* push arg[i] (avoiding overflow problems) */
+ while (i++ < e) /* push arg[i + 1...e] */
+ lua_rawgeti(L, 1, i);
+ return n;
+}
+
+
+static int luaB_select (lua_State *L) {
+ int n = lua_gettop(L);
+ if (lua_type(L, 1) == LUA_TSTRING && *lua_tostring(L, 1) == '#') {
+ lua_pushinteger(L, n-1);
+ return 1;
+ }
+ else {
+ int i = luaL_checkint(L, 1);
+ if (i < 0) i = n + i;
+ else if (i > n) i = n;
+ luaL_argcheck(L, 1 <= i, 1, "index out of range");
+ return n - i;
+ }
+}
+
+
+static int luaB_pcall (lua_State *L) {
+ int status;
+ luaL_checkany(L, 1);
+ status = lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0);
+ lua_pushboolean(L, (status == 0));
+ lua_insert(L, 1);
+ return lua_gettop(L); /* return status + all results */
+}
+
+
+static int luaB_xpcall (lua_State *L) {
+ int status;
+ luaL_checkany(L, 2);
+ lua_settop(L, 2);
+ lua_insert(L, 1); /* put error function under function to be called */
+ status = lua_pcall(L, 0, LUA_MULTRET, 1);
+ lua_pushboolean(L, (status == 0));
+ lua_replace(L, 1);
+ return lua_gettop(L); /* return status + all results */
+}
+
+
+static int luaB_tostring (lua_State *L) {
+ luaL_checkany(L, 1);
+ if (luaL_callmeta(L, 1, "__tostring")) /* is there a metafield? */
+ return 1; /* use its value */
+ switch (lua_type(L, 1)) {
+ case LUA_TNUMBER:
+ lua_pushstring(L, lua_tostring(L, 1));
+ break;
+ case LUA_TSTRING:
+ lua_pushvalue(L, 1);
+ break;
+ case LUA_TBOOLEAN:
+ lua_pushstring(L, (lua_toboolean(L, 1) ? "true" : "false"));
+ break;
+ case LUA_TNIL:
+ lua_pushliteral(L, "nil");
+ break;
+ default:
+ lua_pushfstring(L, "%s: %p", luaL_typename(L, 1), lua_topointer(L, 1));
+ break;
+ }
+ return 1;
+}
+
+
+static int luaB_newproxy (lua_State *L) {
+ lua_settop(L, 1);
+ lua_newuserdata(L, 0); /* create proxy */
+ if (lua_toboolean(L, 1) == 0)
+ return 1; /* no metatable */
+ else if (lua_isboolean(L, 1)) {
+ lua_newtable(L); /* create a new metatable `m' ... */
+ lua_pushvalue(L, -1); /* ... and mark `m' as a valid metatable */
+ lua_pushboolean(L, 1);
+ lua_rawset(L, lua_upvalueindex(1)); /* weaktable[m] = true */
+ }
+ else {
+ int validproxy = 0; /* to check if weaktable[metatable(u)] == true */
+ if (lua_getmetatable(L, 1)) {
+ lua_rawget(L, lua_upvalueindex(1));
+ validproxy = lua_toboolean(L, -1);
+ lua_pop(L, 1); /* remove value */
+ }
+ luaL_argcheck(L, validproxy, 1, "boolean or proxy expected");
+ lua_getmetatable(L, 1); /* metatable is valid; get it */
+ }
+ lua_setmetatable(L, 2);
+ return 1;
+}
+
+
+static const luaL_Reg base_funcs[] = {
+ {"assert", luaB_assert},
+ {"collectgarbage", luaB_collectgarbage},
+ {"dofile", luaB_dofile},
+ {"error", luaB_error},
+ {"gcinfo", luaB_gcinfo},
+ {"getfenv", luaB_getfenv},
+ {"getmetatable", luaB_getmetatable},
+ {"loadfile", luaB_loadfile},
+ {"load", luaB_load},
+ {"loadstring", luaB_loadstring},
+ {"next", luaB_next},
+ {"pcall", luaB_pcall},
+ {"print", luaB_print},
+ {"rawequal", luaB_rawequal},
+ {"rawget", luaB_rawget},
+ {"rawset", luaB_rawset},
+ {"select", luaB_select},
+ {"setfenv", luaB_setfenv},
+ {"setmetatable", luaB_setmetatable},
+ {"tonumber", luaB_tonumber},
+ {"tostring", luaB_tostring},
+ {"type", luaB_type},
+ {"unpack", luaB_unpack},
+ {"xpcall", luaB_xpcall},
+ {NULL, NULL}
+};
+
+
+/*
+** {======================================================
+** Coroutine library
+** =======================================================
+*/
+
+#define CO_RUN 0 /* running */
+#define CO_SUS 1 /* suspended */
+#define CO_NOR 2 /* 'normal' (it resumed another coroutine) */
+#define CO_DEAD 3
+
+static const char *const statnames[] =
+ {"running", "suspended", "normal", "dead"};
+
+static int costatus (lua_State *L, lua_State *co) {
+ if (L == co) return CO_RUN;
+ switch (lua_status(co)) {
+ case LUA_YIELD:
+ return CO_SUS;
+ case 0: {
+ lua_Debug ar;
+ if (lua_getstack(co, 0, &ar) > 0) /* does it have frames? */
+ return CO_NOR; /* it is running */
+ else if (lua_gettop(co) == 0)
+ return CO_DEAD;
+ else
+ return CO_SUS; /* initial state */
+ }
+ default: /* some error occured */
+ return CO_DEAD;
+ }
+}
+
+
+static int luaB_costatus (lua_State *L) {
+ lua_State *co = lua_tothread(L, 1);
+ luaL_argcheck(L, co, 1, "coroutine expected");
+ lua_pushstring(L, statnames[costatus(L, co)]);
+ return 1;
+}
+
+
+static int auxresume (lua_State *L, lua_State *co, int narg) {
+ int status = costatus(L, co);
+ if (!lua_checkstack(co, narg))
+ luaL_error(L, "too many arguments to resume");
+ if (status != CO_SUS) {
+ lua_pushfstring(L, "cannot resume %s coroutine", statnames[status]);
+ return -1; /* error flag */
+ }
+ lua_xmove(L, co, narg);
+ lua_setlevel(L, co);
+ status = lua_resume(co, narg);
+ if (status == 0 || status == LUA_YIELD) {
+ int nres = lua_gettop(co);
+ if (!lua_checkstack(L, nres + 1))
+ luaL_error(L, "too many results to resume");
+ lua_xmove(co, L, nres); /* move yielded values */
+ return nres;
+ }
+ else {
+ lua_xmove(co, L, 1); /* move error message */
+ return -1; /* error flag */
+ }
+}
+
+
+static int luaB_coresume (lua_State *L) {
+ lua_State *co = lua_tothread(L, 1);
+ int r;
+ luaL_argcheck(L, co, 1, "coroutine expected");
+ r = auxresume(L, co, lua_gettop(L) - 1);
+ if (r < 0) {
+ lua_pushboolean(L, 0);
+ lua_insert(L, -2);
+ return 2; /* return false + error message */
+ }
+ else {
+ lua_pushboolean(L, 1);
+ lua_insert(L, -(r + 1));
+ return r + 1; /* return true + `resume' returns */
+ }
+}
+
+
+static int luaB_auxwrap (lua_State *L) {
+ lua_State *co = lua_tothread(L, lua_upvalueindex(1));
+ int r = auxresume(L, co, lua_gettop(L));
+ if (r < 0) {
+ if (lua_isstring(L, -1)) { /* error object is a string? */
+ luaL_where(L, 1); /* add extra info */
+ lua_insert(L, -2);
+ lua_concat(L, 2);
+ }
+ lua_error(L); /* propagate error */
+ }
+ return r;
+}
+
+
+static int luaB_cocreate (lua_State *L) {
+ lua_State *NL = lua_newthread(L);
+ luaL_argcheck(L, lua_isfunction(L, 1) && !lua_iscfunction(L, 1), 1,
+ "Lua function expected");
+ lua_pushvalue(L, 1); /* move function to top */
+ lua_xmove(L, NL, 1); /* move function from L to NL */
+ return 1;
+}
+
+
+static int luaB_cowrap (lua_State *L) {
+ luaB_cocreate(L);
+ lua_pushcclosure(L, luaB_auxwrap, 1);
+ return 1;
+}
+
+
+static int luaB_yield (lua_State *L) {
+ return lua_yield(L, lua_gettop(L));
+}
+
+
+static int luaB_corunning (lua_State *L) {
+ if (lua_pushthread(L))
+ lua_pushnil(L); /* main thread is not a coroutine */
+ return 1;
+}
+
+
+static const luaL_Reg co_funcs[] = {
+ {"create", luaB_cocreate},
+ {"resume", luaB_coresume},
+ {"running", luaB_corunning},
+ {"status", luaB_costatus},
+ {"wrap", luaB_cowrap},
+ {"yield", luaB_yield},
+ {NULL, NULL}
+};
+
+/* }====================================================== */
+
+
+static void auxopen (lua_State *L, const char *name,
+ lua_CFunction f, lua_CFunction u) {
+ lua_pushcfunction(L, u);
+ lua_pushcclosure(L, f, 1);
+ lua_setfield(L, -2, name);
+}
+
+
+static void base_open (lua_State *L) {
+ /* set global _G */
+ lua_pushvalue(L, LUA_GLOBALSINDEX);
+ lua_setglobal(L, "_G");
+ /* open lib into global table */
+ luaL_register(L, "_G", base_funcs);
+ lua_pushliteral(L, LUA_VERSION);
+ lua_setglobal(L, "_VERSION"); /* set global _VERSION */
+ /* `ipairs' and `pairs' need auxliliary functions as upvalues */
+ auxopen(L, "ipairs", luaB_ipairs, ipairsaux);
+ auxopen(L, "pairs", luaB_pairs, luaB_next);
+ /* `newproxy' needs a weaktable as upvalue */
+ lua_createtable(L, 0, 1); /* new table `w' */
+ lua_pushvalue(L, -1); /* `w' will be its own metatable */
+ lua_setmetatable(L, -2);
+ lua_pushliteral(L, "kv");
+ lua_setfield(L, -2, "__mode"); /* metatable(w).__mode = "kv" */
+ lua_pushcclosure(L, luaB_newproxy, 1);
+ lua_setglobal(L, "newproxy"); /* set global `newproxy' */
+}
+
+
+LUALIB_API int luaopen_base (lua_State *L) {
+ base_open(L);
+ luaL_register(L, LUA_COLIBNAME, co_funcs);
+ return 2;
+}
+
diff --git a/lib/lua/src/lcode.c b/lib/lua/src/lcode.c
new file mode 100644
index 000000000..cff626b7f
--- /dev/null
+++ b/lib/lua/src/lcode.c
@@ -0,0 +1,839 @@
+/*
+** $Id: lcode.c,v 2.25.1.3 2007/12/28 15:32:23 roberto Exp $
+** Code generator for Lua
+** See Copyright Notice in lua.h
+*/
+
+
+#include <stdlib.h>
+
+#define lcode_c
+#define LUA_CORE
+
+#include "lua.h"
+
+#include "lcode.h"
+#include "ldebug.h"
+#include "ldo.h"
+#include "lgc.h"
+#include "llex.h"
+#include "lmem.h"
+#include "lobject.h"
+#include "lopcodes.h"
+#include "lparser.h"
+#include "ltable.h"
+
+
+#define hasjumps(e) ((e)->t != (e)->f)
+
+
+static int isnumeral(expdesc *e) {
+ return (e->k == VKNUM && e->t == NO_JUMP && e->f == NO_JUMP);
+}
+
+
+void luaK_nil (FuncState *fs, int from, int n) {
+ Instruction *previous;
+ if (fs->pc > fs->lasttarget) { /* no jumps to current position? */
+ if (fs->pc == 0) { /* function start? */
+ if (from >= fs->nactvar)
+ return; /* positions are already clean */
+ }
+ else {
+ previous = &fs->f->code[fs->pc-1];
+ if (GET_OPCODE(*previous) == OP_LOADNIL) {
+ int pfrom = GETARG_A(*previous);
+ int pto = GETARG_B(*previous);
+ if (pfrom <= from && from <= pto+1) { /* can connect both? */
+ if (from+n-1 > pto)
+ SETARG_B(*previous, from+n-1);
+ return;
+ }
+ }
+ }
+ }
+ luaK_codeABC(fs, OP_LOADNIL, from, from+n-1, 0); /* else no optimization */
+}
+
+
+int luaK_jump (FuncState *fs) {
+ int jpc = fs->jpc; /* save list of jumps to here */
+ int j;
+ fs->jpc = NO_JUMP;
+ j = luaK_codeAsBx(fs, OP_JMP, 0, NO_JUMP);
+ luaK_concat(fs, &j, jpc); /* keep them on hold */
+ return j;
+}
+
+
+void luaK_ret (FuncState *fs, int first, int nret) {
+ luaK_codeABC(fs, OP_RETURN, first, nret+1, 0);
+}
+
+
+static int condjump (FuncState *fs, OpCode op, int A, int B, int C) {
+ luaK_codeABC(fs, op, A, B, C);
+ return luaK_jump(fs);
+}
+
+
+static void fixjump (FuncState *fs, int pc, int dest) {
+ Instruction *jmp = &fs->f->code[pc];
+ int offset = dest-(pc+1);
+ lua_assert(dest != NO_JUMP);
+ if (abs(offset) > MAXARG_sBx)
+ luaX_syntaxerror(fs->ls, "control structure too long");
+ SETARG_sBx(*jmp, offset);
+}
+
+
+/*
+** returns current `pc' and marks it as a jump target (to avoid wrong
+** optimizations with consecutive instructions not in the same basic block).
+*/
+int luaK_getlabel (FuncState *fs) {
+ fs->lasttarget = fs->pc;
+ return fs->pc;
+}
+
+
+static int getjump (FuncState *fs, int pc) {
+ int offset = GETARG_sBx(fs->f->code[pc]);
+ if (offset == NO_JUMP) /* point to itself represents end of list */
+ return NO_JUMP; /* end of list */
+ else
+ return (pc+1)+offset; /* turn offset into absolute position */
+}
+
+
+static Instruction *getjumpcontrol (FuncState *fs, int pc) {
+ Instruction *pi = &fs->f->code[pc];
+ if (pc >= 1 && testTMode(GET_OPCODE(*(pi-1))))
+ return pi-1;
+ else
+ return pi;
+}
+
+
+/*
+** check whether list has any jump that do not produce a value
+** (or produce an inverted value)
+*/
+static int need_value (FuncState *fs, int list) {
+ for (; list != NO_JUMP; list = getjump(fs, list)) {
+ Instruction i = *getjumpcontrol(fs, list);
+ if (GET_OPCODE(i) != OP_TESTSET) return 1;
+ }
+ return 0; /* not found */
+}
+
+
+static int patchtestreg (FuncState *fs, int node, int reg) {
+ Instruction *i = getjumpcontrol(fs, node);
+ if (GET_OPCODE(*i) != OP_TESTSET)
+ return 0; /* cannot patch other instructions */
+ if (reg != NO_REG && reg != GETARG_B(*i))
+ SETARG_A(*i, reg);
+ else /* no register to put value or register already has the value */
+ *i = CREATE_ABC(OP_TEST, GETARG_B(*i), 0, GETARG_C(*i));
+
+ return 1;
+}
+
+
+static void removevalues (FuncState *fs, int list) {
+ for (; list != NO_JUMP; list = getjump(fs, list))
+ patchtestreg(fs, list, NO_REG);
+}
+
+
+static void patchlistaux (FuncState *fs, int list, int vtarget, int reg,
+ int dtarget) {
+ while (list != NO_JUMP) {
+ int next = getjump(fs, list);
+ if (patchtestreg(fs, list, reg))
+ fixjump(fs, list, vtarget);
+ else
+ fixjump(fs, list, dtarget); /* jump to default target */
+ list = next;
+ }
+}
+
+
+static void dischargejpc (FuncState *fs) {
+ patchlistaux(fs, fs->jpc, fs->pc, NO_REG, fs->pc);
+ fs->jpc = NO_JUMP;
+}
+
+
+void luaK_patchlist (FuncState *fs, int list, int target) {
+ if (target == fs->pc)
+ luaK_patchtohere(fs, list);
+ else {
+ lua_assert(target < fs->pc);
+ patchlistaux(fs, list, target, NO_REG, target);
+ }
+}
+
+
+void luaK_patchtohere (FuncState *fs, int list) {
+ luaK_getlabel(fs);
+ luaK_concat(fs, &fs->jpc, list);
+}
+
+
+void luaK_concat (FuncState *fs, int *l1, int l2) {
+ if (l2 == NO_JUMP) return;
+ else if (*l1 == NO_JUMP)
+ *l1 = l2;
+ else {
+ int list = *l1;
+ int next;
+ while ((next = getjump(fs, list)) != NO_JUMP) /* find last element */
+ list = next;
+ fixjump(fs, list, l2);
+ }
+}
+
+
+void luaK_checkstack (FuncState *fs, int n) {
+ int newstack = fs->freereg + n;
+ if (newstack > fs->f->maxstacksize) {
+ if (newstack >= MAXSTACK)
+ luaX_syntaxerror(fs->ls, "function or expression too complex");
+ fs->f->maxstacksize = cast_byte(newstack);
+ }
+}
+
+
+void luaK_reserveregs (FuncState *fs, int n) {
+ luaK_checkstack(fs, n);
+ fs->freereg += n;
+}
+
+
+static void freereg (FuncState *fs, int reg) {
+ if (!ISK(reg) && reg >= fs->nactvar) {
+ fs->freereg--;
+ lua_assert(reg == fs->freereg);
+ }
+}
+
+
+static void freeexp (FuncState *fs, expdesc *e) {
+ if (e->k == VNONRELOC)
+ freereg(fs, e->u.s.info);
+}
+
+
+static int addk (FuncState *fs, TValue *k, TValue *v) {
+ lua_State *L = fs->L;
+ TValue *idx = luaH_set(L, fs->h, k);
+ Proto *f = fs->f;
+ int oldsize = f->sizek;
+ if (ttisnumber(idx)) {
+ lua_assert(luaO_rawequalObj(&fs->f->k[cast_int(nvalue(idx))], v));
+ return cast_int(nvalue(idx));
+ }
+ else { /* constant not found; create a new entry */
+ setnvalue(idx, cast_num(fs->nk));
+ luaM_growvector(L, f->k, fs->nk, f->sizek, TValue,
+ MAXARG_Bx, "constant table overflow");
+ while (oldsize < f->sizek) setnilvalue(&f->k[oldsize++]);
+ setobj(L, &f->k[fs->nk], v);
+ luaC_barrier(L, f, v);
+ return fs->nk++;
+ }
+}
+
+
+int luaK_stringK (FuncState *fs, TString *s) {
+ TValue o;
+ setsvalue(fs->L, &o, s);
+ return addk(fs, &o, &o);
+}
+
+
+int luaK_numberK (FuncState *fs, lua_Number r) {
+ TValue o;
+ setnvalue(&o, r);
+ return addk(fs, &o, &o);
+}
+
+
+static int boolK (FuncState *fs, int b) {
+ TValue o;
+ setbvalue(&o, b);
+ return addk(fs, &o, &o);
+}
+
+
+static int nilK (FuncState *fs) {
+ TValue k, v;
+ setnilvalue(&v);
+ /* cannot use nil as key; instead use table itself to represent nil */
+ sethvalue(fs->L, &k, fs->h);
+ return addk(fs, &k, &v);
+}
+
+
+void luaK_setreturns (FuncState *fs, expdesc *e, int nresults) {
+ if (e->k == VCALL) { /* expression is an open function call? */
+ SETARG_C(getcode(fs, e), nresults+1);
+ }
+ else if (e->k == VVARARG) {
+ SETARG_B(getcode(fs, e), nresults+1);
+ SETARG_A(getcode(fs, e), fs->freereg);
+ luaK_reserveregs(fs, 1);
+ }
+}
+
+
+void luaK_setoneret (FuncState *fs, expdesc *e) {
+ if (e->k == VCALL) { /* expression is an open function call? */
+ e->k = VNONRELOC;
+ e->u.s.info = GETARG_A(getcode(fs, e));
+ }
+ else if (e->k == VVARARG) {
+ SETARG_B(getcode(fs, e), 2);
+ e->k = VRELOCABLE; /* can relocate its simple result */
+ }
+}
+
+
+void luaK_dischargevars (FuncState *fs, expdesc *e) {
+ switch (e->k) {
+ case VLOCAL: {
+ e->k = VNONRELOC;
+ break;
+ }
+ case VUPVAL: {
+ e->u.s.info = luaK_codeABC(fs, OP_GETUPVAL, 0, e->u.s.info, 0);
+ e->k = VRELOCABLE;
+ break;
+ }
+ case VGLOBAL: {
+ e->u.s.info = luaK_codeABx(fs, OP_GETGLOBAL, 0, e->u.s.info);
+ e->k = VRELOCABLE;
+ break;
+ }
+ case VINDEXED: {
+ freereg(fs, e->u.s.aux);
+ freereg(fs, e->u.s.info);
+ e->u.s.info = luaK_codeABC(fs, OP_GETTABLE, 0, e->u.s.info, e->u.s.aux);
+ e->k = VRELOCABLE;
+ break;
+ }
+ case VVARARG:
+ case VCALL: {
+ luaK_setoneret(fs, e);
+ break;
+ }
+ default: break; /* there is one value available (somewhere) */
+ }
+}
+
+
+static int code_label (FuncState *fs, int A, int b, int jump) {
+ luaK_getlabel(fs); /* those instructions may be jump targets */
+ return luaK_codeABC(fs, OP_LOADBOOL, A, b, jump);
+}
+
+
+static void discharge2reg (FuncState *fs, expdesc *e, int reg) {
+ luaK_dischargevars(fs, e);
+ switch (e->k) {
+ case VNIL: {
+ luaK_nil(fs, reg, 1);
+ break;
+ }
+ case VFALSE: case VTRUE: {
+ luaK_codeABC(fs, OP_LOADBOOL, reg, e->k == VTRUE, 0);
+ break;
+ }
+ case VK: {
+ luaK_codeABx(fs, OP_LOADK, reg, e->u.s.info);
+ break;
+ }
+ case VKNUM: {
+ luaK_codeABx(fs, OP_LOADK, reg, luaK_numberK(fs, e->u.nval));
+ break;
+ }
+ case VRELOCABLE: {
+ Instruction *pc = &getcode(fs, e);
+ SETARG_A(*pc, reg);
+ break;
+ }
+ case VNONRELOC: {
+ if (reg != e->u.s.info)
+ luaK_codeABC(fs, OP_MOVE, reg, e->u.s.info, 0);
+ break;
+ }
+ default: {
+ lua_assert(e->k == VVOID || e->k == VJMP);
+ return; /* nothing to do... */
+ }
+ }
+ e->u.s.info = reg;
+ e->k = VNONRELOC;
+}
+
+
+static void discharge2anyreg (FuncState *fs, expdesc *e) {
+ if (e->k != VNONRELOC) {
+ luaK_reserveregs(fs, 1);
+ discharge2reg(fs, e, fs->freereg-1);
+ }
+}
+
+
+static void exp2reg (FuncState *fs, expdesc *e, int reg) {
+ discharge2reg(fs, e, reg);
+ if (e->k == VJMP)
+ luaK_concat(fs, &e->t, e->u.s.info); /* put this jump in `t' list */
+ if (hasjumps(e)) {
+ int final; /* position after whole expression */
+ int p_f = NO_JUMP; /* position of an eventual LOAD false */
+ int p_t = NO_JUMP; /* position of an eventual LOAD true */
+ if (need_value(fs, e->t) || need_value(fs, e->f)) {
+ int fj = (e->k == VJMP) ? NO_JUMP : luaK_jump(fs);
+ p_f = code_label(fs, reg, 0, 1);
+ p_t = code_label(fs, reg, 1, 0);
+ luaK_patchtohere(fs, fj);
+ }
+ final = luaK_getlabel(fs);
+ patchlistaux(fs, e->f, final, reg, p_f);
+ patchlistaux(fs, e->t, final, reg, p_t);
+ }
+ e->f = e->t = NO_JUMP;
+ e->u.s.info = reg;
+ e->k = VNONRELOC;
+}
+
+
+void luaK_exp2nextreg (FuncState *fs, expdesc *e) {
+ luaK_dischargevars(fs, e);
+ freeexp(fs, e);
+ luaK_reserveregs(fs, 1);
+ exp2reg(fs, e, fs->freereg - 1);
+}
+
+
+int luaK_exp2anyreg (FuncState *fs, expdesc *e) {
+ luaK_dischargevars(fs, e);
+ if (e->k == VNONRELOC) {
+ if (!hasjumps(e)) return e->u.s.info; /* exp is already in a register */
+ if (e->u.s.info >= fs->nactvar) { /* reg. is not a local? */
+ exp2reg(fs, e, e->u.s.info); /* put value on it */
+ return e->u.s.info;
+ }
+ }
+ luaK_exp2nextreg(fs, e); /* default */
+ return e->u.s.info;
+}
+
+
+void luaK_exp2val (FuncState *fs, expdesc *e) {
+ if (hasjumps(e))
+ luaK_exp2anyreg(fs, e);
+ else
+ luaK_dischargevars(fs, e);
+}
+
+
+int luaK_exp2RK (FuncState *fs, expdesc *e) {
+ luaK_exp2val(fs, e);
+ switch (e->k) {
+ case VKNUM:
+ case VTRUE:
+ case VFALSE:
+ case VNIL: {
+ if (fs->nk <= MAXINDEXRK) { /* constant fit in RK operand? */
+ e->u.s.info = (e->k == VNIL) ? nilK(fs) :
+ (e->k == VKNUM) ? luaK_numberK(fs, e->u.nval) :
+ boolK(fs, (e->k == VTRUE));
+ e->k = VK;
+ return RKASK(e->u.s.info);
+ }
+ else break;
+ }
+ case VK: {
+ if (e->u.s.info <= MAXINDEXRK) /* constant fit in argC? */
+ return RKASK(e->u.s.info);
+ else break;
+ }
+ default: break;
+ }
+ /* not a constant in the right range: put it in a register */
+ return luaK_exp2anyreg(fs, e);
+}
+
+
+void luaK_storevar (FuncState *fs, expdesc *var, expdesc *ex) {
+ switch (var->k) {
+ case VLOCAL: {
+ freeexp(fs, ex);
+ exp2reg(fs, ex, var->u.s.info);
+ return;
+ }
+ case VUPVAL: {
+ int e = luaK_exp2anyreg(fs, ex);
+ luaK_codeABC(fs, OP_SETUPVAL, e, var->u.s.info, 0);
+ break;
+ }
+ case VGLOBAL: {
+ int e = luaK_exp2anyreg(fs, ex);
+ luaK_codeABx(fs, OP_SETGLOBAL, e, var->u.s.info);
+ break;
+ }
+ case VINDEXED: {
+ int e = luaK_exp2RK(fs, ex);
+ luaK_codeABC(fs, OP_SETTABLE, var->u.s.info, var->u.s.aux, e);
+ break;
+ }
+ default: {
+ lua_assert(0); /* invalid var kind to store */
+ break;
+ }
+ }
+ freeexp(fs, ex);
+}
+
+
+void luaK_self (FuncState *fs, expdesc *e, expdesc *key) {
+ int func;
+ luaK_exp2anyreg(fs, e);
+ freeexp(fs, e);
+ func = fs->freereg;
+ luaK_reserveregs(fs, 2);
+ luaK_codeABC(fs, OP_SELF, func, e->u.s.info, luaK_exp2RK(fs, key));
+ freeexp(fs, key);
+ e->u.s.info = func;
+ e->k = VNONRELOC;
+}
+
+
+static void invertjump (FuncState *fs, expdesc *e) {
+ Instruction *pc = getjumpcontrol(fs, e->u.s.info);
+ lua_assert(testTMode(GET_OPCODE(*pc)) && GET_OPCODE(*pc) != OP_TESTSET &&
+ GET_OPCODE(*pc) != OP_TEST);
+ SETARG_A(*pc, !(GETARG_A(*pc)));
+}
+
+
+static int jumponcond (FuncState *fs, expdesc *e, int cond) {
+ if (e->k == VRELOCABLE) {
+ Instruction ie = getcode(fs, e);
+ if (GET_OPCODE(ie) == OP_NOT) {
+ fs->pc--; /* remove previous OP_NOT */
+ return condjump(fs, OP_TEST, GETARG_B(ie), 0, !cond);
+ }
+ /* else go through */
+ }
+ discharge2anyreg(fs, e);
+ freeexp(fs, e);
+ return condjump(fs, OP_TESTSET, NO_REG, e->u.s.info, cond);
+}
+
+
+void luaK_goiftrue (FuncState *fs, expdesc *e) {
+ int pc; /* pc of last jump */
+ luaK_dischargevars(fs, e);
+ switch (e->k) {
+ case VK: case VKNUM: case VTRUE: {
+ pc = NO_JUMP; /* always true; do nothing */
+ break;
+ }
+ case VFALSE: {
+ pc = luaK_jump(fs); /* always jump */
+ break;
+ }
+ case VJMP: {
+ invertjump(fs, e);
+ pc = e->u.s.info;
+ break;
+ }
+ default: {
+ pc = jumponcond(fs, e, 0);
+ break;
+ }
+ }
+ luaK_concat(fs, &e->f, pc); /* insert last jump in `f' list */
+ luaK_patchtohere(fs, e->t);
+ e->t = NO_JUMP;
+}
+
+
+static void luaK_goiffalse (FuncState *fs, expdesc *e) {
+ int pc; /* pc of last jump */
+ luaK_dischargevars(fs, e);
+ switch (e->k) {
+ case VNIL: case VFALSE: {
+ pc = NO_JUMP; /* always false; do nothing */
+ break;
+ }
+ case VTRUE: {
+ pc = luaK_jump(fs); /* always jump */
+ break;
+ }
+ case VJMP: {
+ pc = e->u.s.info;
+ break;
+ }
+ default: {
+ pc = jumponcond(fs, e, 1);
+ break;
+ }
+ }
+ luaK_concat(fs, &e->t, pc); /* insert last jump in `t' list */
+ luaK_patchtohere(fs, e->f);
+ e->f = NO_JUMP;
+}
+
+
+static void codenot (FuncState *fs, expdesc *e) {
+ luaK_dischargevars(fs, e);
+ switch (e->k) {
+ case VNIL: case VFALSE: {
+ e->k = VTRUE;
+ break;
+ }
+ case VK: case VKNUM: case VTRUE: {
+ e->k = VFALSE;
+ break;
+ }
+ case VJMP: {
+ invertjump(fs, e);
+ break;
+ }
+ case VRELOCABLE:
+ case VNONRELOC: {
+ discharge2anyreg(fs, e);
+ freeexp(fs, e);
+ e->u.s.info = luaK_codeABC(fs, OP_NOT, 0, e->u.s.info, 0);
+ e->k = VRELOCABLE;
+ break;
+ }
+ default: {
+ lua_assert(0); /* cannot happen */
+ break;
+ }
+ }
+ /* interchange true and false lists */
+ { int temp = e->f; e->f = e->t; e->t = temp; }
+ removevalues(fs, e->f);
+ removevalues(fs, e->t);
+}
+
+
+void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) {
+ t->u.s.aux = luaK_exp2RK(fs, k);
+ t->k = VINDEXED;
+}
+
+
+static int constfolding (OpCode op, expdesc *e1, expdesc *e2) {
+ lua_Number v1, v2, r;
+ if (!isnumeral(e1) || !isnumeral(e2)) return 0;
+ v1 = e1->u.nval;
+ v2 = e2->u.nval;
+ switch (op) {
+ case OP_ADD: r = luai_numadd(v1, v2); break;
+ case OP_SUB: r = luai_numsub(v1, v2); break;
+ case OP_MUL: r = luai_nummul(v1, v2); break;
+ case OP_DIV:
+ if (v2 == 0) return 0; /* do not attempt to divide by 0 */
+ r = luai_numdiv(v1, v2); break;
+ case OP_MOD:
+ if (v2 == 0) return 0; /* do not attempt to divide by 0 */
+ r = luai_nummod(v1, v2); break;
+ case OP_POW: r = luai_numpow(v1, v2); break;
+ case OP_UNM: r = luai_numunm(v1); break;
+ case OP_LEN: return 0; /* no constant folding for 'len' */
+ default: lua_assert(0); r = 0; break;
+ }
+ if (luai_numisnan(r)) return 0; /* do not attempt to produce NaN */
+ e1->u.nval = r;
+ return 1;
+}
+
+
+static void codearith (FuncState *fs, OpCode op, expdesc *e1, expdesc *e2) {
+ if (constfolding(op, e1, e2))
+ return;
+ else {
+ int o2 = (op != OP_UNM && op != OP_LEN) ? luaK_exp2RK(fs, e2) : 0;
+ int o1 = luaK_exp2RK(fs, e1);
+ if (o1 > o2) {
+ freeexp(fs, e1);
+ freeexp(fs, e2);
+ }
+ else {
+ freeexp(fs, e2);
+ freeexp(fs, e1);
+ }
+ e1->u.s.info = luaK_codeABC(fs, op, 0, o1, o2);
+ e1->k = VRELOCABLE;
+ }
+}
+
+
+static void codecomp (FuncState *fs, OpCode op, int cond, expdesc *e1,
+ expdesc *e2) {
+ int o1 = luaK_exp2RK(fs, e1);
+ int o2 = luaK_exp2RK(fs, e2);
+ freeexp(fs, e2);
+ freeexp(fs, e1);
+ if (cond == 0 && op != OP_EQ) {
+ int temp; /* exchange args to replace by `<' or `<=' */
+ temp = o1; o1 = o2; o2 = temp; /* o1 <==> o2 */
+ cond = 1;
+ }
+ e1->u.s.info = condjump(fs, op, cond, o1, o2);
+ e1->k = VJMP;
+}
+
+
+void luaK_prefix (FuncState *fs, UnOpr op, expdesc *e) {
+ expdesc e2;
+ e2.t = e2.f = NO_JUMP; e2.k = VKNUM; e2.u.nval = 0;
+ switch (op) {
+ case OPR_MINUS: {
+ if (!isnumeral(e))
+ luaK_exp2anyreg(fs, e); /* cannot operate on non-numeric constants */
+ codearith(fs, OP_UNM, e, &e2);
+ break;
+ }
+ case OPR_NOT: codenot(fs, e); break;
+ case OPR_LEN: {
+ luaK_exp2anyreg(fs, e); /* cannot operate on constants */
+ codearith(fs, OP_LEN, e, &e2);
+ break;
+ }
+ default: lua_assert(0);
+ }
+}
+
+
+void luaK_infix (FuncState *fs, BinOpr op, expdesc *v) {
+ switch (op) {
+ case OPR_AND: {
+ luaK_goiftrue(fs, v);
+ break;
+ }
+ case OPR_OR: {
+ luaK_goiffalse(fs, v);
+ break;
+ }
+ case OPR_CONCAT: {
+ luaK_exp2nextreg(fs, v); /* operand must be on the `stack' */
+ break;
+ }
+ case OPR_ADD: case OPR_SUB: case OPR_MUL: case OPR_DIV:
+ case OPR_MOD: case OPR_POW: {
+ if (!isnumeral(v)) luaK_exp2RK(fs, v);
+ break;
+ }
+ default: {
+ luaK_exp2RK(fs, v);
+ break;
+ }
+ }
+}
+
+
+void luaK_posfix (FuncState *fs, BinOpr op, expdesc *e1, expdesc *e2) {
+ switch (op) {
+ case OPR_AND: {
+ lua_assert(e1->t == NO_JUMP); /* list must be closed */
+ luaK_dischargevars(fs, e2);
+ luaK_concat(fs, &e2->f, e1->f);
+ *e1 = *e2;
+ break;
+ }
+ case OPR_OR: {
+ lua_assert(e1->f == NO_JUMP); /* list must be closed */
+ luaK_dischargevars(fs, e2);
+ luaK_concat(fs, &e2->t, e1->t);
+ *e1 = *e2;
+ break;
+ }
+ case OPR_CONCAT: {
+ luaK_exp2val(fs, e2);
+ if (e2->k == VRELOCABLE && GET_OPCODE(getcode(fs, e2)) == OP_CONCAT) {
+ lua_assert(e1->u.s.info == GETARG_B(getcode(fs, e2))-1);
+ freeexp(fs, e1);
+ SETARG_B(getcode(fs, e2), e1->u.s.info);
+ e1->k = VRELOCABLE; e1->u.s.info = e2->u.s.info;
+ }
+ else {
+ luaK_exp2nextreg(fs, e2); /* operand must be on the 'stack' */
+ codearith(fs, OP_CONCAT, e1, e2);
+ }
+ break;
+ }
+ case OPR_ADD: codearith(fs, OP_ADD, e1, e2); break;
+ case OPR_SUB: codearith(fs, OP_SUB, e1, e2); break;
+ case OPR_MUL: codearith(fs, OP_MUL, e1, e2); break;
+ case OPR_DIV: codearith(fs, OP_DIV, e1, e2); break;
+ case OPR_MOD: codearith(fs, OP_MOD, e1, e2); break;
+ case OPR_POW: codearith(fs, OP_POW, e1, e2); break;
+ case OPR_EQ: codecomp(fs, OP_EQ, 1, e1, e2); break;
+ case OPR_NE: codecomp(fs, OP_EQ, 0, e1, e2); break;
+ case OPR_LT: codecomp(fs, OP_LT, 1, e1, e2); break;
+ case OPR_LE: codecomp(fs, OP_LE, 1, e1, e2); break;
+ case OPR_GT: codecomp(fs, OP_LT, 0, e1, e2); break;
+ case OPR_GE: codecomp(fs, OP_LE, 0, e1, e2); break;
+ default: lua_assert(0);
+ }
+}
+
+
+void luaK_fixline (FuncState *fs, int line) {
+ fs->f->lineinfo[fs->pc - 1] = line;
+}
+
+
+static int luaK_code (FuncState *fs, Instruction i, int line) {
+ Proto *f = fs->f;
+ dischargejpc(fs); /* `pc' will change */
+ /* put new instruction in code array */
+ luaM_growvector(fs->L, f->code, fs->pc, f->sizecode, Instruction,
+ MAX_INT, "code size overflow");
+ f->code[fs->pc] = i;
+ /* save corresponding line information */
+ luaM_growvector(fs->L, f->lineinfo, fs->pc, f->sizelineinfo, int,
+ MAX_INT, "code size overflow");
+ f->lineinfo[fs->pc] = line;
+ return fs->pc++;
+}
+
+
+int luaK_codeABC (FuncState *fs, OpCode o, int a, int b, int c) {
+ lua_assert(getOpMode(o) == iABC);
+ lua_assert(getBMode(o) != OpArgN || b == 0);
+ lua_assert(getCMode(o) != OpArgN || c == 0);
+ return luaK_code(fs, CREATE_ABC(o, a, b, c), fs->ls->lastline);
+}
+
+
+int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) {
+ lua_assert(getOpMode(o) == iABx || getOpMode(o) == iAsBx);
+ lua_assert(getCMode(o) == OpArgN);
+ return luaK_code(fs, CREATE_ABx(o, a, bc), fs->ls->lastline);
+}
+
+
+void luaK_setlist (FuncState *fs, int base, int nelems, int tostore) {
+ int c = (nelems - 1)/LFIELDS_PER_FLUSH + 1;
+ int b = (tostore == LUA_MULTRET) ? 0 : tostore;
+ lua_assert(tostore != 0);
+ if (c <= MAXARG_C)
+ luaK_codeABC(fs, OP_SETLIST, base, b, c);
+ else {
+ luaK_codeABC(fs, OP_SETLIST, base, b, 0);
+ luaK_code(fs, cast(Instruction, c), fs->ls->lastline);
+ }
+ fs->freereg = base + 1; /* free registers with list values */
+}
+
diff --git a/lib/lua/src/lcode.h b/lib/lua/src/lcode.h
new file mode 100644
index 000000000..b941c6072
--- /dev/null
+++ b/lib/lua/src/lcode.h
@@ -0,0 +1,76 @@
+/*
+** $Id: lcode.h,v 1.48.1.1 2007/12/27 13:02:25 roberto Exp $
+** Code generator for Lua
+** See Copyright Notice in lua.h
+*/
+
+#ifndef lcode_h
+#define lcode_h
+
+#include "llex.h"
+#include "lobject.h"
+#include "lopcodes.h"
+#include "lparser.h"
+
+
+/*
+** Marks the end of a patch list. It is an invalid value both as an absolute
+** address, and as a list link (would link an element to itself).
+*/
+#define NO_JUMP (-1)
+
+
+/*
+** grep "ORDER OPR" if you change these enums
+*/
+typedef enum BinOpr {
+ OPR_ADD, OPR_SUB, OPR_MUL, OPR_DIV, OPR_MOD, OPR_POW,
+ OPR_CONCAT,
+ OPR_NE, OPR_EQ,
+ OPR_LT, OPR_LE, OPR_GT, OPR_GE,
+ OPR_AND, OPR_OR,
+ OPR_NOBINOPR
+} BinOpr;
+
+
+typedef enum UnOpr { OPR_MINUS, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr;
+
+
+#define getcode(fs,e) ((fs)->f->code[(e)->u.s.info])
+
+#define luaK_codeAsBx(fs,o,A,sBx) luaK_codeABx(fs,o,A,(sBx)+MAXARG_sBx)
+
+#define luaK_setmultret(fs,e) luaK_setreturns(fs, e, LUA_MULTRET)
+
+LUAI_FUNC int luaK_codeABx (FuncState *fs, OpCode o, int A, unsigned int Bx);
+LUAI_FUNC int luaK_codeABC (FuncState *fs, OpCode o, int A, int B, int C);
+LUAI_FUNC void luaK_fixline (FuncState *fs, int line);
+LUAI_FUNC void luaK_nil (FuncState *fs, int from, int n);
+LUAI_FUNC void luaK_reserveregs (FuncState *fs, int n);
+LUAI_FUNC void luaK_checkstack (FuncState *fs, int n);
+LUAI_FUNC int luaK_stringK (FuncState *fs, TString *s);
+LUAI_FUNC int luaK_numberK (FuncState *fs, lua_Number r);
+LUAI_FUNC void luaK_dischargevars (FuncState *fs, expdesc *e);
+LUAI_FUNC int luaK_exp2anyreg (FuncState *fs, expdesc *e);
+LUAI_FUNC void luaK_exp2nextreg (FuncState *fs, expdesc *e);
+LUAI_FUNC void luaK_exp2val (FuncState *fs, expdesc *e);
+LUAI_FUNC int luaK_exp2RK (FuncState *fs, expdesc *e);
+LUAI_FUNC void luaK_self (FuncState *fs, expdesc *e, expdesc *key);
+LUAI_FUNC void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k);
+LUAI_FUNC void luaK_goiftrue (FuncState *fs, expdesc *e);
+LUAI_FUNC void luaK_storevar (FuncState *fs, expdesc *var, expdesc *e);
+LUAI_FUNC void luaK_setreturns (FuncState *fs, expdesc *e, int nresults);
+LUAI_FUNC void luaK_setoneret (FuncState *fs, expdesc *e);
+LUAI_FUNC int luaK_jump (FuncState *fs);
+LUAI_FUNC void luaK_ret (FuncState *fs, int first, int nret);
+LUAI_FUNC void luaK_patchlist (FuncState *fs, int list, int target);
+LUAI_FUNC void luaK_patchtohere (FuncState *fs, int list);
+LUAI_FUNC void luaK_concat (FuncState *fs, int *l1, int l2);
+LUAI_FUNC int luaK_getlabel (FuncState *fs);
+LUAI_FUNC void luaK_prefix (FuncState *fs, UnOpr op, expdesc *v);
+LUAI_FUNC void luaK_infix (FuncState *fs, BinOpr op, expdesc *v);
+LUAI_FUNC void luaK_posfix (FuncState *fs, BinOpr op, expdesc *v1, expdesc *v2);
+LUAI_FUNC void luaK_setlist (FuncState *fs, int base, int nelems, int tostore);
+
+
+#endif
diff --git a/lib/lua/src/ldblib.c b/lib/lua/src/ldblib.c
new file mode 100644
index 000000000..67de1222a
--- /dev/null
+++ b/lib/lua/src/ldblib.c
@@ -0,0 +1,397 @@
+/*
+** $Id: ldblib.c,v 1.104.1.3 2008/01/21 13:11:21 roberto Exp $
+** Interface from Lua to its debug API
+** See Copyright Notice in lua.h
+*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define ldblib_c
+#define LUA_LIB
+
+#include "lua.h"
+
+#include "lauxlib.h"
+#include "lualib.h"
+
+
+
+static int db_getregistry (lua_State *L) {
+ lua_pushvalue(L, LUA_REGISTRYINDEX);
+ return 1;
+}
+
+
+static int db_getmetatable (lua_State *L) {
+ luaL_checkany(L, 1);
+ if (!lua_getmetatable(L, 1)) {
+ lua_pushnil(L); /* no metatable */
+ }
+ return 1;
+}
+
+
+static int db_setmetatable (lua_State *L) {
+ int t = lua_type(L, 2);
+ luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2,
+ "nil or table expected");
+ lua_settop(L, 2);
+ lua_pushboolean(L, lua_setmetatable(L, 1));
+ return 1;
+}
+
+
+static int db_getfenv (lua_State *L) {
+ lua_getfenv(L, 1);
+ return 1;
+}
+
+
+static int db_setfenv (lua_State *L) {
+ luaL_checktype(L, 2, LUA_TTABLE);
+ lua_settop(L, 2);
+ if (lua_setfenv(L, 1) == 0)
+ luaL_error(L, LUA_QL("setfenv")
+ " cannot change environment of given object");
+ return 1;
+}
+
+
+static void settabss (lua_State *L, const char *i, const char *v) {
+ lua_pushstring(L, v);
+ lua_setfield(L, -2, i);
+}
+
+
+static void settabsi (lua_State *L, const char *i, int v) {
+ lua_pushinteger(L, v);
+ lua_setfield(L, -2, i);
+}
+
+
+static lua_State *getthread (lua_State *L, int *arg) {
+ if (lua_isthread(L, 1)) {
+ *arg = 1;
+ return lua_tothread(L, 1);
+ }
+ else {
+ *arg = 0;
+ return L;
+ }
+}
+
+
+static void treatstackoption (lua_State *L, lua_State *L1, const char *fname) {
+ if (L == L1) {
+ lua_pushvalue(L, -2);
+ lua_remove(L, -3);
+ }
+ else
+ lua_xmove(L1, L, 1);
+ lua_setfield(L, -2, fname);
+}
+
+
+static int db_getinfo (lua_State *L) {
+ lua_Debug ar;
+ int arg;
+ lua_State *L1 = getthread(L, &arg);
+ const char *options = luaL_optstring(L, arg+2, "flnSu");
+ if (lua_isnumber(L, arg+1)) {
+ if (!lua_getstack(L1, (int)lua_tointeger(L, arg+1), &ar)) {
+ lua_pushnil(L); /* level out of range */
+ return 1;
+ }
+ }
+ else if (lua_isfunction(L, arg+1)) {
+ lua_pushfstring(L, ">%s", options);
+ options = lua_tostring(L, -1);
+ lua_pushvalue(L, arg+1);
+ lua_xmove(L, L1, 1);
+ }
+ else
+ return luaL_argerror(L, arg+1, "function or level expected");
+ if (!lua_getinfo(L1, options, &ar))
+ return luaL_argerror(L, arg+2, "invalid option");
+ lua_createtable(L, 0, 2);
+ if (strchr(options, 'S')) {
+ settabss(L, "source", ar.source);
+ settabss(L, "short_src", ar.short_src);
+ settabsi(L, "linedefined", ar.linedefined);
+ settabsi(L, "lastlinedefined", ar.lastlinedefined);
+ settabss(L, "what", ar.what);
+ }
+ if (strchr(options, 'l'))
+ settabsi(L, "currentline", ar.currentline);
+ if (strchr(options, 'u'))
+ settabsi(L, "nups", ar.nups);
+ if (strchr(options, 'n')) {
+ settabss(L, "name", ar.name);
+ settabss(L, "namewhat", ar.namewhat);
+ }
+ if (strchr(options, 'L'))
+ treatstackoption(L, L1, "activelines");
+ if (strchr(options, 'f'))
+ treatstackoption(L, L1, "func");
+ return 1; /* return table */
+}
+
+
+static int db_getlocal (lua_State *L) {
+ int arg;
+ lua_State *L1 = getthread(L, &arg);
+ lua_Debug ar;
+ const char *name;
+ if (!lua_getstack(L1, luaL_checkint(L, arg+1), &ar)) /* out of range? */
+ return luaL_argerror(L, arg+1, "level out of range");
+ name = lua_getlocal(L1, &ar, luaL_checkint(L, arg+2));
+ if (name) {
+ lua_xmove(L1, L, 1);
+ lua_pushstring(L, name);
+ lua_pushvalue(L, -2);
+ return 2;
+ }
+ else {
+ lua_pushnil(L);
+ return 1;
+ }
+}
+
+
+static int db_setlocal (lua_State *L) {
+ int arg;
+ lua_State *L1 = getthread(L, &arg);
+ lua_Debug ar;
+ if (!lua_getstack(L1, luaL_checkint(L, arg+1), &ar)) /* out of range? */
+ return luaL_argerror(L, arg+1, "level out of range");
+ luaL_checkany(L, arg+3);
+ lua_settop(L, arg+3);
+ lua_xmove(L, L1, 1);
+ lua_pushstring(L, lua_setlocal(L1, &ar, luaL_checkint(L, arg+2)));
+ return 1;
+}
+
+
+static int auxupvalue (lua_State *L, int get) {
+ const char *name;
+ int n = luaL_checkint(L, 2);
+ luaL_checktype(L, 1, LUA_TFUNCTION);
+ if (lua_iscfunction(L, 1)) return 0; /* cannot touch C upvalues from Lua */
+ name = get ? lua_getupvalue(L, 1, n) : lua_setupvalue(L, 1, n);
+ if (name == NULL) return 0;
+ lua_pushstring(L, name);
+ lua_insert(L, -(get+1));
+ return get + 1;
+}
+
+
+static int db_getupvalue (lua_State *L) {
+ return auxupvalue(L, 1);
+}
+
+
+static int db_setupvalue (lua_State *L) {
+ luaL_checkany(L, 3);
+ return auxupvalue(L, 0);
+}
+
+
+
+static const char KEY_HOOK = 'h';
+
+
+static void hookf (lua_State *L, lua_Debug *ar) {
+ static const char *const hooknames[] =
+ {"call", "return", "line", "count", "tail return"};
+ lua_pushlightuserdata(L, (void *)&KEY_HOOK);
+ lua_rawget(L, LUA_REGISTRYINDEX);
+ lua_pushlightuserdata(L, L);
+ lua_rawget(L, -2);
+ if (lua_isfunction(L, -1)) {
+ lua_pushstring(L, hooknames[(int)ar->event]);
+ if (ar->currentline >= 0)
+ lua_pushinteger(L, ar->currentline);
+ else lua_pushnil(L);
+ lua_assert(lua_getinfo(L, "lS", ar));
+ lua_call(L, 2, 0);
+ }
+}
+
+
+static int makemask (const char *smask, int count) {
+ int mask = 0;
+ if (strchr(smask, 'c')) mask |= LUA_MASKCALL;
+ if (strchr(smask, 'r')) mask |= LUA_MASKRET;
+ if (strchr(smask, 'l')) mask |= LUA_MASKLINE;
+ if (count > 0) mask |= LUA_MASKCOUNT;
+ return mask;
+}
+
+
+static char *unmakemask (int mask, char *smask) {
+ int i = 0;
+ if (mask & LUA_MASKCALL) smask[i++] = 'c';
+ if (mask & LUA_MASKRET) smask[i++] = 'r';
+ if (mask & LUA_MASKLINE) smask[i++] = 'l';
+ smask[i] = '\0';
+ return smask;
+}
+
+
+static void gethooktable (lua_State *L) {
+ lua_pushlightuserdata(L, (void *)&KEY_HOOK);
+ lua_rawget(L, LUA_REGISTRYINDEX);
+ if (!lua_istable(L, -1)) {
+ lua_pop(L, 1);
+ lua_createtable(L, 0, 1);
+ lua_pushlightuserdata(L, (void *)&KEY_HOOK);
+ lua_pushvalue(L, -2);
+ lua_rawset(L, LUA_REGISTRYINDEX);
+ }
+}
+
+
+static int db_sethook (lua_State *L) {
+ int arg, mask, count;
+ lua_Hook func;
+ lua_State *L1 = getthread(L, &arg);
+ if (lua_isnoneornil(L, arg+1)) {
+ lua_settop(L, arg+1);
+ func = NULL; mask = 0; count = 0; /* turn off hooks */
+ }
+ else {
+ const char *smask = luaL_checkstring(L, arg+2);
+ luaL_checktype(L, arg+1, LUA_TFUNCTION);
+ count = luaL_optint(L, arg+3, 0);
+ func = hookf; mask = makemask(smask, count);
+ }
+ gethooktable(L);
+ lua_pushlightuserdata(L, L1);
+ lua_pushvalue(L, arg+1);
+ lua_rawset(L, -3); /* set new hook */
+ lua_pop(L, 1); /* remove hook table */
+ lua_sethook(L1, func, mask, count); /* set hooks */
+ return 0;
+}
+
+
+static int db_gethook (lua_State *L) {
+ int arg;
+ lua_State *L1 = getthread(L, &arg);
+ char buff[5];
+ int mask = lua_gethookmask(L1);
+ lua_Hook hook = lua_gethook(L1);
+ if (hook != NULL && hook != hookf) /* external hook? */
+ lua_pushliteral(L, "external hook");
+ else {
+ gethooktable(L);
+ lua_pushlightuserdata(L, L1);
+ lua_rawget(L, -2); /* get hook */
+ lua_remove(L, -2); /* remove hook table */
+ }
+ lua_pushstring(L, unmakemask(mask, buff));
+ lua_pushinteger(L, lua_gethookcount(L1));
+ return 3;
+}
+
+
+static int db_debug (lua_State *L) {
+ for (;;) {
+ char buffer[250];
+ fputs("lua_debug> ", stderr);
+ if (fgets(buffer, sizeof(buffer), stdin) == 0 ||
+ strcmp(buffer, "cont\n") == 0)
+ return 0;
+ if (luaL_loadbuffer(L, buffer, strlen(buffer), "=(debug command)") ||
+ lua_pcall(L, 0, 0, 0)) {
+ fputs(lua_tostring(L, -1), stderr);
+ fputs("\n", stderr);
+ }
+ lua_settop(L, 0); /* remove eventual returns */
+ }
+}
+
+
+#define LEVELS1 12 /* size of the first part of the stack */
+#define LEVELS2 10 /* size of the second part of the stack */
+
+static int db_errorfb (lua_State *L) {
+ int level;
+ int firstpart = 1; /* still before eventual `...' */
+ int arg;
+ lua_State *L1 = getthread(L, &arg);
+ lua_Debug ar;
+ if (lua_isnumber(L, arg+2)) {
+ level = (int)lua_tointeger(L, arg+2);
+ lua_pop(L, 1);
+ }
+ else
+ level = (L == L1) ? 1 : 0; /* level 0 may be this own function */
+ if (lua_gettop(L) == arg)
+ lua_pushliteral(L, "");
+ else if (!lua_isstring(L, arg+1)) return 1; /* message is not a string */
+ else lua_pushliteral(L, "\n");
+ lua_pushliteral(L, "stack traceback:");
+ while (lua_getstack(L1, level++, &ar)) {
+ if (level > LEVELS1 && firstpart) {
+ /* no more than `LEVELS2' more levels? */
+ if (!lua_getstack(L1, level+LEVELS2, &ar))
+ level--; /* keep going */
+ else {
+ lua_pushliteral(L, "\n\t..."); /* too many levels */
+ while (lua_getstack(L1, level+LEVELS2, &ar)) /* find last levels */
+ level++;
+ }
+ firstpart = 0;
+ continue;
+ }
+ lua_pushliteral(L, "\n\t");
+ lua_getinfo(L1, "Snl", &ar);
+ lua_pushfstring(L, "%s:", ar.short_src);
+ if (ar.currentline > 0)
+ lua_pushfstring(L, "%d:", ar.currentline);
+ if (*ar.namewhat != '\0') /* is there a name? */
+ lua_pushfstring(L, " in function " LUA_QS, ar.name);
+ else {
+ if (*ar.what == 'm') /* main? */
+ lua_pushfstring(L, " in main chunk");
+ else if (*ar.what == 'C' || *ar.what == 't')
+ lua_pushliteral(L, " ?"); /* C function or tail call */
+ else
+ lua_pushfstring(L, " in function <%s:%d>",
+ ar.short_src, ar.linedefined);
+ }
+ lua_concat(L, lua_gettop(L) - arg);
+ }
+ lua_concat(L, lua_gettop(L) - arg);
+ return 1;
+}
+
+
+static const luaL_Reg dblib[] = {
+ {"debug", db_debug},
+ {"getfenv", db_getfenv},
+ {"gethook", db_gethook},
+ {"getinfo", db_getinfo},
+ {"getlocal", db_getlocal},
+ {"getregistry", db_getregistry},
+ {"getmetatable", db_getmetatable},
+ {"getupvalue", db_getupvalue},
+ {"setfenv", db_setfenv},
+ {"sethook", db_sethook},
+ {"setlocal", db_setlocal},
+ {"setmetatable", db_setmetatable},
+ {"setupvalue", db_setupvalue},
+ {"traceback", db_errorfb},
+ {NULL, NULL}
+};
+
+
+LUALIB_API int luaopen_debug (lua_State *L) {
+ luaL_register(L, LUA_DBLIBNAME, dblib);
+ return 1;
+}
+
diff --git a/lib/lua/src/ldebug.c b/lib/lua/src/ldebug.c
new file mode 100644
index 000000000..50ad3d380
--- /dev/null
+++ b/lib/lua/src/ldebug.c
@@ -0,0 +1,638 @@
+/*
+** $Id: ldebug.c,v 2.29.1.6 2008/05/08 16:56:26 roberto Exp $
+** Debug Interface
+** See Copyright Notice in lua.h
+*/
+
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+
+
+#define ldebug_c
+#define LUA_CORE
+
+#include "lua.h"
+
+#include "lapi.h"
+#include "lcode.h"
+#include "ldebug.h"
+#include "ldo.h"
+#include "lfunc.h"
+#include "lobject.h"
+#include "lopcodes.h"
+#include "lstate.h"
+#include "lstring.h"
+#include "ltable.h"
+#include "ltm.h"
+#include "lvm.h"
+
+
+
+static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name);
+
+
+static int currentpc (lua_State *L, CallInfo *ci) {
+ if (!isLua(ci)) return -1; /* function is not a Lua function? */
+ if (ci == L->ci)
+ ci->savedpc = L->savedpc;
+ return pcRel(ci->savedpc, ci_func(ci)->l.p);
+}
+
+
+static int currentline (lua_State *L, CallInfo *ci) {
+ int pc = currentpc(L, ci);
+ if (pc < 0)
+ return -1; /* only active lua functions have current-line information */
+ else
+ return getline(ci_func(ci)->l.p, pc);
+}
+
+
+/*
+** this function can be called asynchronous (e.g. during a signal)
+*/
+LUA_API int lua_sethook (lua_State *L, lua_Hook func, int mask, int count) {
+ if (func == NULL || mask == 0) { /* turn off hooks? */
+ mask = 0;
+ func = NULL;
+ }
+ L->hook = func;
+ L->basehookcount = count;
+ resethookcount(L);
+ L->hookmask = cast_byte(mask);
+ return 1;
+}
+
+
+LUA_API lua_Hook lua_gethook (lua_State *L) {
+ return L->hook;
+}
+
+
+LUA_API int lua_gethookmask (lua_State *L) {
+ return L->hookmask;
+}
+
+
+LUA_API int lua_gethookcount (lua_State *L) {
+ return L->basehookcount;
+}
+
+
+LUA_API int lua_getstack (lua_State *L, int level, lua_Debug *ar) {
+ int status;
+ CallInfo *ci;
+ lua_lock(L);
+ for (ci = L->ci; level > 0 && ci > L->base_ci; ci--) {
+ level--;
+ if (f_isLua(ci)) /* Lua function? */
+ level -= ci->tailcalls; /* skip lost tail calls */
+ }
+ if (level == 0 && ci > L->base_ci) { /* level found? */
+ status = 1;
+ ar->i_ci = cast_int(ci - L->base_ci);
+ }
+ else if (level < 0) { /* level is of a lost tail call? */
+ status = 1;
+ ar->i_ci = 0;
+ }
+ else status = 0; /* no such level */
+ lua_unlock(L);
+ return status;
+}
+
+
+static Proto *getluaproto (CallInfo *ci) {
+ return (isLua(ci) ? ci_func(ci)->l.p : NULL);
+}
+
+
+static const char *findlocal (lua_State *L, CallInfo *ci, int n) {
+ const char *name;
+ Proto *fp = getluaproto(ci);
+ if (fp && (name = luaF_getlocalname(fp, n, currentpc(L, ci))) != NULL)
+ return name; /* is a local variable in a Lua function */
+ else {
+ StkId limit = (ci == L->ci) ? L->top : (ci+1)->func;
+ if (limit - ci->base >= n && n > 0) /* is 'n' inside 'ci' stack? */
+ return "(*temporary)";
+ else
+ return NULL;
+ }
+}
+
+
+LUA_API const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n) {
+ CallInfo *ci = L->base_ci + ar->i_ci;
+ const char *name = findlocal(L, ci, n);
+ lua_lock(L);
+ if (name)
+ luaA_pushobject(L, ci->base + (n - 1));
+ lua_unlock(L);
+ return name;
+}
+
+
+LUA_API const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n) {
+ CallInfo *ci = L->base_ci + ar->i_ci;
+ const char *name = findlocal(L, ci, n);
+ lua_lock(L);
+ if (name)
+ setobjs2s(L, ci->base + (n - 1), L->top - 1);
+ L->top--; /* pop value */
+ lua_unlock(L);
+ return name;
+}
+
+
+static void funcinfo (lua_Debug *ar, Closure *cl) {
+ if (cl->c.isC) {
+ ar->source = "=[C]";
+ ar->linedefined = -1;
+ ar->lastlinedefined = -1;
+ ar->what = "C";
+ }
+ else {
+ ar->source = getstr(cl->l.p->source);
+ ar->linedefined = cl->l.p->linedefined;
+ ar->lastlinedefined = cl->l.p->lastlinedefined;
+ ar->what = (ar->linedefined == 0) ? "main" : "Lua";
+ }
+ luaO_chunkid(ar->short_src, ar->source, LUA_IDSIZE);
+}
+
+
+static void info_tailcall (lua_Debug *ar) {
+ ar->name = ar->namewhat = "";
+ ar->what = "tail";
+ ar->lastlinedefined = ar->linedefined = ar->currentline = -1;
+ ar->source = "=(tail call)";
+ luaO_chunkid(ar->short_src, ar->source, LUA_IDSIZE);
+ ar->nups = 0;
+}
+
+
+static void collectvalidlines (lua_State *L, Closure *f) {
+ if (f == NULL || f->c.isC) {
+ setnilvalue(L->top);
+ }
+ else {
+ Table *t = luaH_new(L, 0, 0);
+ int *lineinfo = f->l.p->lineinfo;
+ int i;
+ for (i=0; i<f->l.p->sizelineinfo; i++)
+ setbvalue(luaH_setnum(L, t, lineinfo[i]), 1);
+ sethvalue(L, L->top, t);
+ }
+ incr_top(L);
+}
+
+
+static int auxgetinfo (lua_State *L, const char *what, lua_Debug *ar,
+ Closure *f, CallInfo *ci) {
+ int status = 1;
+ if (f == NULL) {
+ info_tailcall(ar);
+ return status;
+ }
+ for (; *what; what++) {
+ switch (*what) {
+ case 'S': {
+ funcinfo(ar, f);
+ break;
+ }
+ case 'l': {
+ ar->currentline = (ci) ? currentline(L, ci) : -1;
+ break;
+ }
+ case 'u': {
+ ar->nups = f->c.nupvalues;
+ break;
+ }
+ case 'n': {
+ ar->namewhat = (ci) ? getfuncname(L, ci, &ar->name) : NULL;
+ if (ar->namewhat == NULL) {
+ ar->namewhat = ""; /* not found */
+ ar->name = NULL;
+ }
+ break;
+ }
+ case 'L':
+ case 'f': /* handled by lua_getinfo */
+ break;
+ default: status = 0; /* invalid option */
+ }
+ }
+ return status;
+}
+
+
+LUA_API int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar) {
+ int status;
+ Closure *f = NULL;
+ CallInfo *ci = NULL;
+ lua_lock(L);
+ if (*what == '>') {
+ StkId func = L->top - 1;
+ luai_apicheck(L, ttisfunction(func));
+ what++; /* skip the '>' */
+ f = clvalue(func);
+ L->top--; /* pop function */
+ }
+ else if (ar->i_ci != 0) { /* no tail call? */
+ ci = L->base_ci + ar->i_ci;
+ lua_assert(ttisfunction(ci->func));
+ f = clvalue(ci->func);
+ }
+ status = auxgetinfo(L, what, ar, f, ci);
+ if (strchr(what, 'f')) {
+ if (f == NULL) setnilvalue(L->top);
+ else setclvalue(L, L->top, f);
+ incr_top(L);
+ }
+ if (strchr(what, 'L'))
+ collectvalidlines(L, f);
+ lua_unlock(L);
+ return status;
+}
+
+
+/*
+** {======================================================
+** Symbolic Execution and code checker
+** =======================================================
+*/
+
+#define check(x) if (!(x)) return 0;
+
+#define checkjump(pt,pc) check(0 <= pc && pc < pt->sizecode)
+
+#define checkreg(pt,reg) check((reg) < (pt)->maxstacksize)
+
+
+
+static int precheck (const Proto *pt) {
+ check(pt->maxstacksize <= MAXSTACK);
+ check(pt->numparams+(pt->is_vararg & VARARG_HASARG) <= pt->maxstacksize);
+ check(!(pt->is_vararg & VARARG_NEEDSARG) ||
+ (pt->is_vararg & VARARG_HASARG));
+ check(pt->sizeupvalues <= pt->nups);
+ check(pt->sizelineinfo == pt->sizecode || pt->sizelineinfo == 0);
+ check(pt->sizecode > 0 && GET_OPCODE(pt->code[pt->sizecode-1]) == OP_RETURN);
+ return 1;
+}
+
+
+#define checkopenop(pt,pc) luaG_checkopenop((pt)->code[(pc)+1])
+
+int luaG_checkopenop (Instruction i) {
+ switch (GET_OPCODE(i)) {
+ case OP_CALL:
+ case OP_TAILCALL:
+ case OP_RETURN:
+ case OP_SETLIST: {
+ check(GETARG_B(i) == 0);
+ return 1;
+ }
+ default: return 0; /* invalid instruction after an open call */
+ }
+}
+
+
+static int checkArgMode (const Proto *pt, int r, enum OpArgMask mode) {
+ switch (mode) {
+ case OpArgN: check(r == 0); break;
+ case OpArgU: break;
+ case OpArgR: checkreg(pt, r); break;
+ case OpArgK:
+ check(ISK(r) ? INDEXK(r) < pt->sizek : r < pt->maxstacksize);
+ break;
+ }
+ return 1;
+}
+
+
+static Instruction symbexec (const Proto *pt, int lastpc, int reg) {
+ int pc;
+ int last; /* stores position of last instruction that changed `reg' */
+ last = pt->sizecode-1; /* points to final return (a `neutral' instruction) */
+ check(precheck(pt));
+ for (pc = 0; pc < lastpc; pc++) {
+ Instruction i = pt->code[pc];
+ OpCode op = GET_OPCODE(i);
+ int a = GETARG_A(i);
+ int b = 0;
+ int c = 0;
+ check(op < NUM_OPCODES);
+ checkreg(pt, a);
+ switch (getOpMode(op)) {
+ case iABC: {
+ b = GETARG_B(i);
+ c = GETARG_C(i);
+ check(checkArgMode(pt, b, getBMode(op)));
+ check(checkArgMode(pt, c, getCMode(op)));
+ break;
+ }
+ case iABx: {
+ b = GETARG_Bx(i);
+ if (getBMode(op) == OpArgK) check(b < pt->sizek);
+ break;
+ }
+ case iAsBx: {
+ b = GETARG_sBx(i);
+ if (getBMode(op) == OpArgR) {
+ int dest = pc+1+b;
+ check(0 <= dest && dest < pt->sizecode);
+ if (dest > 0) {
+ int j;
+ /* check that it does not jump to a setlist count; this
+ is tricky, because the count from a previous setlist may
+ have the same value of an invalid setlist; so, we must
+ go all the way back to the first of them (if any) */
+ for (j = 0; j < dest; j++) {
+ Instruction d = pt->code[dest-1-j];
+ if (!(GET_OPCODE(d) == OP_SETLIST && GETARG_C(d) == 0)) break;
+ }
+ /* if 'j' is even, previous value is not a setlist (even if
+ it looks like one) */
+ check((j&1) == 0);
+ }
+ }
+ break;
+ }
+ }
+ if (testAMode(op)) {
+ if (a == reg) last = pc; /* change register `a' */
+ }
+ if (testTMode(op)) {
+ check(pc+2 < pt->sizecode); /* check skip */
+ check(GET_OPCODE(pt->code[pc+1]) == OP_JMP);
+ }
+ switch (op) {
+ case OP_LOADBOOL: {
+ if (c == 1) { /* does it jump? */
+ check(pc+2 < pt->sizecode); /* check its jump */
+ check(GET_OPCODE(pt->code[pc+1]) != OP_SETLIST ||
+ GETARG_C(pt->code[pc+1]) != 0);
+ }
+ break;
+ }
+ case OP_LOADNIL: {
+ if (a <= reg && reg <= b)
+ last = pc; /* set registers from `a' to `b' */
+ break;
+ }
+ case OP_GETUPVAL:
+ case OP_SETUPVAL: {
+ check(b < pt->nups);
+ break;
+ }
+ case OP_GETGLOBAL:
+ case OP_SETGLOBAL: {
+ check(ttisstring(&pt->k[b]));
+ break;
+ }
+ case OP_SELF: {
+ checkreg(pt, a+1);
+ if (reg == a+1) last = pc;
+ break;
+ }
+ case OP_CONCAT: {
+ check(b < c); /* at least two operands */
+ break;
+ }
+ case OP_TFORLOOP: {
+ check(c >= 1); /* at least one result (control variable) */
+ checkreg(pt, a+2+c); /* space for results */
+ if (reg >= a+2) last = pc; /* affect all regs above its base */
+ break;
+ }
+ case OP_FORLOOP:
+ case OP_FORPREP:
+ checkreg(pt, a+3);
+ /* go through */
+ case OP_JMP: {
+ int dest = pc+1+b;
+ /* not full check and jump is forward and do not skip `lastpc'? */
+ if (reg != NO_REG && pc < dest && dest <= lastpc)
+ pc += b; /* do the jump */
+ break;
+ }
+ case OP_CALL:
+ case OP_TAILCALL: {
+ if (b != 0) {
+ checkreg(pt, a+b-1);
+ }
+ c--; /* c = num. returns */
+ if (c == LUA_MULTRET) {
+ check(checkopenop(pt, pc));
+ }
+ else if (c != 0)
+ checkreg(pt, a+c-1);
+ if (reg >= a) last = pc; /* affect all registers above base */
+ break;
+ }
+ case OP_RETURN: {
+ b--; /* b = num. returns */
+ if (b > 0) checkreg(pt, a+b-1);
+ break;
+ }
+ case OP_SETLIST: {
+ if (b > 0) checkreg(pt, a + b);
+ if (c == 0) {
+ pc++;
+ check(pc < pt->sizecode - 1);
+ }
+ break;
+ }
+ case OP_CLOSURE: {
+ int nup, j;
+ check(b < pt->sizep);
+ nup = pt->p[b]->nups;
+ check(pc + nup < pt->sizecode);
+ for (j = 1; j <= nup; j++) {
+ OpCode op1 = GET_OPCODE(pt->code[pc + j]);
+ check(op1 == OP_GETUPVAL || op1 == OP_MOVE);
+ }
+ if (reg != NO_REG) /* tracing? */
+ pc += nup; /* do not 'execute' these pseudo-instructions */
+ break;
+ }
+ case OP_VARARG: {
+ check((pt->is_vararg & VARARG_ISVARARG) &&
+ !(pt->is_vararg & VARARG_NEEDSARG));
+ b--;
+ if (b == LUA_MULTRET) check(checkopenop(pt, pc));
+ checkreg(pt, a+b-1);
+ break;
+ }
+ default: break;
+ }
+ }
+ return pt->code[last];
+}
+
+#undef check
+#undef checkjump
+#undef checkreg
+
+/* }====================================================== */
+
+
+int luaG_checkcode (const Proto *pt) {
+ return (symbexec(pt, pt->sizecode, NO_REG) != 0);
+}
+
+
+static const char *kname (Proto *p, int c) {
+ if (ISK(c) && ttisstring(&p->k[INDEXK(c)]))
+ return svalue(&p->k[INDEXK(c)]);
+ else
+ return "?";
+}
+
+
+static const char *getobjname (lua_State *L, CallInfo *ci, int stackpos,
+ const char **name) {
+ if (isLua(ci)) { /* a Lua function? */
+ Proto *p = ci_func(ci)->l.p;
+ int pc = currentpc(L, ci);
+ Instruction i;
+ *name = luaF_getlocalname(p, stackpos+1, pc);
+ if (*name) /* is a local? */
+ return "local";
+ i = symbexec(p, pc, stackpos); /* try symbolic execution */
+ lua_assert(pc != -1);
+ switch (GET_OPCODE(i)) {
+ case OP_GETGLOBAL: {
+ int g = GETARG_Bx(i); /* global index */
+ lua_assert(ttisstring(&p->k[g]));
+ *name = svalue(&p->k[g]);
+ return "global";
+ }
+ case OP_MOVE: {
+ int a = GETARG_A(i);
+ int b = GETARG_B(i); /* move from `b' to `a' */
+ if (b < a)
+ return getobjname(L, ci, b, name); /* get name for `b' */
+ break;
+ }
+ case OP_GETTABLE: {
+ int k = GETARG_C(i); /* key index */
+ *name = kname(p, k);
+ return "field";
+ }
+ case OP_GETUPVAL: {
+ int u = GETARG_B(i); /* upvalue index */
+ *name = p->upvalues ? getstr(p->upvalues[u]) : "?";
+ return "upvalue";
+ }
+ case OP_SELF: {
+ int k = GETARG_C(i); /* key index */
+ *name = kname(p, k);
+ return "method";
+ }
+ default: break;
+ }
+ }
+ return NULL; /* no useful name found */
+}
+
+
+static const char *getfuncname (lua_State *L, CallInfo *ci, const char **name) {
+ Instruction i;
+ if ((isLua(ci) && ci->tailcalls > 0) || !isLua(ci - 1))
+ return NULL; /* calling function is not Lua (or is unknown) */
+ ci--; /* calling function */
+ i = ci_func(ci)->l.p->code[currentpc(L, ci)];
+ if (GET_OPCODE(i) == OP_CALL || GET_OPCODE(i) == OP_TAILCALL ||
+ GET_OPCODE(i) == OP_TFORLOOP)
+ return getobjname(L, ci, GETARG_A(i), name);
+ else
+ return NULL; /* no useful name can be found */
+}
+
+
+/* only ANSI way to check whether a pointer points to an array */
+static int isinstack (CallInfo *ci, const TValue *o) {
+ StkId p;
+ for (p = ci->base; p < ci->top; p++)
+ if (o == p) return 1;
+ return 0;
+}
+
+
+void luaG_typeerror (lua_State *L, const TValue *o, const char *op) {
+ const char *name = NULL;
+ const char *t = luaT_typenames[ttype(o)];
+ const char *kind = (isinstack(L->ci, o)) ?
+ getobjname(L, L->ci, cast_int(o - L->base), &name) :
+ NULL;
+ if (kind)
+ luaG_runerror(L, "attempt to %s %s " LUA_QS " (a %s value)",
+ op, kind, name, t);
+ else
+ luaG_runerror(L, "attempt to %s a %s value", op, t);
+}
+
+
+void luaG_concaterror (lua_State *L, StkId p1, StkId p2) {
+ if (ttisstring(p1) || ttisnumber(p1)) p1 = p2;
+ lua_assert(!ttisstring(p1) && !ttisnumber(p1));
+ luaG_typeerror(L, p1, "concatenate");
+}
+
+
+void luaG_aritherror (lua_State *L, const TValue *p1, const TValue *p2) {
+ TValue temp;
+ if (luaV_tonumber(p1, &temp) == NULL)
+ p2 = p1; /* first operand is wrong */
+ luaG_typeerror(L, p2, "perform arithmetic on");
+}
+
+
+int luaG_ordererror (lua_State *L, const TValue *p1, const TValue *p2) {
+ const char *t1 = luaT_typenames[ttype(p1)];
+ const char *t2 = luaT_typenames[ttype(p2)];
+ if (t1[2] == t2[2])
+ luaG_runerror(L, "attempt to compare two %s values", t1);
+ else
+ luaG_runerror(L, "attempt to compare %s with %s", t1, t2);
+ return 0;
+}
+
+
+static void addinfo (lua_State *L, const char *msg) {
+ CallInfo *ci = L->ci;
+ if (isLua(ci)) { /* is Lua code? */
+ char buff[LUA_IDSIZE]; /* add file:line information */
+ int line = currentline(L, ci);
+ luaO_chunkid(buff, getstr(getluaproto(ci)->source), LUA_IDSIZE);
+ luaO_pushfstring(L, "%s:%d: %s", buff, line, msg);
+ }
+}
+
+
+void luaG_errormsg (lua_State *L) {
+ if (L->errfunc != 0) { /* is there an error handling function? */
+ StkId errfunc = restorestack(L, L->errfunc);
+ if (!ttisfunction(errfunc)) luaD_throw(L, LUA_ERRERR);
+ setobjs2s(L, L->top, L->top - 1); /* move argument */
+ setobjs2s(L, L->top - 1, errfunc); /* push function */
+ incr_top(L);
+ luaD_call(L, L->top - 2, 1); /* call it */
+ }
+ luaD_throw(L, LUA_ERRRUN);
+}
+
+
+void luaG_runerror (lua_State *L, const char *fmt, ...) {
+ va_list argp;
+ va_start(argp, fmt);
+ addinfo(L, luaO_pushvfstring(L, fmt, argp));
+ va_end(argp);
+ luaG_errormsg(L);
+}
+
diff --git a/lib/lua/src/ldebug.h b/lib/lua/src/ldebug.h
new file mode 100644
index 000000000..ba28a9724
--- /dev/null
+++ b/lib/lua/src/ldebug.h
@@ -0,0 +1,33 @@
+/*
+** $Id: ldebug.h,v 2.3.1.1 2007/12/27 13:02:25 roberto Exp $
+** Auxiliary functions from Debug Interface module
+** See Copyright Notice in lua.h
+*/
+
+#ifndef ldebug_h
+#define ldebug_h
+
+
+#include "lstate.h"
+
+
+#define pcRel(pc, p) (cast(int, (pc) - (p)->code) - 1)
+
+#define getline(f,pc) (((f)->lineinfo) ? (f)->lineinfo[pc] : 0)
+
+#define resethookcount(L) (L->hookcount = L->basehookcount)
+
+
+LUAI_FUNC void luaG_typeerror (lua_State *L, const TValue *o,
+ const char *opname);
+LUAI_FUNC void luaG_concaterror (lua_State *L, StkId p1, StkId p2);
+LUAI_FUNC void luaG_aritherror (lua_State *L, const TValue *p1,
+ const TValue *p2);
+LUAI_FUNC int luaG_ordererror (lua_State *L, const TValue *p1,
+ const TValue *p2);
+LUAI_FUNC void luaG_runerror (lua_State *L, const char *fmt, ...);
+LUAI_FUNC void luaG_errormsg (lua_State *L);
+LUAI_FUNC int luaG_checkcode (const Proto *pt);
+LUAI_FUNC int luaG_checkopenop (Instruction i);
+
+#endif
diff --git a/lib/lua/src/ldo.c b/lib/lua/src/ldo.c
new file mode 100644
index 000000000..8de05f728
--- /dev/null
+++ b/lib/lua/src/ldo.c
@@ -0,0 +1,518 @@
+/*
+** $Id: ldo.c,v 2.38.1.3 2008/01/18 22:31:22 roberto Exp $
+** Stack and Call structure of Lua
+** See Copyright Notice in lua.h
+*/
+
+
+#include <setjmp.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define ldo_c
+#define LUA_CORE
+
+#include "lua.h"
+
+#include "ldebug.h"
+#include "ldo.h"
+#include "lfunc.h"
+#include "lgc.h"
+#include "lmem.h"
+#include "lobject.h"
+#include "lopcodes.h"
+#include "lparser.h"
+#include "lstate.h"
+#include "lstring.h"
+#include "ltable.h"
+#include "ltm.h"
+#include "lundump.h"
+#include "lvm.h"
+#include "lzio.h"
+
+
+
+
+/*
+** {======================================================
+** Error-recovery functions
+** =======================================================
+*/
+
+
+/* chain list of long jump buffers */
+struct lua_longjmp {
+ struct lua_longjmp *previous;
+ luai_jmpbuf b;
+ volatile int status; /* error code */
+};
+
+
+void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) {
+ switch (errcode) {
+ case LUA_ERRMEM: {
+ setsvalue2s(L, oldtop, luaS_newliteral(L, MEMERRMSG));
+ break;
+ }
+ case LUA_ERRERR: {
+ setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling"));
+ break;
+ }
+ case LUA_ERRSYNTAX:
+ case LUA_ERRRUN: {
+ setobjs2s(L, oldtop, L->top - 1); /* error message on current top */
+ break;
+ }
+ }
+ L->top = oldtop + 1;
+}
+
+
+static void restore_stack_limit (lua_State *L) {
+ lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1);
+ if (L->size_ci > LUAI_MAXCALLS) { /* there was an overflow? */
+ int inuse = cast_int(L->ci - L->base_ci);
+ if (inuse + 1 < LUAI_MAXCALLS) /* can `undo' overflow? */
+ luaD_reallocCI(L, LUAI_MAXCALLS);
+ }
+}
+
+
+static void resetstack (lua_State *L, int status) {
+ L->ci = L->base_ci;
+ L->base = L->ci->base;
+ luaF_close(L, L->base); /* close eventual pending closures */
+ luaD_seterrorobj(L, status, L->base);
+ L->nCcalls = L->baseCcalls;
+ L->allowhook = 1;
+ restore_stack_limit(L);
+ L->errfunc = 0;
+ L->errorJmp = NULL;
+}
+
+
+void luaD_throw (lua_State *L, int errcode) {
+ if (L->errorJmp) {
+ L->errorJmp->status = errcode;
+ LUAI_THROW(L, L->errorJmp);
+ }
+ else {
+ L->status = cast_byte(errcode);
+ if (G(L)->panic) {
+ resetstack(L, errcode);
+ lua_unlock(L);
+ G(L)->panic(L);
+ }
+ exit(EXIT_FAILURE);
+ }
+}
+
+
+int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
+ struct lua_longjmp lj;
+ lj.status = 0;
+ lj.previous = L->errorJmp; /* chain new error handler */
+ L->errorJmp = &lj;
+ LUAI_TRY(L, &lj,
+ (*f)(L, ud);
+ );
+ L->errorJmp = lj.previous; /* restore old error handler */
+ return lj.status;
+}
+
+/* }====================================================== */
+
+
+static void correctstack (lua_State *L, TValue *oldstack) {
+ CallInfo *ci;
+ GCObject *up;
+ L->top = (L->top - oldstack) + L->stack;
+ for (up = L->openupval; up != NULL; up = up->gch.next)
+ gco2uv(up)->v = (gco2uv(up)->v - oldstack) + L->stack;
+ for (ci = L->base_ci; ci <= L->ci; ci++) {
+ ci->top = (ci->top - oldstack) + L->stack;
+ ci->base = (ci->base - oldstack) + L->stack;
+ ci->func = (ci->func - oldstack) + L->stack;
+ }
+ L->base = (L->base - oldstack) + L->stack;
+}
+
+
+void luaD_reallocstack (lua_State *L, int newsize) {
+ TValue *oldstack = L->stack;
+ int realsize = newsize + 1 + EXTRA_STACK;
+ lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1);
+ luaM_reallocvector(L, L->stack, L->stacksize, realsize, TValue);
+ L->stacksize = realsize;
+ L->stack_last = L->stack+newsize;
+ correctstack(L, oldstack);
+}
+
+
+void luaD_reallocCI (lua_State *L, int newsize) {
+ CallInfo *oldci = L->base_ci;
+ luaM_reallocvector(L, L->base_ci, L->size_ci, newsize, CallInfo);
+ L->size_ci = newsize;
+ L->ci = (L->ci - oldci) + L->base_ci;
+ L->end_ci = L->base_ci + L->size_ci - 1;
+}
+
+
+void luaD_growstack (lua_State *L, int n) {
+ if (n <= L->stacksize) /* double size is enough? */
+ luaD_reallocstack(L, 2*L->stacksize);
+ else
+ luaD_reallocstack(L, L->stacksize + n);
+}
+
+
+static CallInfo *growCI (lua_State *L) {
+ if (L->size_ci > LUAI_MAXCALLS) /* overflow while handling overflow? */
+ luaD_throw(L, LUA_ERRERR);
+ else {
+ luaD_reallocCI(L, 2*L->size_ci);
+ if (L->size_ci > LUAI_MAXCALLS)
+ luaG_runerror(L, "stack overflow");
+ }
+ return ++L->ci;
+}
+
+
+void luaD_callhook (lua_State *L, int event, int line) {
+ lua_Hook hook = L->hook;
+ if (hook && L->allowhook) {
+ ptrdiff_t top = savestack(L, L->top);
+ ptrdiff_t ci_top = savestack(L, L->ci->top);
+ lua_Debug ar;
+ ar.event = event;
+ ar.currentline = line;
+ if (event == LUA_HOOKTAILRET)
+ ar.i_ci = 0; /* tail call; no debug information about it */
+ else
+ ar.i_ci = cast_int(L->ci - L->base_ci);
+ luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */
+ L->ci->top = L->top + LUA_MINSTACK;
+ lua_assert(L->ci->top <= L->stack_last);
+ L->allowhook = 0; /* cannot call hooks inside a hook */
+ lua_unlock(L);
+ (*hook)(L, &ar);
+ lua_lock(L);
+ lua_assert(!L->allowhook);
+ L->allowhook = 1;
+ L->ci->top = restorestack(L, ci_top);
+ L->top = restorestack(L, top);
+ }
+}
+
+
+static StkId adjust_varargs (lua_State *L, Proto *p, int actual) {
+ int i;
+ int nfixargs = p->numparams;
+ Table *htab = NULL;
+ StkId base, fixed;
+ for (; actual < nfixargs; ++actual)
+ setnilvalue(L->top++);
+#if defined(LUA_COMPAT_VARARG)
+ if (p->is_vararg & VARARG_NEEDSARG) { /* compat. with old-style vararg? */
+ int nvar = actual - nfixargs; /* number of extra arguments */
+ lua_assert(p->is_vararg & VARARG_HASARG);
+ luaC_checkGC(L);
+ htab = luaH_new(L, nvar, 1); /* create `arg' table */
+ for (i=0; i<nvar; i++) /* put extra arguments into `arg' table */
+ setobj2n(L, luaH_setnum(L, htab, i+1), L->top - nvar + i);
+ /* store counter in field `n' */
+ setnvalue(luaH_setstr(L, htab, luaS_newliteral(L, "n")), cast_num(nvar));
+ }
+#endif
+ /* move fixed parameters to final position */
+ fixed = L->top - actual; /* first fixed argument */
+ base = L->top; /* final position of first argument */
+ for (i=0; i<nfixargs; i++) {
+ setobjs2s(L, L->top++, fixed+i);
+ setnilvalue(fixed+i);
+ }
+ /* add `arg' parameter */
+ if (htab) {
+ sethvalue(L, L->top++, htab);
+ lua_assert(iswhite(obj2gco(htab)));
+ }
+ return base;
+}
+
+
+static StkId tryfuncTM (lua_State *L, StkId func) {
+ const TValue *tm = luaT_gettmbyobj(L, func, TM_CALL);
+ StkId p;
+ ptrdiff_t funcr = savestack(L, func);
+ if (!ttisfunction(tm))
+ luaG_typeerror(L, func, "call");
+ /* Open a hole inside the stack at `func' */
+ for (p = L->top; p > func; p--) setobjs2s(L, p, p-1);
+ incr_top(L);
+ func = restorestack(L, funcr); /* previous call may change stack */
+ setobj2s(L, func, tm); /* tag method is the new function to be called */
+ return func;
+}
+
+
+
+#define inc_ci(L) \
+ ((L->ci == L->end_ci) ? growCI(L) : \
+ (condhardstacktests(luaD_reallocCI(L, L->size_ci)), ++L->ci))
+
+
+int luaD_precall (lua_State *L, StkId func, int nresults) {
+ LClosure *cl;
+ ptrdiff_t funcr;
+ if (!ttisfunction(func)) /* `func' is not a function? */
+ func = tryfuncTM(L, func); /* check the `function' tag method */
+ funcr = savestack(L, func);
+ cl = &clvalue(func)->l;
+ L->ci->savedpc = L->savedpc;
+ if (!cl->isC) { /* Lua function? prepare its call */
+ CallInfo *ci;
+ StkId st, base;
+ Proto *p = cl->p;
+ luaD_checkstack(L, p->maxstacksize);
+ func = restorestack(L, funcr);
+ if (!p->is_vararg) { /* no varargs? */
+ base = func + 1;
+ if (L->top > base + p->numparams)
+ L->top = base + p->numparams;
+ }
+ else { /* vararg function */
+ int nargs = cast_int(L->top - func) - 1;
+ base = adjust_varargs(L, p, nargs);
+ func = restorestack(L, funcr); /* previous call may change the stack */
+ }
+ ci = inc_ci(L); /* now `enter' new function */
+ ci->func = func;
+ L->base = ci->base = base;
+ ci->top = L->base + p->maxstacksize;
+ lua_assert(ci->top <= L->stack_last);
+ L->savedpc = p->code; /* starting point */
+ ci->tailcalls = 0;
+ ci->nresults = nresults;
+ for (st = L->top; st < ci->top; st++)
+ setnilvalue(st);
+ L->top = ci->top;
+ if (L->hookmask & LUA_MASKCALL) {
+ L->savedpc++; /* hooks assume 'pc' is already incremented */
+ luaD_callhook(L, LUA_HOOKCALL, -1);
+ L->savedpc--; /* correct 'pc' */
+ }
+ return PCRLUA;
+ }
+ else { /* if is a C function, call it */
+ CallInfo *ci;
+ int n;
+ luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */
+ ci = inc_ci(L); /* now `enter' new function */
+ ci->func = restorestack(L, funcr);
+ L->base = ci->base = ci->func + 1;
+ ci->top = L->top + LUA_MINSTACK;
+ lua_assert(ci->top <= L->stack_last);
+ ci->nresults = nresults;
+ if (L->hookmask & LUA_MASKCALL)
+ luaD_callhook(L, LUA_HOOKCALL, -1);
+ lua_unlock(L);
+ n = (*curr_func(L)->c.f)(L); /* do the actual call */
+ lua_lock(L);
+ if (n < 0) /* yielding? */
+ return PCRYIELD;
+ else {
+ luaD_poscall(L, L->top - n);
+ return PCRC;
+ }
+ }
+}
+
+
+static StkId callrethooks (lua_State *L, StkId firstResult) {
+ ptrdiff_t fr = savestack(L, firstResult); /* next call may change stack */
+ luaD_callhook(L, LUA_HOOKRET, -1);
+ if (f_isLua(L->ci)) { /* Lua function? */
+ while ((L->hookmask & LUA_MASKRET) && L->ci->tailcalls--) /* tail calls */
+ luaD_callhook(L, LUA_HOOKTAILRET, -1);
+ }
+ return restorestack(L, fr);
+}
+
+
+int luaD_poscall (lua_State *L, StkId firstResult) {
+ StkId res;
+ int wanted, i;
+ CallInfo *ci;
+ if (L->hookmask & LUA_MASKRET)
+ firstResult = callrethooks(L, firstResult);
+ ci = L->ci--;
+ res = ci->func; /* res == final position of 1st result */
+ wanted = ci->nresults;
+ L->base = (ci - 1)->base; /* restore base */
+ L->savedpc = (ci - 1)->savedpc; /* restore savedpc */
+ /* move results to correct place */
+ for (i = wanted; i != 0 && firstResult < L->top; i--)
+ setobjs2s(L, res++, firstResult++);
+ while (i-- > 0)
+ setnilvalue(res++);
+ L->top = res;
+ return (wanted - LUA_MULTRET); /* 0 iff wanted == LUA_MULTRET */
+}
+
+
+/*
+** Call a function (C or Lua). The function to be called is at *func.
+** The arguments are on the stack, right after the function.
+** When returns, all the results are on the stack, starting at the original
+** function position.
+*/
+void luaD_call (lua_State *L, StkId func, int nResults) {
+ if (++L->nCcalls >= LUAI_MAXCCALLS) {
+ if (L->nCcalls == LUAI_MAXCCALLS)
+ luaG_runerror(L, "C stack overflow");
+ else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3)))
+ luaD_throw(L, LUA_ERRERR); /* error while handing stack error */
+ }
+ if (luaD_precall(L, func, nResults) == PCRLUA) /* is a Lua function? */
+ luaV_execute(L, 1); /* call it */
+ L->nCcalls--;
+ luaC_checkGC(L);
+}
+
+
+static void resume (lua_State *L, void *ud) {
+ StkId firstArg = cast(StkId, ud);
+ CallInfo *ci = L->ci;
+ if (L->status == 0) { /* start coroutine? */
+ lua_assert(ci == L->base_ci && firstArg > L->base);
+ if (luaD_precall(L, firstArg - 1, LUA_MULTRET) != PCRLUA)
+ return;
+ }
+ else { /* resuming from previous yield */
+ lua_assert(L->status == LUA_YIELD);
+ L->status = 0;
+ if (!f_isLua(ci)) { /* `common' yield? */
+ /* finish interrupted execution of `OP_CALL' */
+ lua_assert(GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_CALL ||
+ GET_OPCODE(*((ci-1)->savedpc - 1)) == OP_TAILCALL);
+ if (luaD_poscall(L, firstArg)) /* complete it... */
+ L->top = L->ci->top; /* and correct top if not multiple results */
+ }
+ else /* yielded inside a hook: just continue its execution */
+ L->base = L->ci->base;
+ }
+ luaV_execute(L, cast_int(L->ci - L->base_ci));
+}
+
+
+static int resume_error (lua_State *L, const char *msg) {
+ L->top = L->ci->base;
+ setsvalue2s(L, L->top, luaS_new(L, msg));
+ incr_top(L);
+ lua_unlock(L);
+ return LUA_ERRRUN;
+}
+
+
+LUA_API int lua_resume (lua_State *L, int nargs) {
+ int status;
+ lua_lock(L);
+ if (L->status != LUA_YIELD && (L->status != 0 || L->ci != L->base_ci))
+ return resume_error(L, "cannot resume non-suspended coroutine");
+ if (L->nCcalls >= LUAI_MAXCCALLS)
+ return resume_error(L, "C stack overflow");
+ luai_userstateresume(L, nargs);
+ lua_assert(L->errfunc == 0);
+ L->baseCcalls = ++L->nCcalls;
+ status = luaD_rawrunprotected(L, resume, L->top - nargs);
+ if (status != 0) { /* error? */
+ L->status = cast_byte(status); /* mark thread as `dead' */
+ luaD_seterrorobj(L, status, L->top);
+ L->ci->top = L->top;
+ }
+ else {
+ lua_assert(L->nCcalls == L->baseCcalls);
+ status = L->status;
+ }
+ --L->nCcalls;
+ lua_unlock(L);
+ return status;
+}
+
+
+LUA_API int lua_yield (lua_State *L, int nresults) {
+ luai_userstateyield(L, nresults);
+ lua_lock(L);
+ if (L->nCcalls > L->baseCcalls)
+ luaG_runerror(L, "attempt to yield across metamethod/C-call boundary");
+ L->base = L->top - nresults; /* protect stack slots below */
+ L->status = LUA_YIELD;
+ lua_unlock(L);
+ return -1;
+}
+
+
+int luaD_pcall (lua_State *L, Pfunc func, void *u,
+ ptrdiff_t old_top, ptrdiff_t ef) {
+ int status;
+ unsigned short oldnCcalls = L->nCcalls;
+ ptrdiff_t old_ci = saveci(L, L->ci);
+ lu_byte old_allowhooks = L->allowhook;
+ ptrdiff_t old_errfunc = L->errfunc;
+ L->errfunc = ef;
+ status = luaD_rawrunprotected(L, func, u);
+ if (status != 0) { /* an error occurred? */
+ StkId oldtop = restorestack(L, old_top);
+ luaF_close(L, oldtop); /* close eventual pending closures */
+ luaD_seterrorobj(L, status, oldtop);
+ L->nCcalls = oldnCcalls;
+ L->ci = restoreci(L, old_ci);
+ L->base = L->ci->base;
+ L->savedpc = L->ci->savedpc;
+ L->allowhook = old_allowhooks;
+ restore_stack_limit(L);
+ }
+ L->errfunc = old_errfunc;
+ return status;
+}
+
+
+
+/*
+** Execute a protected parser.
+*/
+struct SParser { /* data to `f_parser' */
+ ZIO *z;
+ Mbuffer buff; /* buffer to be used by the scanner */
+ const char *name;
+};
+
+static void f_parser (lua_State *L, void *ud) {
+ int i;
+ Proto *tf;
+ Closure *cl;
+ struct SParser *p = cast(struct SParser *, ud);
+ int c = luaZ_lookahead(p->z);
+ luaC_checkGC(L);
+ tf = ((c == LUA_SIGNATURE[0]) ? luaU_undump : luaY_parser)(L, p->z,
+ &p->buff, p->name);
+ cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L)));
+ cl->l.p = tf;
+ for (i = 0; i < tf->nups; i++) /* initialize eventual upvalues */
+ cl->l.upvals[i] = luaF_newupval(L);
+ setclvalue(L, L->top, cl);
+ incr_top(L);
+}
+
+
+int luaD_protectedparser (lua_State *L, ZIO *z, const char *name) {
+ struct SParser p;
+ int status;
+ p.z = z; p.name = name;
+ luaZ_initbuffer(L, &p.buff);
+ status = luaD_pcall(L, f_parser, &p, savestack(L, L->top), L->errfunc);
+ luaZ_freebuffer(L, &p.buff);
+ return status;
+}
+
+
diff --git a/lib/lua/src/ldo.h b/lib/lua/src/ldo.h
new file mode 100644
index 000000000..98fddac59
--- /dev/null
+++ b/lib/lua/src/ldo.h
@@ -0,0 +1,57 @@
+/*
+** $Id: ldo.h,v 2.7.1.1 2007/12/27 13:02:25 roberto Exp $
+** Stack and Call structure of Lua
+** See Copyright Notice in lua.h
+*/
+
+#ifndef ldo_h
+#define ldo_h
+
+
+#include "lobject.h"
+#include "lstate.h"
+#include "lzio.h"
+
+
+#define luaD_checkstack(L,n) \
+ if ((char *)L->stack_last - (char *)L->top <= (n)*(int)sizeof(TValue)) \
+ luaD_growstack(L, n); \
+ else condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1));
+
+
+#define incr_top(L) {luaD_checkstack(L,1); L->top++;}
+
+#define savestack(L,p) ((char *)(p) - (char *)L->stack)
+#define restorestack(L,n) ((TValue *)((char *)L->stack + (n)))
+
+#define saveci(L,p) ((char *)(p) - (char *)L->base_ci)
+#define restoreci(L,n) ((CallInfo *)((char *)L->base_ci + (n)))
+
+
+/* results from luaD_precall */
+#define PCRLUA 0 /* initiated a call to a Lua function */
+#define PCRC 1 /* did a call to a C function */
+#define PCRYIELD 2 /* C funtion yielded */
+
+
+/* type of protected functions, to be ran by `runprotected' */
+typedef void (*Pfunc) (lua_State *L, void *ud);
+
+LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name);
+LUAI_FUNC void luaD_callhook (lua_State *L, int event, int line);
+LUAI_FUNC int luaD_precall (lua_State *L, StkId func, int nresults);
+LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults);
+LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u,
+ ptrdiff_t oldtop, ptrdiff_t ef);
+LUAI_FUNC int luaD_poscall (lua_State *L, StkId firstResult);
+LUAI_FUNC void luaD_reallocCI (lua_State *L, int newsize);
+LUAI_FUNC void luaD_reallocstack (lua_State *L, int newsize);
+LUAI_FUNC void luaD_growstack (lua_State *L, int n);
+
+LUAI_FUNC void luaD_throw (lua_State *L, int errcode);
+LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud);
+
+LUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop);
+
+#endif
+
diff --git a/lib/lua/src/ldump.c b/lib/lua/src/ldump.c
new file mode 100644
index 000000000..c9d3d4870
--- /dev/null
+++ b/lib/lua/src/ldump.c
@@ -0,0 +1,164 @@
+/*
+** $Id: ldump.c,v 2.8.1.1 2007/12/27 13:02:25 roberto Exp $
+** save precompiled Lua chunks
+** See Copyright Notice in lua.h
+*/
+
+#include <stddef.h>
+
+#define ldump_c
+#define LUA_CORE
+
+#include "lua.h"
+
+#include "lobject.h"
+#include "lstate.h"
+#include "lundump.h"
+
+typedef struct {
+ lua_State* L;
+ lua_Writer writer;
+ void* data;
+ int strip;
+ int status;
+} DumpState;
+
+#define DumpMem(b,n,size,D) DumpBlock(b,(n)*(size),D)
+#define DumpVar(x,D) DumpMem(&x,1,sizeof(x),D)
+
+static void DumpBlock(const void* b, size_t size, DumpState* D)
+{
+ if (D->status==0)
+ {
+ lua_unlock(D->L);
+ D->status=(*D->writer)(D->L,b,size,D->data);
+ lua_lock(D->L);
+ }
+}
+
+static void DumpChar(int y, DumpState* D)
+{
+ char x=(char)y;
+ DumpVar(x,D);
+}
+
+static void DumpInt(int x, DumpState* D)
+{
+ DumpVar(x,D);
+}
+
+static void DumpNumber(lua_Number x, DumpState* D)
+{
+ DumpVar(x,D);
+}
+
+static void DumpVector(const void* b, int n, size_t size, DumpState* D)
+{
+ DumpInt(n,D);
+ DumpMem(b,n,size,D);
+}
+
+static void DumpString(const TString* s, DumpState* D)
+{
+ if (s==NULL || getstr(s)==NULL)
+ {
+ size_t size=0;
+ DumpVar(size,D);
+ }
+ else
+ {
+ size_t size=s->tsv.len+1; /* include trailing '\0' */
+ DumpVar(size,D);
+ DumpBlock(getstr(s),size,D);
+ }
+}
+
+#define DumpCode(f,D) DumpVector(f->code,f->sizecode,sizeof(Instruction),D)
+
+static void DumpFunction(const Proto* f, const TString* p, DumpState* D);
+
+static void DumpConstants(const Proto* f, DumpState* D)
+{
+ int i,n=f->sizek;
+ DumpInt(n,D);
+ for (i=0; i<n; i++)
+ {
+ const TValue* o=&f->k[i];
+ DumpChar(ttype(o),D);
+ switch (ttype(o))
+ {
+ case LUA_TNIL:
+ break;
+ case LUA_TBOOLEAN:
+ DumpChar(bvalue(o),D);
+ break;
+ case LUA_TNUMBER:
+ DumpNumber(nvalue(o),D);
+ break;
+ case LUA_TSTRING:
+ DumpString(rawtsvalue(o),D);
+ break;
+ default:
+ lua_assert(0); /* cannot happen */
+ break;
+ }
+ }
+ n=f->sizep;
+ DumpInt(n,D);
+ for (i=0; i<n; i++) DumpFunction(f->p[i],f->source,D);
+}
+
+static void DumpDebug(const Proto* f, DumpState* D)
+{
+ int i,n;
+ n= (D->strip) ? 0 : f->sizelineinfo;
+ DumpVector(f->lineinfo,n,sizeof(int),D);
+ n= (D->strip) ? 0 : f->sizelocvars;
+ DumpInt(n,D);
+ for (i=0; i<n; i++)
+ {
+ DumpString(f->locvars[i].varname,D);
+ DumpInt(f->locvars[i].startpc,D);
+ DumpInt(f->locvars[i].endpc,D);
+ }
+ n= (D->strip) ? 0 : f->sizeupvalues;
+ DumpInt(n,D);
+ for (i=0; i<n; i++) DumpString(f->upvalues[i],D);
+}
+
+static void DumpFunction(const Proto* f, const TString* p, DumpState* D)
+{
+ DumpString((f->source==p || D->strip) ? NULL : f->source,D);
+ DumpInt(f->linedefined,D);
+ DumpInt(f->lastlinedefined,D);
+ DumpChar(f->nups,D);
+ DumpChar(f->numparams,D);
+ DumpChar(f->is_vararg,D);
+ DumpChar(f->maxstacksize,D);
+ DumpCode(f,D);
+ DumpConstants(f,D);
+ DumpDebug(f,D);
+}
+
+static void DumpHeader(DumpState* D)
+{
+ char h[LUAC_HEADERSIZE];
+ luaU_header(h);
+ DumpBlock(h,LUAC_HEADERSIZE,D);
+}
+
+/*
+** dump Lua function as precompiled chunk
+*/
+int luaU_dump (lua_State* L, const Proto* f, lua_Writer w, void* data, int strip)
+{
+ DumpState D;
+ D.L=L;
+ D.writer=w;
+ D.data=data;
+ D.strip=strip;
+ D.status=0;
+ DumpHeader(&D);
+ DumpFunction(f,NULL,&D);
+ return D.status;
+}
diff --git a/lib/lua/src/lfunc.c b/lib/lua/src/lfunc.c
new file mode 100644
index 000000000..813e88f58
--- /dev/null
+++ b/lib/lua/src/lfunc.c
@@ -0,0 +1,174 @@
+/*
+** $Id: lfunc.c,v 2.12.1.2 2007/12/28 14:58:43 roberto Exp $
+** Auxiliary functions to manipulate prototypes and closures
+** See Copyright Notice in lua.h
+*/
+
+
+#include <stddef.h>
+
+#define lfunc_c
+#define LUA_CORE
+
+#include "lua.h"
+
+#include "lfunc.h"
+#include "lgc.h"
+#include "lmem.h"
+#include "lobject.h"
+#include "lstate.h"
+
+
+
+Closure *luaF_newCclosure (lua_State *L, int nelems, Table *e) {
+ Closure *c = cast(Closure *, luaM_malloc(L, sizeCclosure(nelems)));
+ luaC_link(L, obj2gco(c), LUA_TFUNCTION);
+ c->c.isC = 1;
+ c->c.env = e;
+ c->c.nupvalues = cast_byte(nelems);
+ return c;
+}
+
+
+Closure *luaF_newLclosure (lua_State *L, int nelems, Table *e) {
+ Closure *c = cast(Closure *, luaM_malloc(L, sizeLclosure(nelems)));
+ luaC_link(L, obj2gco(c), LUA_TFUNCTION);
+ c->l.isC = 0;
+ c->l.env = e;
+ c->l.nupvalues = cast_byte(nelems);
+ while (nelems--) c->l.upvals[nelems] = NULL;
+ return c;
+}
+
+
+UpVal *luaF_newupval (lua_State *L) {
+ UpVal *uv = luaM_new(L, UpVal);
+ luaC_link(L, obj2gco(uv), LUA_TUPVAL);
+ uv->v = &uv->u.value;
+ setnilvalue(uv->v);
+ return uv;
+}
+
+
+UpVal *luaF_findupval (lua_State *L, StkId level) {
+ global_State *g = G(L);
+ GCObject **pp = &L->openupval;
+ UpVal *p;
+ UpVal *uv;
+ while (*pp != NULL && (p = ngcotouv(*pp))->v >= level) {
+ lua_assert(p->v != &p->u.value);
+ if (p->v == level) { /* found a corresponding upvalue? */
+ if (isdead(g, obj2gco(p))) /* is it dead? */
+ changewhite(obj2gco(p)); /* ressurect it */
+ return p;
+ }
+ pp = &p->next;
+ }
+ uv = luaM_new(L, UpVal); /* not found: create a new one */
+ uv->tt = LUA_TUPVAL;
+ uv->marked = luaC_white(g);
+ uv->v = level; /* current value lives in the stack */
+ uv->next = *pp; /* chain it in the proper position */
+ *pp = obj2gco(uv);
+ uv->u.l.prev = &g->uvhead; /* double link it in `uvhead' list */
+ uv->u.l.next = g->uvhead.u.l.next;
+ uv->u.l.next->u.l.prev = uv;
+ g->uvhead.u.l.next = uv;
+ lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
+ return uv;
+}
+
+
+static void unlinkupval (UpVal *uv) {
+ lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
+ uv->u.l.next->u.l.prev = uv->u.l.prev; /* remove from `uvhead' list */
+ uv->u.l.prev->u.l.next = uv->u.l.next;
+}
+
+
+void luaF_freeupval (lua_State *L, UpVal *uv) {
+ if (uv->v != &uv->u.value) /* is it open? */
+ unlinkupval(uv); /* remove from open list */
+ luaM_free(L, uv); /* free upvalue */
+}
+
+
+void luaF_close (lua_State *L, StkId level) {
+ UpVal *uv;
+ global_State *g = G(L);
+ while (L->openupval != NULL && (uv = ngcotouv(L->openupval))->v >= level) {
+ GCObject *o = obj2gco(uv);
+ lua_assert(!isblack(o) && uv->v != &uv->u.value);
+ L->openupval = uv->next; /* remove from `open' list */
+ if (isdead(g, o))
+ luaF_freeupval(L, uv); /* free upvalue */
+ else {
+ unlinkupval(uv);
+ setobj(L, &uv->u.value, uv->v);
+ uv->v = &uv->u.value; /* now current value lives here */
+ luaC_linkupval(L, uv); /* link upvalue into `gcroot' list */
+ }
+ }
+}
+
+
+Proto *luaF_newproto (lua_State *L) {
+ Proto *f = luaM_new(L, Proto);
+ luaC_link(L, obj2gco(f), LUA_TPROTO);
+ f->k = NULL;
+ f->sizek = 0;
+ f->p = NULL;
+ f->sizep = 0;
+ f->code = NULL;
+ f->sizecode = 0;
+ f->sizelineinfo = 0;
+ f->sizeupvalues = 0;
+ f->nups = 0;
+ f->upvalues = NULL;
+ f->numparams = 0;
+ f->is_vararg = 0;
+ f->maxstacksize = 0;
+ f->lineinfo = NULL;
+ f->sizelocvars = 0;
+ f->locvars = NULL;
+ f->linedefined = 0;
+ f->lastlinedefined = 0;
+ f->source = NULL;
+ return f;
+}
+
+
+void luaF_freeproto (lua_State *L, Proto *f) {
+ luaM_freearray(L, f->code, f->sizecode, Instruction);
+ luaM_freearray(L, f->p, f->sizep, Proto *);
+ luaM_freearray(L, f->k, f->sizek, TValue);
+ luaM_freearray(L, f->lineinfo, f->sizelineinfo, int);
+ luaM_freearray(L, f->locvars, f->sizelocvars, struct LocVar);
+ luaM_freearray(L, f->upvalues, f->sizeupvalues, TString *);
+ luaM_free(L, f);
+}
+
+
+void luaF_freeclosure (lua_State *L, Closure *c) {
+ int size = (c->c.isC) ? sizeCclosure(c->c.nupvalues) :
+ sizeLclosure(c->l.nupvalues);
+ luaM_freemem(L, c, size);
+}
+
+
+/*
+** Look for n-th local variable at line `line' in function `func'.
+** Returns NULL if not found.
+*/
+const char *luaF_getlocalname (const Proto *f, int local_number, int pc) {
+ int i;
+ for (i = 0; i<f->sizelocvars && f->locvars[i].startpc <= pc; i++) {
+ if (pc < f->locvars[i].endpc) { /* is variable active? */
+ local_number--;
+ if (local_number == 0)
+ return getstr(f->locvars[i].varname);
+ }
+ }
+ return NULL; /* not found */
+}
+
diff --git a/lib/lua/src/lfunc.h b/lib/lua/src/lfunc.h
new file mode 100644
index 000000000..a68cf5151
--- /dev/null
+++ b/lib/lua/src/lfunc.h
@@ -0,0 +1,34 @@
+/*
+** $Id: lfunc.h,v 2.4.1.1 2007/12/27 13:02:25 roberto Exp $
+** Auxiliary functions to manipulate prototypes and closures
+** See Copyright Notice in lua.h
+*/
+
+#ifndef lfunc_h
+#define lfunc_h
+
+
+#include "lobject.h"
+
+
+#define sizeCclosure(n) (cast(int, sizeof(CClosure)) + \
+ cast(int, sizeof(TValue)*((n)-1)))
+
+#define sizeLclosure(n) (cast(int, sizeof(LClosure)) + \
+ cast(int, sizeof(TValue *)*((n)-1)))
+
+
+LUAI_FUNC Proto *luaF_newproto (lua_State *L);
+LUAI_FUNC Closure *luaF_newCclosure (lua_State *L, int nelems, Table *e);
+LUAI_FUNC Closure *luaF_newLclosure (lua_State *L, int nelems, Table *e);
+LUAI_FUNC UpVal *luaF_newupval (lua_State *L);
+LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level);
+LUAI_FUNC void luaF_close (lua_State *L, StkId level);
+LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f);
+LUAI_FUNC void luaF_freeclosure (lua_State *L, Closure *c);
+LUAI_FUNC void luaF_freeupval (lua_State *L, UpVal *uv);
+LUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number,
+ int pc);
+
+
+#endif
diff --git a/lib/lua/src/lgc.c b/lib/lua/src/lgc.c
new file mode 100644
index 000000000..d9e0b7829
--- /dev/null
+++ b/lib/lua/src/lgc.c
@@ -0,0 +1,711 @@
+/*
+** $Id: lgc.c,v 2.38.1.1 2007/12/27 13:02:25 roberto Exp $
+** Garbage Collector
+** See Copyright Notice in lua.h
+*/
+
+#include <string.h>
+
+#define lgc_c
+#define LUA_CORE
+
+#include "lua.h"
+
+#include "ldebug.h"
+#include "ldo.h"
+#include "lfunc.h"
+#include "lgc.h"
+#include "lmem.h"
+#include "lobject.h"
+#include "lstate.h"
+#include "lstring.h"
+#include "ltable.h"
+#include "ltm.h"
+
+
+#define GCSTEPSIZE 1024u
+#define GCSWEEPMAX 40
+#define GCSWEEPCOST 10
+#define GCFINALIZECOST 100
+
+
+#define maskmarks cast_byte(~(bitmask(BLACKBIT)|WHITEBITS))
+
+#define makewhite(g,x) \
+ ((x)->gch.marked = cast_byte(((x)->gch.marked & maskmarks) | luaC_white(g)))
+
+#define white2gray(x) reset2bits((x)->gch.marked, WHITE0BIT, WHITE1BIT)
+#define black2gray(x) resetbit((x)->gch.marked, BLACKBIT)
+
+#define stringmark(s) reset2bits((s)->tsv.marked, WHITE0BIT, WHITE1BIT)
+
+
+#define isfinalized(u) testbit((u)->marked, FINALIZEDBIT)
+#define markfinalized(u) l_setbit((u)->marked, FINALIZEDBIT)
+
+
+#define KEYWEAK bitmask(KEYWEAKBIT)
+#define VALUEWEAK bitmask(VALUEWEAKBIT)
+
+
+
+#define markvalue(g,o) { checkconsistency(o); \
+ if (iscollectable(o) && iswhite(gcvalue(o))) reallymarkobject(g,gcvalue(o)); }
+
+#define markobject(g,t) { if (iswhite(obj2gco(t))) \
+ reallymarkobject(g, obj2gco(t)); }
+
+
+#define setthreshold(g) (g->GCthreshold = (g->estimate/100) * g->gcpause)
+
+
+static void removeentry (Node *n) {
+ lua_assert(ttisnil(gval(n)));
+ if (iscollectable(gkey(n)))
+ setttype(gkey(n), LUA_TDEADKEY); /* dead key; remove it */
+}
+
+
+static void reallymarkobject (global_State *g, GCObject *o) {
+ lua_assert(iswhite(o) && !isdead(g, o));
+ white2gray(o);
+ switch (o->gch.tt) {
+ case LUA_TSTRING: {
+ return;
+ }
+ case LUA_TUSERDATA: {
+ Table *mt = gco2u(o)->metatable;
+ gray2black(o); /* udata are never gray */
+ if (mt) markobject(g, mt);
+ markobject(g, gco2u(o)->env);
+ return;
+ }
+ case LUA_TUPVAL: {
+ UpVal *uv = gco2uv(o);
+ markvalue(g, uv->v);
+ if (uv->v == &uv->u.value) /* closed? */
+ gray2black(o); /* open upvalues are never black */
+ return;
+ }
+ case LUA_TFUNCTION: {
+ gco2cl(o)->c.gclist = g->gray;
+ g->gray = o;
+ break;
+ }
+ case LUA_TTABLE: {
+ gco2h(o)->gclist = g->gray;
+ g->gray = o;
+ break;
+ }
+ case LUA_TTHREAD: {
+ gco2th(o)->gclist = g->gray;
+ g->gray = o;
+ break;
+ }
+ case LUA_TPROTO: {
+ gco2p(o)->gclist = g->gray;
+ g->gray = o;
+ break;
+ }
+ default: lua_assert(0);
+ }
+}
+
+
+static void marktmu (global_State *g) {
+ GCObject *u = g->tmudata;
+ if (u) {
+ do {
+ u = u->gch.next;
+ makewhite(g, u); /* may be marked, if left from previous GC */
+ reallymarkobject(g, u);
+ } while (u != g->tmudata);
+ }
+}
+
+
+/* move `dead' udata that need finalization to list `tmudata' */
+size_t luaC_separateudata (lua_State *L, int all) {
+ global_State *g = G(L);
+ size_t deadmem = 0;
+ GCObject **p = &g->mainthread->next;
+ GCObject *curr;
+ while ((curr = *p) != NULL) {
+ if (!(iswhite(curr) || all) || isfinalized(gco2u(curr)))
+ p = &curr->gch.next; /* don't bother with them */
+ else if (fasttm(L, gco2u(curr)->metatable, TM_GC) == NULL) {
+ markfinalized(gco2u(curr)); /* don't need finalization */
+ p = &curr->gch.next;
+ }
+ else { /* must call its gc method */
+ deadmem += sizeudata(gco2u(curr));
+ markfinalized(gco2u(curr));
+ *p = curr->gch.next;
+ /* link `curr' at the end of `tmudata' list */
+ if (g->tmudata == NULL) /* list is empty? */
+ g->tmudata = curr->gch.next = curr; /* creates a circular list */
+ else {
+ curr->gch.next = g->tmudata->gch.next;
+ g->tmudata->gch.next = curr;
+ g->tmudata = curr;
+ }
+ }
+ }
+ return deadmem;
+}
+
+
+static int traversetable (global_State *g, Table *h) {
+ int i;
+ int weakkey = 0;
+ int weakvalue = 0;
+ const TValue *mode;
+ if (h->metatable)
+ markobject(g, h->metatable);
+ mode = gfasttm(g, h->metatable, TM_MODE);
+ if (mode && ttisstring(mode)) { /* is there a weak mode? */
+ weakkey = (strchr(svalue(mode), 'k') != NULL);
+ weakvalue = (strchr(svalue(mode), 'v') != NULL);
+ if (weakkey || weakvalue) { /* is really weak? */
+ h->marked &= ~(KEYWEAK | VALUEWEAK); /* clear bits */
+ h->marked |= cast_byte((weakkey << KEYWEAKBIT) |
+ (weakvalue << VALUEWEAKBIT));
+ h->gclist = g->weak; /* must be cleared after GC, ... */
+ g->weak = obj2gco(h); /* ... so put in the appropriate list */
+ }
+ }
+ if (weakkey && weakvalue) return 1;
+ if (!weakvalue) {
+ i = h->sizearray;
+ while (i--)
+ markvalue(g, &h->array[i]);
+ }
+ i = sizenode(h);
+ while (i--) {
+ Node *n = gnode(h, i);
+ lua_assert(ttype(gkey(n)) != LUA_TDEADKEY || ttisnil(gval(n)));
+ if (ttisnil(gval(n)))
+ removeentry(n); /* remove empty entries */
+ else {
+ lua_assert(!ttisnil(gkey(n)));
+ if (!weakkey) markvalue(g, gkey(n));
+ if (!weakvalue) markvalue(g, gval(n));
+ }
+ }
+ return weakkey || weakvalue;
+}
+
+
+/*
+** All marks are conditional because a GC may happen while the
+** prototype is still being created
+*/
+static void traverseproto (global_State *g, Proto *f) {
+ int i;
+ if (f->source) stringmark(f->source);
+ for (i=0; i<f->sizek; i++) /* mark literals */
+ markvalue(g, &f->k[i]);
+ for (i=0; i<f->sizeupvalues; i++) { /* mark upvalue names */
+ if (f->upvalues[i])
+ stringmark(f->upvalues[i]);
+ }
+ for (i=0; i<f->sizep; i++) { /* mark nested protos */
+ if (f->p[i])
+ markobject(g, f->p[i]);
+ }
+ for (i=0; i<f->sizelocvars; i++) { /* mark local-variable names */
+ if (f->locvars[i].varname)
+ stringmark(f->locvars[i].varname);
+ }
+}
+
+
+
+static void traverseclosure (global_State *g, Closure *cl) {
+ markobject(g, cl->c.env);
+ if (cl->c.isC) {
+ int i;
+ for (i=0; i<cl->c.nupvalues; i++) /* mark its upvalues */
+ markvalue(g, &cl->c.upvalue[i]);
+ }
+ else {
+ int i;
+ lua_assert(cl->l.nupvalues == cl->l.p->nups);
+ markobject(g, cl->l.p);
+ for (i=0; i<cl->l.nupvalues; i++) /* mark its upvalues */
+ markobject(g, cl->l.upvals[i]);
+ }
+}
+
+
+static void checkstacksizes (lua_State *L, StkId max) {
+ int ci_used = cast_int(L->ci - L->base_ci); /* number of `ci' in use */
+ int s_used = cast_int(max - L->stack); /* part of stack in use */
+ if (L->size_ci > LUAI_MAXCALLS) /* handling overflow? */
+ return; /* do not touch the stacks */
+ if (4*ci_used < L->size_ci && 2*BASIC_CI_SIZE < L->size_ci)
+ luaD_reallocCI(L, L->size_ci/2); /* still big enough... */
+ condhardstacktests(luaD_reallocCI(L, ci_used + 1));
+ if (4*s_used < L->stacksize &&
+ 2*(BASIC_STACK_SIZE+EXTRA_STACK) < L->stacksize)
+ luaD_reallocstack(L, L->stacksize/2); /* still big enough... */
+ condhardstacktests(luaD_reallocstack(L, s_used));
+}
+
+
+static void traversestack (global_State *g, lua_State *l) {
+ StkId o, lim;
+ CallInfo *ci;
+ markvalue(g, gt(l));
+ lim = l->top;
+ for (ci = l->base_ci; ci <= l->ci; ci++) {
+ lua_assert(ci->top <= l->stack_last);
+ if (lim < ci->top) lim = ci->top;
+ }
+ for (o = l->stack; o < l->top; o++)
+ markvalue(g, o);
+ for (; o <= lim; o++)
+ setnilvalue(o);
+ checkstacksizes(l, lim);
+}
+
+
+/*
+** traverse one gray object, turning it to black.
+** Returns `quantity' traversed.
+*/
+static l_mem propagatemark (global_State *g) {
+ GCObject *o = g->gray;
+ lua_assert(isgray(o));
+ gray2black(o);
+ switch (o->gch.tt) {
+ case LUA_TTABLE: {
+ Table *h = gco2h(o);
+ g->gray = h->gclist;
+ if (traversetable(g, h)) /* table is weak? */
+ black2gray(o); /* keep it gray */
+ return sizeof(Table) + sizeof(TValue) * h->sizearray +
+ sizeof(Node) * sizenode(h);
+ }
+ case LUA_TFUNCTION: {
+ Closure *cl = gco2cl(o);
+ g->gray = cl->c.gclist;
+ traverseclosure(g, cl);
+ return (cl->c.isC) ? sizeCclosure(cl->c.nupvalues) :
+ sizeLclosure(cl->l.nupvalues);
+ }
+ case LUA_TTHREAD: {
+ lua_State *th = gco2th(o);
+ g->gray = th->gclist;
+ th->gclist = g->grayagain;
+ g->grayagain = o;
+ black2gray(o);
+ traversestack(g, th);
+ return sizeof(lua_State) + sizeof(TValue) * th->stacksize +
+ sizeof(CallInfo) * th->size_ci;
+ }
+ case LUA_TPROTO: {
+ Proto *p = gco2p(o);
+ g->gray = p->gclist;
+ traverseproto(g, p);
+ return sizeof(Proto) + sizeof(Instruction) * p->sizecode +
+ sizeof(Proto *) * p->sizep +
+ sizeof(TValue) * p->sizek +
+ sizeof(int) * p->sizelineinfo +
+ sizeof(LocVar) * p->sizelocvars +
+ sizeof(TString *) * p->sizeupvalues;
+ }
+ default: lua_assert(0); return 0;
+ }
+}
+
+
+static size_t propagateall (global_State *g) {
+ size_t m = 0;
+ while (g->gray) m += propagatemark(g);
+ return m;
+}
+
+
+/*
+** The next function tells whether a key or value can be cleared from
+** a weak table. Non-collectable objects are never removed from weak
+** tables. Strings behave as `values', so are never removed too. for
+** other objects: if really collected, cannot keep them; for userdata
+** being finalized, keep them in keys, but not in values
+*/
+static int iscleared (const TValue *o, int iskey) {
+ if (!iscollectable(o)) return 0;
+ if (ttisstring(o)) {
+ stringmark(rawtsvalue(o)); /* strings are `values', so are never weak */
+ return 0;
+ }
+ return iswhite(gcvalue(o)) ||
+ (ttisuserdata(o) && (!iskey && isfinalized(uvalue(o))));
+}
+
+
+/*
+** clear collected entries from weaktables
+*/
+static void cleartable (GCObject *l) {
+ while (l) {
+ Table *h = gco2h(l);
+ int i = h->sizearray;
+ lua_assert(testbit(h->marked, VALUEWEAKBIT) ||
+ testbit(h->marked, KEYWEAKBIT));
+ if (testbit(h->marked, VALUEWEAKBIT)) {
+ while (i--) {
+ TValue *o = &h->array[i];
+ if (iscleared(o, 0)) /* value was collected? */
+ setnilvalue(o); /* remove value */
+ }
+ }
+ i = sizenode(h);
+ while (i--) {
+ Node *n = gnode(h, i);
+ if (!ttisnil(gval(n)) && /* non-empty entry? */
+ (iscleared(key2tval(n), 1) || iscleared(gval(n), 0))) {
+ setnilvalue(gval(n)); /* remove value ... */
+ removeentry(n); /* remove entry from table */
+ }
+ }
+ l = h->gclist;
+ }
+}
+
+
+static void freeobj (lua_State *L, GCObject *o) {
+ switch (o->gch.tt) {
+ case LUA_TPROTO: luaF_freeproto(L, gco2p(o)); break;
+ case LUA_TFUNCTION: luaF_freeclosure(L, gco2cl(o)); break;
+ case LUA_TUPVAL: luaF_freeupval(L, gco2uv(o)); break;
+ case LUA_TTABLE: luaH_free(L, gco2h(o)); break;
+ case LUA_TTHREAD: {
+ lua_assert(gco2th(o) != L && gco2th(o) != G(L)->mainthread);
+ luaE_freethread(L, gco2th(o));
+ break;
+ }
+ case LUA_TSTRING: {
+ G(L)->strt.nuse--;
+ luaM_freemem(L, o, sizestring(gco2ts(o)));
+ break;
+ }
+ case LUA_TUSERDATA: {
+ luaM_freemem(L, o, sizeudata(gco2u(o)));
+ break;
+ }
+ default: lua_assert(0);
+ }
+}
+
+
+
+#define sweepwholelist(L,p) sweeplist(L,p,MAX_LUMEM)
+
+
+static GCObject **sweeplist (lua_State *L, GCObject **p, lu_mem count) {
+ GCObject *curr;
+ global_State *g = G(L);
+ int deadmask = otherwhite(g);
+ while ((curr = *p) != NULL && count-- > 0) {
+ if (curr->gch.tt == LUA_TTHREAD) /* sweep open upvalues of each thread */
+ sweepwholelist(L, &gco2th(curr)->openupval);
+ if ((curr->gch.marked ^ WHITEBITS) & deadmask) { /* not dead? */
+ lua_assert(!isdead(g, curr) || testbit(curr->gch.marked, FIXEDBIT));
+ makewhite(g, curr); /* make it white (for next cycle) */
+ p = &curr->gch.next;
+ }
+ else { /* must erase `curr' */
+ lua_assert(isdead(g, curr) || deadmask == bitmask(SFIXEDBIT));
+ *p = curr->gch.next;
+ if (curr == g->rootgc) /* is the first element of the list? */
+ g->rootgc = curr->gch.next; /* adjust first */
+ freeobj(L, curr);
+ }
+ }
+ return p;
+}
+
+
+static void checkSizes (lua_State *L) {
+ global_State *g = G(L);
+ /* check size of string hash */
+ if (g->strt.nuse < cast(lu_int32, g->strt.size/4) &&
+ g->strt.size > MINSTRTABSIZE*2)
+ luaS_resize(L, g->strt.size/2); /* table is too big */
+ /* check size of buffer */
+ if (luaZ_sizebuffer(&g->buff) > LUA_MINBUFFER*2) { /* buffer too big? */
+ size_t newsize = luaZ_sizebuffer(&g->buff) / 2;
+ luaZ_resizebuffer(L, &g->buff, newsize);
+ }
+}
+
+
+static void GCTM (lua_State *L) {
+ global_State *g = G(L);
+ GCObject *o = g->tmudata->gch.next; /* get first element */
+ Udata *udata = rawgco2u(o);
+ const TValue *tm;
+ /* remove udata from `tmudata' */
+ if (o == g->tmudata) /* last element? */
+ g->tmudata = NULL;
+ else
+ g->tmudata->gch.next = udata->uv.next;
+ udata->uv.next = g->mainthread->next; /* return it to `root' list */
+ g->mainthread->next = o;
+ makewhite(g, o);
+ tm = fasttm(L, udata->uv.metatable, TM_GC);
+ if (tm != NULL) {
+ lu_byte oldah = L->allowhook;
+ lu_mem oldt = g->GCthreshold;
+ L->allowhook = 0; /* stop debug hooks during GC tag method */
+ g->GCthreshold = 2*g->totalbytes; /* avoid GC steps */
+ setobj2s(L, L->top, tm);
+ setuvalue(L, L->top+1, udata);
+ L->top += 2;
+ luaD_call(L, L->top - 2, 0);
+ L->allowhook = oldah; /* restore hooks */
+ g->GCthreshold = oldt; /* restore threshold */
+ }
+}
+
+
+/*
+** Call all GC tag methods
+*/
+void luaC_callGCTM (lua_State *L) {
+ while (G(L)->tmudata)
+ GCTM(L);
+}
+
+
+void luaC_freeall (lua_State *L) {
+ global_State *g = G(L);
+ int i;
+ g->currentwhite = WHITEBITS | bitmask(SFIXEDBIT); /* mask to collect all elements */
+ sweepwholelist(L, &g->rootgc);
+ for (i = 0; i < g->strt.size; i++) /* free all string lists */
+ sweepwholelist(L, &g->strt.hash[i]);
+}
+
+
+static void markmt (global_State *g) {
+ int i;
+ for (i=0; i<NUM_TAGS; i++)
+ if (g->mt[i]) markobject(g, g->mt[i]);
+}
+
+
+/* mark root set */
+static void markroot (lua_State *L) {
+ global_State *g = G(L);
+ g->gray = NULL;
+ g->grayagain = NULL;
+ g->weak = NULL;
+ markobject(g, g->mainthread);
+ /* make global table be traversed before main stack */
+ markvalue(g, gt(g->mainthread));
+ markvalue(g, registry(L));
+ markmt(g);
+ g->gcstate = GCSpropagate;
+}
+
+
+static void remarkupvals (global_State *g) {
+ UpVal *uv;
+ for (uv = g->uvhead.u.l.next; uv != &g->uvhead; uv = uv->u.l.next) {
+ lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
+ if (isgray(obj2gco(uv)))
+ markvalue(g, uv->v);
+ }
+}
+
+
+static void atomic (lua_State *L) {
+ global_State *g = G(L);
+ size_t udsize; /* total size of userdata to be finalized */
+ /* remark occasional upvalues of (maybe) dead threads */
+ remarkupvals(g);
+ /* traverse objects cautch by write barrier and by 'remarkupvals' */
+ propagateall(g);
+ /* remark weak tables */
+ g->gray = g->weak;
+ g->weak = NULL;
+ lua_assert(!iswhite(obj2gco(g->mainthread)));
+ markobject(g, L); /* mark running thread */
+ markmt(g); /* mark basic metatables (again) */
+ propagateall(g);
+ /* remark gray again */
+ g->gray = g->grayagain;
+ g->grayagain = NULL;
+ propagateall(g);
+ udsize = luaC_separateudata(L, 0); /* separate userdata to be finalized */
+ marktmu(g); /* mark `preserved' userdata */
+ udsize += propagateall(g); /* remark, to propagate `preserveness' */
+ cleartable(g->weak); /* remove collected objects from weak tables */
+ /* flip current white */
+ g->currentwhite = cast_byte(otherwhite(g));
+ g->sweepstrgc = 0;
+ g->sweepgc = &g->rootgc;
+ g->gcstate = GCSsweepstring;
+ g->estimate = g->totalbytes - udsize; /* first estimate */
+}
+
+
+static l_mem singlestep (lua_State *L) {
+ global_State *g = G(L);
+ /*lua_checkmemory(L);*/
+ switch (g->gcstate) {
+ case GCSpause: {
+ markroot(L); /* start a new collection */
+ return 0;
+ }
+ case GCSpropagate: {
+ if (g->gray)
+ return propagatemark(g);
+ else { /* no more `gray' objects */
+ atomic(L); /* finish mark phase */
+ return 0;
+ }
+ }
+ case GCSsweepstring: {
+ lu_mem old = g->totalbytes;
+ sweepwholelist(L, &g->strt.hash[g->sweepstrgc++]);
+ if (g->sweepstrgc >= g->strt.size) /* nothing more to sweep? */
+ g->gcstate = GCSsweep; /* end sweep-string phase */
+ lua_assert(old >= g->totalbytes);
+ g->estimate -= old - g->totalbytes;
+ return GCSWEEPCOST;
+ }
+ case GCSsweep: {
+ lu_mem old = g->totalbytes;
+ g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX);
+ if (*g->sweepgc == NULL) { /* nothing more to sweep? */
+ checkSizes(L);
+ g->gcstate = GCSfinalize; /* end sweep phase */
+ }
+ lua_assert(old >= g->totalbytes);
+ g->estimate -= old - g->totalbytes;
+ return GCSWEEPMAX*GCSWEEPCOST;
+ }
+ case GCSfinalize: {
+ if (g->tmudata) {
+ GCTM(L);
+ if (g->estimate > GCFINALIZECOST)
+ g->estimate -= GCFINALIZECOST;
+ return GCFINALIZECOST;
+ }
+ else {
+ g->gcstate = GCSpause; /* end collection */
+ g->gcdept = 0;
+ return 0;
+ }
+ }
+ default: lua_assert(0); return 0;
+ }
+}
+
+
+void luaC_step (lua_State *L) {
+ global_State *g = G(L);
+ l_mem lim = (GCSTEPSIZE/100) * g->gcstepmul;
+ if (lim == 0)
+ lim = (MAX_LUMEM-1)/2; /* no limit */
+ g->gcdept += g->totalbytes - g->GCthreshold;
+ do {
+ lim -= singlestep(L);
+ if (g->gcstate == GCSpause)
+ break;
+ } while (lim > 0);
+ if (g->gcstate != GCSpause) {
+ if (g->gcdept < GCSTEPSIZE)
+ g->GCthreshold = g->totalbytes + GCSTEPSIZE; /* - lim/g->gcstepmul;*/
+ else {
+ g->gcdept -= GCSTEPSIZE;
+ g->GCthreshold = g->totalbytes;
+ }
+ }
+ else {
+ lua_assert(g->totalbytes >= g->estimate);
+ setthreshold(g);
+ }
+}
+
+
+void luaC_fullgc (lua_State *L) {
+ global_State *g = G(L);
+ if (g->gcstate <= GCSpropagate) {
+ /* reset sweep marks to sweep all elements (returning them to white) */
+ g->sweepstrgc = 0;
+ g->sweepgc = &g->rootgc;
+ /* reset other collector lists */
+ g->gray = NULL;
+ g->grayagain = NULL;
+ g->weak = NULL;
+ g->gcstate = GCSsweepstring;
+ }
+ lua_assert(g->gcstate != GCSpause && g->gcstate != GCSpropagate);
+ /* finish any pending sweep phase */
+ while (g->gcstate != GCSfinalize) {
+ lua_assert(g->gcstate == GCSsweepstring || g->gcstate == GCSsweep);
+ singlestep(L);
+ }
+ markroot(L);
+ while (g->gcstate != GCSpause) {
+ singlestep(L);
+ }
+ setthreshold(g);
+}
+
+
+void luaC_barrierf (lua_State *L, GCObject *o, GCObject *v) {
+ global_State *g = G(L);
+ lua_assert(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o));
+ lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause);
+ lua_assert(ttype(&o->gch) != LUA_TTABLE);
+ /* must keep invariant? */
+ if (g->gcstate == GCSpropagate)
+ reallymarkobject(g, v); /* restore invariant */
+ else /* don't mind */
+ makewhite(g, o); /* mark as white just to avoid other barriers */
+}
+
+
+void luaC_barrierback (lua_State *L, Table *t) {
+ global_State *g = G(L);
+ GCObject *o = obj2gco(t);
+ lua_assert(isblack(o) && !isdead(g, o));
+ lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause);
+ black2gray(o); /* make table gray (again) */
+ t->gclist = g->grayagain;
+ g->grayagain = o;
+}
+
+
+void luaC_link (lua_State *L, GCObject *o, lu_byte tt) {
+ global_State *g = G(L);
+ o->gch.next = g->rootgc;
+ g->rootgc = o;
+ o->gch.marked = luaC_white(g);
+ o->gch.tt = tt;
+}
+
+
+void luaC_linkupval (lua_State *L, UpVal *uv) {
+ global_State *g = G(L);
+ GCObject *o = obj2gco(uv);
+ o->gch.next = g->rootgc; /* link upvalue into `rootgc' list */
+ g->rootgc = o;
+ if (isgray(o)) {
+ if (g->gcstate == GCSpropagate) {
+ gray2black(o); /* closed upvalues need barrier */
+ luaC_barrier(L, uv, uv->v);
+ }
+ else { /* sweep phase: sweep it (turning it into white) */
+ makewhite(g, o);
+ lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause);
+ }
+ }
+}
+
diff --git a/lib/lua/src/lgc.h b/lib/lua/src/lgc.h
new file mode 100644
index 000000000..5a8dc605b
--- /dev/null
+++ b/lib/lua/src/lgc.h
@@ -0,0 +1,110 @@
+/*
+** $Id: lgc.h,v 2.15.1.1 2007/12/27 13:02:25 roberto Exp $
+** Garbage Collector
+** See Copyright Notice in lua.h
+*/
+
+#ifndef lgc_h
+#define lgc_h
+
+
+#include "lobject.h"
+
+
+/*
+** Possible states of the Garbage Collector
+*/
+#define GCSpause 0
+#define GCSpropagate 1
+#define GCSsweepstring 2
+#define GCSsweep 3
+#define GCSfinalize 4
+
+
+/*
+** some userful bit tricks
+*/
+#define resetbits(x,m) ((x) &= cast(lu_byte, ~(m)))
+#define setbits(x,m) ((x) |= (m))
+#define testbits(x,m) ((x) & (m))
+#define bitmask(b) (1<<(b))
+#define bit2mask(b1,b2) (bitmask(b1) | bitmask(b2))
+#define l_setbit(x,b) setbits(x, bitmask(b))
+#define resetbit(x,b) resetbits(x, bitmask(b))
+#define testbit(x,b) testbits(x, bitmask(b))
+#define set2bits(x,b1,b2) setbits(x, (bit2mask(b1, b2)))
+#define reset2bits(x,b1,b2) resetbits(x, (bit2mask(b1, b2)))
+#define test2bits(x,b1,b2) testbits(x, (bit2mask(b1, b2)))
+
+
+
+/*
+** Layout for bit use in `marked' field:
+** bit 0 - object is white (type 0)
+** bit 1 - object is white (type 1)
+** bit 2 - object is black
+** bit 3 - for userdata: has been finalized
+** bit 3 - for tables: has weak keys
+** bit 4 - for tables: has weak values
+** bit 5 - object is fixed (should not be collected)
+** bit 6 - object is "super" fixed (only the main thread)
+*/
+
+
+#define WHITE0BIT 0
+#define WHITE1BIT 1
+#define BLACKBIT 2
+#define FINALIZEDBIT 3
+#define KEYWEAKBIT 3
+#define VALUEWEAKBIT 4
+#define FIXEDBIT 5
+#define SFIXEDBIT 6
+#define WHITEBITS bit2mask(WHITE0BIT, WHITE1BIT)
+
+
+#define iswhite(x) test2bits((x)->gch.marked, WHITE0BIT, WHITE1BIT)
+#define isblack(x) testbit((x)->gch.marked, BLACKBIT)
+#define isgray(x) (!isblack(x) && !iswhite(x))
+
+#define otherwhite(g) (g->currentwhite ^ WHITEBITS)
+#define isdead(g,v) ((v)->gch.marked & otherwhite(g) & WHITEBITS)
+
+#define changewhite(x) ((x)->gch.marked ^= WHITEBITS)
+#define gray2black(x) l_setbit((x)->gch.marked, BLACKBIT)
+
+#define valiswhite(x) (iscollectable(x) && iswhite(gcvalue(x)))
+
+#define luaC_white(g) cast(lu_byte, (g)->currentwhite & WHITEBITS)
+
+
+#define luaC_checkGC(L) { \
+ condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1)); \
+ if (G(L)->totalbytes >= G(L)->GCthreshold) \
+ luaC_step(L); }
+
+
+#define luaC_barrier(L,p,v) { if (valiswhite(v) && isblack(obj2gco(p))) \
+ luaC_barrierf(L,obj2gco(p),gcvalue(v)); }
+
+#define luaC_barriert(L,t,v) { if (valiswhite(v) && isblack(obj2gco(t))) \
+ luaC_barrierback(L,t); }
+
+#define luaC_objbarrier(L,p,o) \
+ { if (iswhite(obj2gco(o)) && isblack(obj2gco(p))) \
+ luaC_barrierf(L,obj2gco(p),obj2gco(o)); }
+
+#define luaC_objbarriert(L,t,o) \
+ { if (iswhite(obj2gco(o)) && isblack(obj2gco(t))) luaC_barrierback(L,t); }
+
+LUAI_FUNC size_t luaC_separateudata (lua_State *L, int all);
+LUAI_FUNC void luaC_callGCTM (lua_State *L);
+LUAI_FUNC void luaC_freeall (lua_State *L);
+LUAI_FUNC void luaC_step (lua_State *L);
+LUAI_FUNC void luaC_fullgc (lua_State *L);
+LUAI_FUNC void luaC_link (lua_State *L, GCObject *o, lu_byte tt);
+LUAI_FUNC void luaC_linkupval (lua_State *L, UpVal *uv);
+LUAI_FUNC void luaC_barrierf (lua_State *L, GCObject *o, GCObject *v);
+LUAI_FUNC void luaC_barrierback (lua_State *L, Table *t);
+
+
+#endif
diff --git a/lib/lua/src/linit.c b/lib/lua/src/linit.c
new file mode 100644
index 000000000..c1f90dfab
--- /dev/null
+++ b/lib/lua/src/linit.c
@@ -0,0 +1,38 @@
+/*
+** $Id: linit.c,v 1.14.1.1 2007/12/27 13:02:25 roberto Exp $
+** Initialization of libraries for lua.c
+** See Copyright Notice in lua.h
+*/
+
+
+#define linit_c
+#define LUA_LIB
+
+#include "lua.h"
+
+#include "lualib.h"
+#include "lauxlib.h"
+
+
+static const luaL_Reg lualibs[] = {
+ {"", luaopen_base},
+ {LUA_LOADLIBNAME, luaopen_package},
+ {LUA_TABLIBNAME, luaopen_table},
+ {LUA_IOLIBNAME, luaopen_io},
+ {LUA_OSLIBNAME, luaopen_os},
+ {LUA_STRLIBNAME, luaopen_string},
+ {LUA_MATHLIBNAME, luaopen_math},
+ {LUA_DBLIBNAME, luaopen_debug},
+ {NULL, NULL}
+};
+
+
+LUALIB_API void luaL_openlibs (lua_State *L) {
+ const luaL_Reg *lib = lualibs;
+ for (; lib->func; lib++) {
+ lua_pushcfunction(L, lib->func);
+ lua_pushstring(L, lib->name);
+ lua_call(L, 1, 0);
+ }
+}
+
diff --git a/lib/lua/src/liolib.c b/lib/lua/src/liolib.c
new file mode 100644
index 000000000..e79ed1cb2
--- /dev/null
+++ b/lib/lua/src/liolib.c
@@ -0,0 +1,553 @@
+/*
+** $Id: liolib.c,v 2.73.1.3 2008/01/18 17:47:43 roberto Exp $
+** Standard I/O (and system) library
+** See Copyright Notice in lua.h
+*/
+
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define liolib_c
+#define LUA_LIB
+
+#include "lua.h"
+
+#include "lauxlib.h"
+#include "lualib.h"
+
+
+
+#define IO_INPUT 1
+#define IO_OUTPUT 2
+
+
+static const char *const fnames[] = {"input", "output"};
+
+
+static int pushresult (lua_State *L, int i, const char *filename) {
+ int en = errno; /* calls to Lua API may change this value */
+ if (i) {
+ lua_pushboolean(L, 1);
+ return 1;
+ }
+ else {
+ lua_pushnil(L);
+ if (filename)
+ lua_pushfstring(L, "%s: %s", filename, strerror(en));
+ else
+ lua_pushfstring(L, "%s", strerror(en));
+ lua_pushinteger(L, en);
+ return 3;
+ }
+}
+
+
+static void fileerror (lua_State *L, int arg, const char *filename) {
+ lua_pushfstring(L, "%s: %s", filename, strerror(errno));
+ luaL_argerror(L, arg, lua_tostring(L, -1));
+}
+
+
+#define tofilep(L) ((FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE))
+
+
+static int io_type (lua_State *L) {
+ void *ud;
+ luaL_checkany(L, 1);
+ ud = lua_touserdata(L, 1);
+ lua_getfield(L, LUA_REGISTRYINDEX, LUA_FILEHANDLE);
+ if (ud == NULL || !lua_getmetatable(L, 1) || !lua_rawequal(L, -2, -1))
+ lua_pushnil(L); /* not a file */
+ else if (*((FILE **)ud) == NULL)
+ lua_pushliteral(L, "closed file");
+ else
+ lua_pushliteral(L, "file");
+ return 1;
+}
+
+
+static FILE *tofile (lua_State *L) {
+ FILE **f = tofilep(L);
+ if (*f == NULL)
+ luaL_error(L, "attempt to use a closed file");
+ return *f;
+}
+
+
+
+/*
+** When creating file handles, always creates a `closed' file handle
+** before opening the actual file; so, if there is a memory error, the
+** file is not left opened.
+*/
+static FILE **newfile (lua_State *L) {
+ FILE **pf = (FILE **)lua_newuserdata(L, sizeof(FILE *));
+ *pf = NULL; /* file handle is currently `closed' */
+ luaL_getmetatable(L, LUA_FILEHANDLE);
+ lua_setmetatable(L, -2);
+ return pf;
+}
+
+
+/*
+** function to (not) close the standard files stdin, stdout, and stderr
+*/
+static int io_noclose (lua_State *L) {
+ lua_pushnil(L);
+ lua_pushliteral(L, "cannot close standard file");
+ return 2;
+}
+
+
+/*
+** function to close 'popen' files
+*/
+static int io_pclose (lua_State *L) {
+ FILE **p = tofilep(L);
+ int ok = lua_pclose(L, *p);
+ *p = NULL;
+ return pushresult(L, ok, NULL);
+}
+
+
+/*
+** function to close regular files
+*/
+static int io_fclose (lua_State *L) {
+ FILE **p = tofilep(L);
+ int ok = (fclose(*p) == 0);
+ *p = NULL;
+ return pushresult(L, ok, NULL);
+}
+
+
+static int aux_close (lua_State *L) {
+ lua_getfenv(L, 1);
+ lua_getfield(L, -1, "__close");
+ return (lua_tocfunction(L, -1))(L);
+}
+
+
+static int io_close (lua_State *L) {
+ if (lua_isnone(L, 1))
+ lua_rawgeti(L, LUA_ENVIRONINDEX, IO_OUTPUT);
+ tofile(L); /* make sure argument is a file */
+ return aux_close(L);
+}
+
+
+static int io_gc (lua_State *L) {
+ FILE *f = *tofilep(L);
+ /* ignore closed files */
+ if (f != NULL)
+ aux_close(L);
+ return 0;
+}
+
+
+static int io_tostring (lua_State *L) {
+ FILE *f = *tofilep(L);
+ if (f == NULL)
+ lua_pushliteral(L, "file (closed)");
+ else
+ lua_pushfstring(L, "file (%p)", f);
+ return 1;
+}
+
+
+static int io_open (lua_State *L) {
+ const char *filename = luaL_checkstring(L, 1);
+ const char *mode = luaL_optstring(L, 2, "r");
+ FILE **pf = newfile(L);
+ *pf = fopen(filename, mode);
+ return (*pf == NULL) ? pushresult(L, 0, filename) : 1;
+}
+
+
+/*
+** this function has a separated environment, which defines the
+** correct __close for 'popen' files
+*/
+static int io_popen (lua_State *L) {
+ const char *filename = luaL_checkstring(L, 1);
+ const char *mode = luaL_optstring(L, 2, "r");
+ FILE **pf = newfile(L);
+ *pf = lua_popen(L, filename, mode);
+ return (*pf == NULL) ? pushresult(L, 0, filename) : 1;
+}
+
+
+static int io_tmpfile (lua_State *L) {
+ FILE **pf = newfile(L);
+ *pf = tmpfile();
+ return (*pf == NULL) ? pushresult(L, 0, NULL) : 1;
+}
+
+
+static FILE *getiofile (lua_State *L, int findex) {
+ FILE *f;
+ lua_rawgeti(L, LUA_ENVIRONINDEX, findex);
+ f = *(FILE **)lua_touserdata(L, -1);
+ if (f == NULL)
+ luaL_error(L, "standard %s file is closed", fnames[findex - 1]);
+ return f;
+}
+
+
+static int g_iofile (lua_State *L, int f, const char *mode) {
+ if (!lua_isnoneornil(L, 1)) {
+ const char *filename = lua_tostring(L, 1);
+ if (filename) {
+ FILE **pf = newfile(L);
+ *pf = fopen(filename, mode);
+ if (*pf == NULL)
+ fileerror(L, 1, filename);
+ }
+ else {
+ tofile(L); /* check that it's a valid file handle */
+ lua_pushvalue(L, 1);
+ }
+ lua_rawseti(L, LUA_ENVIRONINDEX, f);
+ }
+ /* return current value */
+ lua_rawgeti(L, LUA_ENVIRONINDEX, f);
+ return 1;
+}
+
+
+static int io_input (lua_State *L) {
+ return g_iofile(L, IO_INPUT, "r");
+}
+
+
+static int io_output (lua_State *L) {
+ return g_iofile(L, IO_OUTPUT, "w");
+}
+
+
+static int io_readline (lua_State *L);
+
+
+static void aux_lines (lua_State *L, int idx, int toclose) {
+ lua_pushvalue(L, idx);
+ lua_pushboolean(L, toclose); /* close/not close file when finished */
+ lua_pushcclosure(L, io_readline, 2);
+}
+
+
+static int f_lines (lua_State *L) {
+ tofile(L); /* check that it's a valid file handle */
+ aux_lines(L, 1, 0);
+ return 1;
+}
+
+
+static int io_lines (lua_State *L) {
+ if (lua_isnoneornil(L, 1)) { /* no arguments? */
+ /* will iterate over default input */
+ lua_rawgeti(L, LUA_ENVIRONINDEX, IO_INPUT);
+ return f_lines(L);
+ }
+ else {
+ const char *filename = luaL_checkstring(L, 1);
+ FILE **pf = newfile(L);
+ *pf = fopen(filename, "r");
+ if (*pf == NULL)
+ fileerror(L, 1, filename);
+ aux_lines(L, lua_gettop(L), 1);
+ return 1;
+ }
+}
+
+
+/*
+** {======================================================
+** READ
+** =======================================================
+*/
+
+
+static int read_number (lua_State *L, FILE *f) {
+ lua_Number d;
+ if (fscanf(f, LUA_NUMBER_SCAN, &d) == 1) {
+ lua_pushnumber(L, d);
+ return 1;
+ }
+ else return 0; /* read fails */
+}
+
+
+static int test_eof (lua_State *L, FILE *f) {
+ int c = getc(f);
+ ungetc(c, f);
+ lua_pushlstring(L, NULL, 0);
+ return (c != EOF);
+}
+
+
+static int read_line (lua_State *L, FILE *f) {
+ luaL_Buffer b;
+ luaL_buffinit(L, &b);
+ for (;;) {
+ size_t l;
+ char *p = luaL_prepbuffer(&b);
+ if (fgets(p, LUAL_BUFFERSIZE, f) == NULL) { /* eof? */
+ luaL_pushresult(&b); /* close buffer */
+ return (lua_objlen(L, -1) > 0); /* check whether read something */
+ }
+ l = strlen(p);
+ if (l == 0 || p[l-1] != '\n')
+ luaL_addsize(&b, l);
+ else {
+ luaL_addsize(&b, l - 1); /* do not include `eol' */
+ luaL_pushresult(&b); /* close buffer */
+ return 1; /* read at least an `eol' */
+ }
+ }
+}
+
+
+static int read_chars (lua_State *L, FILE *f, size_t n) {
+ size_t rlen; /* how much to read */
+ size_t nr; /* number of chars actually read */
+ luaL_Buffer b;
+ luaL_buffinit(L, &b);
+ rlen = LUAL_BUFFERSIZE; /* try to read that much each time */
+ do {
+ char *p = luaL_prepbuffer(&b);
+ if (rlen > n) rlen = n; /* cannot read more than asked */
+ nr = fread(p, sizeof(char), rlen, f);
+ luaL_addsize(&b, nr);
+ n -= nr; /* still have to read `n' chars */
+ } while (n > 0 && nr == rlen); /* until end of count or eof */
+ luaL_pushresult(&b); /* close buffer */
+ return (n == 0 || lua_objlen(L, -1) > 0);
+}
+
+
+static int g_read (lua_State *L, FILE *f, int first) {
+ int nargs = lua_gettop(L) - 1;
+ int success;
+ int n;
+ clearerr(f);
+ if (nargs == 0) { /* no arguments? */
+ success = read_line(L, f);
+ n = first+1; /* to return 1 result */
+ }
+ else { /* ensure stack space for all results and for auxlib's buffer */
+ luaL_checkstack(L, nargs+LUA_MINSTACK, "too many arguments");
+ success = 1;
+ for (n = first; nargs-- && success; n++) {
+ if (lua_type(L, n) == LUA_TNUMBER) {
+ size_t l = (size_t)lua_tointeger(L, n);
+ success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l);
+ }
+ else {
+ const char *p = lua_tostring(L, n);
+ luaL_argcheck(L, p && p[0] == '*', n, "invalid option");
+ switch (p[1]) {
+ case 'n': /* number */
+ success = read_number(L, f);
+ break;
+ case 'l': /* line */
+ success = read_line(L, f);
+ break;
+ case 'a': /* file */
+ read_chars(L, f, ~((size_t)0)); /* read MAX_SIZE_T chars */
+ success = 1; /* always success */
+ break;
+ default:
+ return luaL_argerror(L, n, "invalid format");
+ }
+ }
+ }
+ }
+ if (ferror(f))
+ return pushresult(L, 0, NULL);
+ if (!success) {
+ lua_pop(L, 1); /* remove last result */
+ lua_pushnil(L); /* push nil instead */
+ }
+ return n - first;
+}
+
+
+static int io_read (lua_State *L) {
+ return g_read(L, getiofile(L, IO_INPUT), 1);
+}
+
+
+static int f_read (lua_State *L) {
+ return g_read(L, tofile(L), 2);
+}
+
+
+static int io_readline (lua_State *L) {
+ FILE *f = *(FILE **)lua_touserdata(L, lua_upvalueindex(1));
+ int sucess;
+ if (f == NULL) /* file is already closed? */
+ luaL_error(L, "file is already closed");
+ sucess = read_line(L, f);
+ if (ferror(f))
+ return luaL_error(L, "%s", strerror(errno));
+ if (sucess) return 1;
+ else { /* EOF */
+ if (lua_toboolean(L, lua_upvalueindex(2))) { /* generator created file? */
+ lua_settop(L, 0);
+ lua_pushvalue(L, lua_upvalueindex(1));
+ aux_close(L); /* close it */
+ }
+ return 0;
+ }
+}
+
+/* }====================================================== */
+
+
+static int g_write (lua_State *L, FILE *f, int arg) {
+ int nargs = lua_gettop(L) - 1;
+ int status = 1;
+ for (; nargs--; arg++) {
+ if (lua_type(L, arg) == LUA_TNUMBER) {
+ /* optimization: could be done exactly as for strings */
+ status = status &&
+ fprintf(f, LUA_NUMBER_FMT, lua_tonumber(L, arg)) > 0;
+ }
+ else {
+ size_t l;
+ const char *s = luaL_checklstring(L, arg, &l);
+ status = status && (fwrite(s, sizeof(char), l, f) == l);
+ }
+ }
+ return pushresult(L, status, NULL);
+}
+
+
+static int io_write (lua_State *L) {
+ return g_write(L, getiofile(L, IO_OUTPUT), 1);
+}
+
+
+static int f_write (lua_State *L) {
+ return g_write(L, tofile(L), 2);
+}
+
+
+static int f_seek (lua_State *L) {
+ static const int mode[] = {SEEK_SET, SEEK_CUR, SEEK_END};
+ static const char *const modenames[] = {"set", "cur", "end", NULL};
+ FILE *f = tofile(L);
+ int op = luaL_checkoption(L, 2, "cur", modenames);
+ long offset = luaL_optlong(L, 3, 0);
+ op = fseek(f, offset, mode[op]);
+ if (op)
+ return pushresult(L, 0, NULL); /* error */
+ else {
+ lua_pushinteger(L, ftell(f));
+ return 1;
+ }
+}
+
+
+static int f_setvbuf (lua_State *L) {
+ static const int mode[] = {_IONBF, _IOFBF, _IOLBF};
+ static const char *const modenames[] = {"no", "full", "line", NULL};
+ FILE *f = tofile(L);
+ int op = luaL_checkoption(L, 2, NULL, modenames);
+ lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE);
+ int res = setvbuf(f, NULL, mode[op], sz);
+ return pushresult(L, res == 0, NULL);
+}
+
+
+
+static int io_flush (lua_State *L) {
+ return pushresult(L, fflush(getiofile(L, IO_OUTPUT)) == 0, NULL);
+}
+
+
+static int f_flush (lua_State *L) {
+ return pushresult(L, fflush(tofile(L)) == 0, NULL);
+}
+
+
+static const luaL_Reg iolib[] = {
+ {"close", io_close},
+ {"flush", io_flush},
+ {"input", io_input},
+ {"lines", io_lines},
+ {"open", io_open},
+ {"output", io_output},
+ {"popen", io_popen},
+ {"read", io_read},
+ {"tmpfile", io_tmpfile},
+ {"type", io_type},
+ {"write", io_write},
+ {NULL, NULL}
+};
+
+
+static const luaL_Reg flib[] = {
+ {"close", io_close},
+ {"flush", f_flush},
+ {"lines", f_lines},
+ {"read", f_read},
+ {"seek", f_seek},
+ {"setvbuf", f_setvbuf},
+ {"write", f_write},
+ {"__gc", io_gc},
+ {"__tostring", io_tostring},
+ {NULL, NULL}
+};
+
+
+static void createmeta (lua_State *L) {
+ luaL_newmetatable(L, LUA_FILEHANDLE); /* create metatable for file handles */
+ lua_pushvalue(L, -1); /* push metatable */
+ lua_setfield(L, -2, "__index"); /* metatable.__index = metatable */
+ luaL_register(L, NULL, flib); /* file methods */
+}
+
+
+static void createstdfile (lua_State *L, FILE *f, int k, const char *fname) {
+ *newfile(L) = f;
+ if (k > 0) {
+ lua_pushvalue(L, -1);
+ lua_rawseti(L, LUA_ENVIRONINDEX, k);
+ }
+ lua_pushvalue(L, -2); /* copy environment */
+ lua_setfenv(L, -2); /* set it */
+ lua_setfield(L, -3, fname);
+}
+
+
+static void newfenv (lua_State *L, lua_CFunction cls) {
+ lua_createtable(L, 0, 1);
+ lua_pushcfunction(L, cls);
+ lua_setfield(L, -2, "__close");
+}
+
+
+LUALIB_API int luaopen_io (lua_State *L) {
+ createmeta(L);
+ /* create (private) environment (with fields IO_INPUT, IO_OUTPUT, __close) */
+ newfenv(L, io_fclose);
+ lua_replace(L, LUA_ENVIRONINDEX);
+ /* open library */
+ luaL_register(L, LUA_IOLIBNAME, iolib);
+ /* create (and set) default files */
+ newfenv(L, io_noclose); /* close function for default files */
+ createstdfile(L, stdin, IO_INPUT, "stdin");
+ createstdfile(L, stdout, IO_OUTPUT, "stdout");
+ createstdfile(L, stderr, 0, "stderr");
+ lua_pop(L, 1); /* pop environment for default files */
+ lua_getfield(L, -1, "popen");
+ newfenv(L, io_pclose); /* create environment for 'popen' */
+ lua_setfenv(L, -2); /* set fenv for 'popen' */
+ lua_pop(L, 1); /* pop 'popen' */
+ return 1;
+}
+
diff --git a/lib/lua/src/llex.c b/lib/lua/src/llex.c
new file mode 100644
index 000000000..98068c1aa
--- /dev/null
+++ b/lib/lua/src/llex.c
@@ -0,0 +1,467 @@
+/*
+** $Id: llex.c,v 2.20.1.1 2007/12/27 13:02:25 roberto Exp $
+** Lexical Analyzer
+** See Copyright Notice in lua.h
+*/
+
+
+#include <ctype.h>
+#include <locale.h>
+#include <string.h>
+
+#define llex_c
+#define LUA_CORE
+
+#include "lua.h"
+
+#include "ldo.h"
+#include "llex.h"
+#include "lobject.h"
+#include "lparser.h"
+#include "lstate.h"
+#include "lstring.h"
+#include "ltable.h"
+#include "lzio.h"
+
+
+
+#define next(ls) (ls->current = zgetc(ls->z))
+
+
+
+
+#define currIsNewline(ls) (ls->current == '\n' || ls->current == '\r')
+
+
+/* ORDER RESERVED */
+const char *const luaX_tokens [] = {
+ "and", "break", "do", "else", "elseif",
+ "end", "false", "for", "function", "if",
+ "in", "local", "nil", "not", "or", "repeat",
+ "return", "then", "true", "until", "while",
+ "..", "...", "==", ">=", "<=", "~=",
+ "<number>", "<name>", "<string>", "<eof>",
+ NULL
+};
+
+
+#define save_and_next(ls) (save(ls, ls->current), next(ls))
+
+
+static void save (LexState *ls, int c) {
+ Mbuffer *b = ls->buff;
+ if (b->n + 1 > b->buffsize) {
+ size_t newsize;
+ if (b->buffsize >= MAX_SIZET/2)
+ luaX_lexerror(ls, "lexical element too long", 0);
+ newsize = b->buffsize * 2;
+ luaZ_resizebuffer(ls->L, b, newsize);
+ }
+ b->buffer[b->n++] = cast(char, c);
+}
+
+
+void luaX_init (lua_State *L) {
+ int i;
+ for (i=0; i<NUM_RESERVED; i++) {
+ TString *ts = luaS_new(L, luaX_tokens[i]);
+ luaS_fix(ts); /* reserved words are never collected */
+ lua_assert(strlen(luaX_tokens[i])+1 <= TOKEN_LEN);
+ ts->tsv.reserved = cast_byte(i+1); /* reserved word */
+ }
+}
+
+
+#define MAXSRC 80
+
+
+const char *luaX_token2str (LexState *ls, int token) {
+ if (token < FIRST_RESERVED) {
+ lua_assert(token == cast(unsigned char, token));
+ return (iscntrl(token)) ? luaO_pushfstring(ls->L, "char(%d)", token) :
+ luaO_pushfstring(ls->L, "%c", token);
+ }
+ else
+ return luaX_tokens[token-FIRST_RESERVED];
+}
+
+
+static const char *txtToken (LexState *ls, int token) {
+ switch (token) {
+ case TK_NAME:
+ case TK_STRING:
+ case TK_NUMBER:
+ save(ls, '\0');
+ return luaZ_buffer(ls->buff);
+ default:
+ return luaX_token2str(ls, token);
+ }
+}
+
+
+void luaX_lexerror (LexState *ls, const char *msg, int token) {
+ char buff[MAXSRC];
+ luaO_chunkid(buff, getstr(ls->source), MAXSRC);
+ msg = luaO_pushfstring(ls->L, "%s:%d: %s", buff, ls->linenumber, msg);
+ if (token)
+ luaO_pushfstring(ls->L, "%s near " LUA_QS, msg, txtToken(ls, token));
+ luaD_throw(ls->L, LUA_ERRSYNTAX);
+}
+
+
+void luaX_syntaxerror (LexState *ls, const char *msg) {
+ luaX_lexerror(ls, msg, ls->t.token);
+}
+
+
+TString *luaX_newstring (LexState *ls, const char *str, size_t l) {
+ lua_State *L = ls->L;
+ TString *ts = luaS_newlstr(L, str, l);
+ TValue *o = luaH_setstr(L, ls->fs->h, ts); /* entry for `str' */
+ if (ttisnil(o))
+ setbvalue(o, 1); /* make sure `str' will not be collected */
+ return ts;
+}
+
+
+static void inclinenumber (LexState *ls) {
+ int old = ls->current;
+ lua_assert(currIsNewline(ls));
+ next(ls); /* skip `\n' or `\r' */
+ if (currIsNewline(ls) && ls->current != old)
+ next(ls); /* skip `\n\r' or `\r\n' */
+ if (++ls->linenumber >= MAX_INT)
+ luaX_syntaxerror(ls, "chunk has too many lines");
+}
+
+
+void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source) {
+ ls->decpoint = '.';
+ ls->L = L;
+ ls->lookahead.token = TK_EOS; /* no look-ahead token */
+ ls->z = z;
+ ls->fs = NULL;
+ ls->linenumber = 1;
+ ls->lastline = 1;
+ ls->source = source;
+ luaZ_resizebuffer(ls->L, ls->buff, LUA_MINBUFFER); /* initialize buffer */
+ next(ls); /* read first char */
+}
+
+
+
+/*
+** =======================================================
+** LEXICAL ANALYZER
+** =======================================================
+*/
+
+
+
+static int check_next (LexState *ls, const char *set) {
+ if (!strchr(set, ls->current))
+ return 0;
+ save_and_next(ls);
+ return 1;
+}
+
+
+static void buffreplace (LexState *ls, char from, char to) {
+ size_t n = luaZ_bufflen(ls->buff);
+ char *p = luaZ_buffer(ls->buff);
+ while (n--)
+ if (p[n] == from) p[n] = to;
+}
+
+
+static void trydecpoint (LexState *ls, SemInfo *seminfo) {
+ /* format error: try to update decimal point separator */
+#ifndef __ANDROID__
+ struct lconv *cv = localeconv();
+#endif
+ char old = ls->decpoint;
+#ifndef __ANDROID__
+ ls->decpoint = (cv ? cv->decimal_point[0] : '.');
+#else
+ ls->decpoint = '.';
+#endif
+ buffreplace(ls, old, ls->decpoint); /* try updated decimal separator */
+ if (!luaO_str2d(luaZ_buffer(ls->buff), &seminfo->r)) {
+ /* format error with correct decimal point: no more options */
+ buffreplace(ls, ls->decpoint, '.'); /* undo change (for error message) */
+ luaX_lexerror(ls, "malformed number", TK_NUMBER);
+ }
+}
+
+
+/* LUA_NUMBER */
+static void read_numeral (LexState *ls, SemInfo *seminfo) {
+ lua_assert(isdigit(ls->current));
+ do {
+ save_and_next(ls);
+ } while (isdigit(ls->current) || ls->current == '.');
+ if (check_next(ls, "Ee")) /* `E'? */
+ check_next(ls, "+-"); /* optional exponent sign */
+ while (isalnum(ls->current) || ls->current == '_')
+ save_and_next(ls);
+ save(ls, '\0');
+ buffreplace(ls, '.', ls->decpoint); /* follow locale for decimal point */
+ if (!luaO_str2d(luaZ_buffer(ls->buff), &seminfo->r)) /* format error? */
+ trydecpoint(ls, seminfo); /* try to update decimal point separator */
+}
+
+
+static int skip_sep (LexState *ls) {
+ int count = 0;
+ int s = ls->current;
+ lua_assert(s == '[' || s == ']');
+ save_and_next(ls);
+ while (ls->current == '=') {
+ save_and_next(ls);
+ count++;
+ }
+ return (ls->current == s) ? count : (-count) - 1;
+}
+
+
+static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) {
+ int cont = 0;
+ (void)(cont); /* avoid warnings when `cont' is not used */
+ save_and_next(ls); /* skip 2nd `[' */
+ if (currIsNewline(ls)) /* string starts with a newline? */
+ inclinenumber(ls); /* skip it */
+ for (;;) {
+ switch (ls->current) {
+ case EOZ:
+ luaX_lexerror(ls, (seminfo) ? "unfinished long string" :
+ "unfinished long comment", TK_EOS);
+ break; /* to avoid warnings */
+#if defined(LUA_COMPAT_LSTR)
+ case '[': {
+ if (skip_sep(ls) == sep) {
+ save_and_next(ls); /* skip 2nd `[' */
+ cont++;
+#if LUA_COMPAT_LSTR == 1
+ if (sep == 0)
+ luaX_lexerror(ls, "nesting of [[...]] is deprecated", '[');
+#endif
+ }
+ break;
+ }
+#endif
+ case ']': {
+ if (skip_sep(ls) == sep) {
+ save_and_next(ls); /* skip 2nd `]' */
+#if defined(LUA_COMPAT_LSTR) && LUA_COMPAT_LSTR == 2
+ cont--;
+ if (sep == 0 && cont >= 0) break;
+#endif
+ goto endloop;
+ }
+ break;
+ }
+ case '\n':
+ case '\r': {
+ save(ls, '\n');
+ inclinenumber(ls);
+ if (!seminfo) luaZ_resetbuffer(ls->buff); /* avoid wasting space */
+ break;
+ }
+ default: {
+ if (seminfo) save_and_next(ls);
+ else next(ls);
+ }
+ }
+ } endloop:
+ if (seminfo)
+ seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + (2 + sep),
+ luaZ_bufflen(ls->buff) - 2*(2 + sep));
+}
+
+
+static void read_string (LexState *ls, int del, SemInfo *seminfo) {
+ save_and_next(ls);
+ while (ls->current != del) {
+ switch (ls->current) {
+ case EOZ:
+ luaX_lexerror(ls, "unfinished string", TK_EOS);
+ continue; /* to avoid warnings */
+ case '\n':
+ case '\r':
+ luaX_lexerror(ls, "unfinished string", TK_STRING);
+ continue; /* to avoid warnings */
+ case '\\': {
+ int c;
+ next(ls); /* do not save the `\' */
+ switch (ls->current) {
+ case 'a': c = '\a'; break;
+ case 'b': c = '\b'; break;
+ case 'f': c = '\f'; break;
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'v': c = '\v'; break;
+ case '\n': /* go through */
+ case '\r': save(ls, '\n'); inclinenumber(ls); continue;
+ case EOZ: continue; /* will raise an error next loop */
+ default: {
+ if (!isdigit(ls->current))
+ save_and_next(ls); /* handles \\, \", \', and \? */
+ else { /* \xxx */
+ int i = 0;
+ c = 0;
+ do {
+ c = 10*c + (ls->current-'0');
+ next(ls);
+ } while (++i<3 && isdigit(ls->current));
+ if (c > UCHAR_MAX)
+ luaX_lexerror(ls, "escape sequence too large", TK_STRING);
+ save(ls, c);
+ }
+ continue;
+ }
+ }
+ save(ls, c);
+ next(ls);
+ continue;
+ }
+ default:
+ save_and_next(ls);
+ }
+ }
+ save_and_next(ls); /* skip delimiter */
+ seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + 1,
+ luaZ_bufflen(ls->buff) - 2);
+}
+
+
+static int llex (LexState *ls, SemInfo *seminfo) {
+ luaZ_resetbuffer(ls->buff);
+ for (;;) {
+ switch (ls->current) {
+ case '\n':
+ case '\r': {
+ inclinenumber(ls);
+ continue;
+ }
+ case '-': {
+ next(ls);
+ if (ls->current != '-') return '-';
+ /* else is a comment */
+ next(ls);
+ if (ls->current == '[') {
+ int sep = skip_sep(ls);
+ luaZ_resetbuffer(ls->buff); /* `skip_sep' may dirty the buffer */
+ if (sep >= 0) {
+ read_long_string(ls, NULL, sep); /* long comment */
+ luaZ_resetbuffer(ls->buff);
+ continue;
+ }
+ }
+ /* else short comment */
+ while (!currIsNewline(ls) && ls->current != EOZ)
+ next(ls);
+ continue;
+ }
+ case '[': {
+ int sep = skip_sep(ls);
+ if (sep >= 0) {
+ read_long_string(ls, seminfo, sep);
+ return TK_STRING;
+ }
+ else if (sep == -1) return '[';
+ else luaX_lexerror(ls, "invalid long string delimiter", TK_STRING);
+ }
+ case '=': {
+ next(ls);
+ if (ls->current != '=') return '=';
+ else { next(ls); return TK_EQ; }
+ }
+ case '<': {
+ next(ls);
+ if (ls->current != '=') return '<';
+ else { next(ls); return TK_LE; }
+ }
+ case '>': {
+ next(ls);
+ if (ls->current != '=') return '>';
+ else { next(ls); return TK_GE; }
+ }
+ case '~': {
+ next(ls);
+ if (ls->current != '=') return '~';
+ else { next(ls); return TK_NE; }
+ }
+ case '"':
+ case '\'': {
+ read_string(ls, ls->current, seminfo);
+ return TK_STRING;
+ }
+ case '.': {
+ save_and_next(ls);
+ if (check_next(ls, ".")) {
+ if (check_next(ls, "."))
+ return TK_DOTS; /* ... */
+ else return TK_CONCAT; /* .. */
+ }
+ else if (!isdigit(ls->current)) return '.';
+ else {
+ read_numeral(ls, seminfo);
+ return TK_NUMBER;
+ }
+ }
+ case EOZ: {
+ return TK_EOS;
+ }
+ default: {
+ if (isspace(ls->current)) {
+ lua_assert(!currIsNewline(ls));
+ next(ls);
+ continue;
+ }
+ else if (isdigit(ls->current)) {
+ read_numeral(ls, seminfo);
+ return TK_NUMBER;
+ }
+ else if (isalpha(ls->current) || ls->current == '_') {
+ /* identifier or reserved word */
+ TString *ts;
+ do {
+ save_and_next(ls);
+ } while (isalnum(ls->current) || ls->current == '_');
+ ts = luaX_newstring(ls, luaZ_buffer(ls->buff),
+ luaZ_bufflen(ls->buff));
+ if (ts->tsv.reserved > 0) /* reserved word? */
+ return ts->tsv.reserved - 1 + FIRST_RESERVED;
+ else {
+ seminfo->ts = ts;
+ return TK_NAME;
+ }
+ }
+ else {
+ int c = ls->current;
+ next(ls);
+ return c; /* single-char tokens (+ - / ...) */
+ }
+ }
+ }
+ }
+}
+
+
+void luaX_next (LexState *ls) {
+ ls->lastline = ls->linenumber;
+ if (ls->lookahead.token != TK_EOS) { /* is there a look-ahead token? */
+ ls->t = ls->lookahead; /* use this one */
+ ls->lookahead.token = TK_EOS; /* and discharge it */
+ }
+ else
+ ls->t.token = llex(ls, &ls->t.seminfo); /* read next token */
+}
+
+
+void luaX_lookahead (LexState *ls) {
+ lua_assert(ls->lookahead.token == TK_EOS);
+ ls->lookahead.token = llex(ls, &ls->lookahead.seminfo);
+}
+
diff --git a/lib/lua/src/llex.h b/lib/lua/src/llex.h
new file mode 100644
index 000000000..a9201cee4
--- /dev/null
+++ b/lib/lua/src/llex.h
@@ -0,0 +1,81 @@
+/*
+** $Id: llex.h,v 1.58.1.1 2007/12/27 13:02:25 roberto Exp $
+** Lexical Analyzer
+** See Copyright Notice in lua.h
+*/
+
+#ifndef llex_h
+#define llex_h
+
+#include "lobject.h"
+#include "lzio.h"
+
+
+#define FIRST_RESERVED 257
+
+/* maximum length of a reserved word */
+#define TOKEN_LEN (sizeof("function")/sizeof(char))
+
+
+/*
+* WARNING: if you change the order of this enumeration,
+* grep "ORDER RESERVED"
+*/
+enum RESERVED {
+ /* terminal symbols denoted by reserved words */
+ TK_AND = FIRST_RESERVED, TK_BREAK,
+ TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION,
+ TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT,
+ TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE,
+ /* other terminal symbols */
+ TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, TK_NUMBER,
+ TK_NAME, TK_STRING, TK_EOS
+};
+
+/* number of reserved words */
+#define NUM_RESERVED (cast(int, TK_WHILE-FIRST_RESERVED+1))
+
+
+/* array with token `names' */
+LUAI_DATA const char *const luaX_tokens [];
+
+
+typedef union {
+ lua_Number r;
+ TString *ts;
+} SemInfo; /* semantics information */
+
+
+typedef struct Token {
+ int token;
+ SemInfo seminfo;
+} Token;
+
+
+typedef struct LexState {
+ int current; /* current character (charint) */
+ int linenumber; /* input line counter */
+ int lastline; /* line of last token `consumed' */
+ Token t; /* current token */
+ Token lookahead; /* look ahead token */
+ struct FuncState *fs; /* `FuncState' is private to the parser */
+ struct lua_State *L;
+ ZIO *z; /* input stream */
+ Mbuffer *buff; /* buffer for tokens */
+ TString *source; /* current source name */
+ char decpoint; /* locale decimal point */
+} LexState;
+
+
+LUAI_FUNC void luaX_init (lua_State *L);
+LUAI_FUNC void luaX_setinput (lua_State *L, LexState *ls, ZIO *z,
+ TString *source);
+LUAI_FUNC TString *luaX_newstring (LexState *ls, const char *str, size_t l);
+LUAI_FUNC void luaX_next (LexState *ls);
+LUAI_FUNC void luaX_lookahead (LexState *ls);
+LUAI_FUNC void luaX_lexerror (LexState *ls, const char *msg, int token);
+LUAI_FUNC void luaX_syntaxerror (LexState *ls, const char *s);
+LUAI_FUNC const char *luaX_token2str (LexState *ls, int token);
+
+
+#endif
diff --git a/lib/lua/src/llimits.h b/lib/lua/src/llimits.h
new file mode 100644
index 000000000..ca8dcb722
--- /dev/null
+++ b/lib/lua/src/llimits.h
@@ -0,0 +1,128 @@
+/*
+** $Id: llimits.h,v 1.69.1.1 2007/12/27 13:02:25 roberto Exp $
+** Limits, basic types, and some other `installation-dependent' definitions
+** See Copyright Notice in lua.h
+*/
+
+#ifndef llimits_h
+#define llimits_h
+
+
+#include <limits.h>
+#include <stddef.h>
+
+
+#include "lua.h"
+
+
+typedef LUAI_UINT32 lu_int32;
+
+typedef LUAI_UMEM lu_mem;
+
+typedef LUAI_MEM l_mem;
+
+
+
+/* chars used as small naturals (so that `char' is reserved for characters) */
+typedef unsigned char lu_byte;
+
+
+#define MAX_SIZET ((size_t)(~(size_t)0)-2)
+
+#define MAX_LUMEM ((lu_mem)(~(lu_mem)0)-2)
+
+
+#define MAX_INT (INT_MAX-2) /* maximum value of an int (-2 for safety) */
+
+/*
+** conversion of pointer to integer
+** this is for hashing only; there is no problem if the integer
+** cannot hold the whole pointer value
+*/
+#define IntPoint(p) ((unsigned int)(lu_mem)(p))
+
+
+
+/* type to ensure maximum alignment */
+typedef LUAI_USER_ALIGNMENT_T L_Umaxalign;
+
+
+/* result of a `usual argument conversion' over lua_Number */
+typedef LUAI_UACNUMBER l_uacNumber;
+
+
+/* internal assertions for in-house debugging */
+#ifdef lua_assert
+
+#define check_exp(c,e) (lua_assert(c), (e))
+#define api_check(l,e) lua_assert(e)
+
+#else
+
+#define lua_assert(c) ((void)0)
+#define check_exp(c,e) (e)
+#define api_check luai_apicheck
+
+#endif
+
+
+#ifndef UNUSED
+#define UNUSED(x) ((void)(x)) /* to avoid warnings */
+#endif
+
+
+#ifndef cast
+#define cast(t, exp) ((t)(exp))
+#endif
+
+#define cast_byte(i) cast(lu_byte, (i))
+#define cast_num(i) cast(lua_Number, (i))
+#define cast_int(i) cast(int, (i))
+
+
+
+/*
+** type for virtual-machine instructions
+** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h)
+*/
+typedef lu_int32 Instruction;
+
+
+
+/* maximum stack for a Lua function */
+#define MAXSTACK 250
+
+
+
+/* minimum size for the string table (must be power of 2) */
+#ifndef MINSTRTABSIZE
+#define MINSTRTABSIZE 32
+#endif
+
+
+/* minimum size for string buffer */
+#ifndef LUA_MINBUFFER
+#define LUA_MINBUFFER 32
+#endif
+
+
+#ifndef lua_lock
+#define lua_lock(L) ((void) 0)
+#define lua_unlock(L) ((void) 0)
+#endif
+
+#ifndef luai_threadyield
+#define luai_threadyield(L) {lua_unlock(L); lua_lock(L);}
+#endif
+
+
+/*
+** macro to control inclusion of some hard tests on stack reallocation
+*/
+#ifndef HARDSTACKTESTS
+#define condhardstacktests(x) ((void)0)
+#else
+#define condhardstacktests(x) x
+#endif
+
+#endif
diff --git a/lib/lua/src/lmathlib.c b/lib/lua/src/lmathlib.c
new file mode 100644
index 000000000..441fbf736
--- /dev/null
+++ b/lib/lua/src/lmathlib.c
@@ -0,0 +1,263 @@
+/*
+** $Id: lmathlib.c,v 1.67.1.1 2007/12/27 13:02:25 roberto Exp $
+** Standard mathematical library
+** See Copyright Notice in lua.h
+*/
+
+
+#include <stdlib.h>
+#include <math.h>
+
+#define lmathlib_c
+#define LUA_LIB
+
+#include "lua.h"
+
+#include "lauxlib.h"
+#include "lualib.h"
+
+
+#undef PI
+#define PI (3.14159265358979323846)
+#define RADIANS_PER_DEGREE (PI/180.0)
+
+
+
+static int math_abs (lua_State *L) {
+ lua_pushnumber(L, fabs(luaL_checknumber(L, 1)));
+ return 1;
+}
+
+static int math_sin (lua_State *L) {
+ lua_pushnumber(L, sin(luaL_checknumber(L, 1)));
+ return 1;
+}
+
+static int math_sinh (lua_State *L) {
+ lua_pushnumber(L, sinh(luaL_checknumber(L, 1)));
+ return 1;
+}
+
+static int math_cos (lua_State *L) {
+ lua_pushnumber(L, cos(luaL_checknumber(L, 1)));
+ return 1;
+}
+
+static int math_cosh (lua_State *L) {
+ lua_pushnumber(L, cosh(luaL_checknumber(L, 1)));
+ return 1;
+}
+
+static int math_tan (lua_State *L) {
+ lua_pushnumber(L, tan(luaL_checknumber(L, 1)));
+ return 1;
+}
+
+static int math_tanh (lua_State *L) {
+ lua_pushnumber(L, tanh(luaL_checknumber(L, 1)));
+ return 1;
+}
+
+static int math_asin (lua_State *L) {
+ lua_pushnumber(L, asin(luaL_checknumber(L, 1)));
+ return 1;
+}
+
+static int math_acos (lua_State *L) {
+ lua_pushnumber(L, acos(luaL_checknumber(L, 1)));
+ return 1;
+}
+
+static int math_atan (lua_State *L) {
+ lua_pushnumber(L, atan(luaL_checknumber(L, 1)));
+ return 1;
+}
+
+static int math_atan2 (lua_State *L) {
+ lua_pushnumber(L, atan2(luaL_checknumber(L, 1), luaL_checknumber(L, 2)));
+ return 1;
+}
+
+static int math_ceil (lua_State *L) {
+ lua_pushnumber(L, ceil(luaL_checknumber(L, 1)));
+ return 1;
+}
+
+static int math_floor (lua_State *L) {
+ lua_pushnumber(L, floor(luaL_checknumber(L, 1)));
+ return 1;
+}
+
+static int math_fmod (lua_State *L) {
+ lua_pushnumber(L, fmod(luaL_checknumber(L, 1), luaL_checknumber(L, 2)));
+ return 1;
+}
+
+static int math_modf (lua_State *L) {
+ double ip;
+ double fp = modf(luaL_checknumber(L, 1), &ip);
+ lua_pushnumber(L, ip);
+ lua_pushnumber(L, fp);
+ return 2;
+}
+
+static int math_sqrt (lua_State *L) {
+ lua_pushnumber(L, sqrt(luaL_checknumber(L, 1)));
+ return 1;
+}
+
+static int math_pow (lua_State *L) {
+ lua_pushnumber(L, pow(luaL_checknumber(L, 1), luaL_checknumber(L, 2)));
+ return 1;
+}
+
+static int math_log (lua_State *L) {
+ lua_pushnumber(L, log(luaL_checknumber(L, 1)));
+ return 1;
+}
+
+static int math_log10 (lua_State *L) {
+ lua_pushnumber(L, log10(luaL_checknumber(L, 1)));
+ return 1;
+}
+
+static int math_exp (lua_State *L) {
+ lua_pushnumber(L, exp(luaL_checknumber(L, 1)));
+ return 1;
+}
+
+static int math_deg (lua_State *L) {
+ lua_pushnumber(L, luaL_checknumber(L, 1)/RADIANS_PER_DEGREE);
+ return 1;
+}
+
+static int math_rad (lua_State *L) {
+ lua_pushnumber(L, luaL_checknumber(L, 1)*RADIANS_PER_DEGREE);
+ return 1;
+}
+
+static int math_frexp (lua_State *L) {
+ int e;
+ lua_pushnumber(L, frexp(luaL_checknumber(L, 1), &e));
+ lua_pushinteger(L, e);
+ return 2;
+}
+
+static int math_ldexp (lua_State *L) {
+ lua_pushnumber(L, ldexp(luaL_checknumber(L, 1), luaL_checkint(L, 2)));
+ return 1;
+}
+
+
+
+static int math_min (lua_State *L) {
+ int n = lua_gettop(L); /* number of arguments */
+ lua_Number dmin = luaL_checknumber(L, 1);
+ int i;
+ for (i=2; i<=n; i++) {
+ lua_Number d = luaL_checknumber(L, i);
+ if (d < dmin)
+ dmin = d;
+ }
+ lua_pushnumber(L, dmin);
+ return 1;
+}
+
+
+static int math_max (lua_State *L) {
+ int n = lua_gettop(L); /* number of arguments */
+ lua_Number dmax = luaL_checknumber(L, 1);
+ int i;
+ for (i=2; i<=n; i++) {
+ lua_Number d = luaL_checknumber(L, i);
+ if (d > dmax)
+ dmax = d;
+ }
+ lua_pushnumber(L, dmax);
+ return 1;
+}
+
+
+static int math_random (lua_State *L) {
+ /* the `%' avoids the (rare) case of r==1, and is needed also because on
+ some systems (SunOS!) `rand()' may return a value larger than RAND_MAX */
+ lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX;
+ switch (lua_gettop(L)) { /* check number of arguments */
+ case 0: { /* no arguments */
+ lua_pushnumber(L, r); /* Number between 0 and 1 */
+ break;
+ }
+ case 1: { /* only upper limit */
+ int u = luaL_checkint(L, 1);
+ luaL_argcheck(L, 1<=u, 1, "interval is empty");
+ lua_pushnumber(L, floor(r*u)+1); /* int between 1 and `u' */
+ break;
+ }
+ case 2: { /* lower and upper limits */
+ int l = luaL_checkint(L, 1);
+ int u = luaL_checkint(L, 2);
+ luaL_argcheck(L, l<=u, 2, "interval is empty");
+ lua_pushnumber(L, floor(r*(u-l+1))+l); /* int between `l' and `u' */
+ break;
+ }
+ default: return luaL_error(L, "wrong number of arguments");
+ }
+ return 1;
+}
+
+
+static int math_randomseed (lua_State *L) {
+ srand(luaL_checkint(L, 1));
+ return 0;
+}
+
+
+static const luaL_Reg mathlib[] = {
+ {"abs", math_abs},
+ {"acos", math_acos},
+ {"asin", math_asin},
+ {"atan2", math_atan2},
+ {"atan", math_atan},
+ {"ceil", math_ceil},
+ {"cosh", math_cosh},
+ {"cos", math_cos},
+ {"deg", math_deg},
+ {"exp", math_exp},
+ {"floor", math_floor},
+ {"fmod", math_fmod},
+ {"frexp", math_frexp},
+ {"ldexp", math_ldexp},
+ {"log10", math_log10},
+ {"log", math_log},
+ {"max", math_max},
+ {"min", math_min},
+ {"modf", math_modf},
+ {"pow", math_pow},
+ {"rad", math_rad},
+ {"random", math_random},
+ {"randomseed", math_randomseed},
+ {"sinh", math_sinh},
+ {"sin", math_sin},
+ {"sqrt", math_sqrt},
+ {"tanh", math_tanh},
+ {"tan", math_tan},
+ {NULL, NULL}
+};
+
+
+/*
+** Open math library
+*/
+LUALIB_API int luaopen_math (lua_State *L) {
+ luaL_register(L, LUA_MATHLIBNAME, mathlib);
+ lua_pushnumber(L, PI);
+ lua_setfield(L, -2, "pi");
+ lua_pushnumber(L, HUGE_VAL);
+ lua_setfield(L, -2, "huge");
+#if defined(LUA_COMPAT_MOD)
+ lua_getfield(L, -1, "fmod");
+ lua_setfield(L, -2, "mod");
+#endif
+ return 1;
+}
+
diff --git a/lib/lua/src/lmem.c b/lib/lua/src/lmem.c
new file mode 100644
index 000000000..ae7d8c965
--- /dev/null
+++ b/lib/lua/src/lmem.c
@@ -0,0 +1,86 @@
+/*
+** $Id: lmem.c,v 1.70.1.1 2007/12/27 13:02:25 roberto Exp $
+** Interface to Memory Manager
+** See Copyright Notice in lua.h
+*/
+
+
+#include <stddef.h>
+
+#define lmem_c
+#define LUA_CORE
+
+#include "lua.h"
+
+#include "ldebug.h"
+#include "ldo.h"
+#include "lmem.h"
+#include "lobject.h"
+#include "lstate.h"
+
+
+
+/*
+** About the realloc function:
+** void * frealloc (void *ud, void *ptr, size_t osize, size_t nsize);
+** (`osize' is the old size, `nsize' is the new size)
+**
+** Lua ensures that (ptr == NULL) iff (osize == 0).
+**
+** * frealloc(ud, NULL, 0, x) creates a new block of size `x'
+**
+** * frealloc(ud, p, x, 0) frees the block `p'
+** (in this specific case, frealloc must return NULL).
+** particularly, frealloc(ud, NULL, 0, 0) does nothing
+** (which is equivalent to free(NULL) in ANSI C)
+**
+** frealloc returns NULL if it cannot create or reallocate the area
+** (any reallocation to an equal or smaller size cannot fail!)
+*/
+
+
+
+#define MINSIZEARRAY 4
+
+
+void *luaM_growaux_ (lua_State *L, void *block, int *size, size_t size_elems,
+ int limit, const char *errormsg) {
+ void *newblock;
+ int newsize;
+ if (*size >= limit/2) { /* cannot double it? */
+ if (*size >= limit) /* cannot grow even a little? */
+ luaG_runerror(L, errormsg);
+ newsize = limit; /* still have at least one free place */
+ }
+ else {
+ newsize = (*size)*2;
+ if (newsize < MINSIZEARRAY)
+ newsize = MINSIZEARRAY; /* minimum size */
+ }
+ newblock = luaM_reallocv(L, block, *size, newsize, size_elems);
+ *size = newsize; /* update only when everything else is OK */
+ return newblock;
+}
+
+
+void *luaM_toobig (lua_State *L) {
+ luaG_runerror(L, "memory allocation error: block too big");
+ return NULL; /* to avoid warnings */
+}
+
+
+
+/*
+** generic allocation routine.
+*/
+void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) {
+ global_State *g = G(L);
+ lua_assert((osize == 0) == (block == NULL));
+ block = (*g->frealloc)(g->ud, block, osize, nsize);
+ if (block == NULL && nsize > 0)
+ luaD_throw(L, LUA_ERRMEM);
+ lua_assert((nsize == 0) == (block == NULL));
+ g->totalbytes = (g->totalbytes - osize) + nsize;
+ return block;
+}
+
diff --git a/lib/lua/src/lmem.h b/lib/lua/src/lmem.h
new file mode 100644
index 000000000..7c2dcb322
--- /dev/null
+++ b/lib/lua/src/lmem.h
@@ -0,0 +1,49 @@
+/*
+** $Id: lmem.h,v 1.31.1.1 2007/12/27 13:02:25 roberto Exp $
+** Interface to Memory Manager
+** See Copyright Notice in lua.h
+*/
+
+#ifndef lmem_h
+#define lmem_h
+
+
+#include <stddef.h>
+
+#include "llimits.h"
+#include "lua.h"
+
+#define MEMERRMSG "not enough memory"
+
+
+#define luaM_reallocv(L,b,on,n,e) \
+ ((cast(size_t, (n)+1) <= MAX_SIZET/(e)) ? /* +1 to avoid warnings */ \
+ luaM_realloc_(L, (b), (on)*(e), (n)*(e)) : \
+ luaM_toobig(L))
+
+#define luaM_freemem(L, b, s) luaM_realloc_(L, (b), (s), 0)
+#define luaM_free(L, b) luaM_realloc_(L, (b), sizeof(*(b)), 0)
+#define luaM_freearray(L, b, n, t) luaM_reallocv(L, (b), n, 0, sizeof(t))
+
+#define luaM_malloc(L,t) luaM_realloc_(L, NULL, 0, (t))
+#define luaM_new(L,t) cast(t *, luaM_malloc(L, sizeof(t)))
+#define luaM_newvector(L,n,t) \
+ cast(t *, luaM_reallocv(L, NULL, 0, n, sizeof(t)))
+
+#define luaM_growvector(L,v,nelems,size,t,limit,e) \
+ if ((nelems)+1 > (size)) \
+ ((v)=cast(t *, luaM_growaux_(L,v,&(size),sizeof(t),limit,e)))
+
+#define luaM_reallocvector(L, v,oldn,n,t) \
+ ((v)=cast(t *, luaM_reallocv(L, v, oldn, n, sizeof(t))))
+
+
+LUAI_FUNC void *luaM_realloc_ (lua_State *L, void *block, size_t oldsize,
+ size_t size);
+LUAI_FUNC void *luaM_toobig (lua_State *L);
+LUAI_FUNC void *luaM_growaux_ (lua_State *L, void *block, int *size,
+ size_t size_elem, int limit,
+ const char *errormsg);
+
+#endif
+
diff --git a/lib/lua/src/loadlib.c b/lib/lua/src/loadlib.c
new file mode 100644
index 000000000..0d401eba1
--- /dev/null
+++ b/lib/lua/src/loadlib.c
@@ -0,0 +1,666 @@
+/*
+** $Id: loadlib.c,v 1.52.1.3 2008/08/06 13:29:28 roberto Exp $
+** Dynamic library loader for Lua
+** See Copyright Notice in lua.h
+**
+** This module contains an implementation of loadlib for Unix systems
+** that have dlfcn, an implementation for Darwin (Mac OS X), an
+** implementation for Windows, and a stub for other systems.
+*/
+
+
+#include <stdlib.h>
+#include <string.h>
+
+
+#define loadlib_c
+#define LUA_LIB
+
+#include "lua.h"
+
+#include "lauxlib.h"
+#include "lualib.h"
+
+
+/* prefix for open functions in C libraries */
+#define LUA_POF "luaopen_"
+
+/* separator for open functions in C libraries */
+#define LUA_OFSEP "_"
+
+
+#define LIBPREFIX "LOADLIB: "
+
+#define POF LUA_POF
+#define LIB_FAIL "open"
+
+
+/* error codes for ll_loadfunc */
+#define ERRLIB 1
+#define ERRFUNC 2
+
+#define setprogdir(L) ((void)0)
+
+
+static void ll_unloadlib (void *lib);
+static void *ll_load (lua_State *L, const char *path);
+static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym);
+
+
+
+#if defined(LUA_DL_DLOPEN)
+/*
+** {========================================================================
+** This is an implementation of loadlib based on the dlfcn interface.
+** The dlfcn interface is available in Linux, SunOS, Solaris, IRIX, FreeBSD,
+** NetBSD, AIX 4.2, HPUX 11, and probably most other Unix flavors, at least
+** as an emulation layer on top of native functions.
+** =========================================================================
+*/
+
+#include <dlfcn.h>
+
+static void ll_unloadlib (void *lib) {
+ dlclose(lib);
+}
+
+
+static void *ll_load (lua_State *L, const char *path) {
+ void *lib = dlopen(path, RTLD_NOW);
+ if (lib == NULL) lua_pushstring(L, dlerror());
+ return lib;
+}
+
+
+static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) {
+ lua_CFunction f = (lua_CFunction)dlsym(lib, sym);
+ if (f == NULL) lua_pushstring(L, dlerror());
+ return f;
+}
+
+/* }====================================================== */
+
+
+
+#elif defined(LUA_DL_DLL)
+/*
+** {======================================================================
+** This is an implementation of loadlib for Windows using native functions.
+** =======================================================================
+*/
+
+#include <windows.h>
+
+
+#undef setprogdir
+
+static void setprogdir (lua_State *L) {
+ char buff[MAX_PATH + 1];
+ char *lb;
+ DWORD nsize = sizeof(buff)/sizeof(char);
+ DWORD n = GetModuleFileNameA(NULL, buff, nsize);
+ if (n == 0 || n == nsize || (lb = strrchr(buff, '\\')) == NULL)
+ luaL_error(L, "unable to get ModuleFileName");
+ else {
+ *lb = '\0';
+ luaL_gsub(L, lua_tostring(L, -1), LUA_EXECDIR, buff);
+ lua_remove(L, -2); /* remove original string */
+ }
+}
+
+
+static void pusherror (lua_State *L) {
+ int error = GetLastError();
+ char buffer[128];
+ if (FormatMessageA(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL, error, 0, buffer, sizeof(buffer), NULL))
+ lua_pushstring(L, buffer);
+ else
+ lua_pushfstring(L, "system error %d\n", error);
+}
+
+static void ll_unloadlib (void *lib) {
+ FreeLibrary((HINSTANCE)lib);
+}
+
+
+static void *ll_load (lua_State *L, const char *path) {
+ HINSTANCE lib = LoadLibraryA(path);
+ if (lib == NULL) pusherror(L);
+ return lib;
+}
+
+
+static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) {
+ lua_CFunction f = (lua_CFunction)GetProcAddress((HINSTANCE)lib, sym);
+ if (f == NULL) pusherror(L);
+ return f;
+}
+
+/* }====================================================== */
+
+
+
+#elif defined(LUA_DL_DYLD)
+/*
+** {======================================================================
+** Native Mac OS X / Darwin Implementation
+** =======================================================================
+*/
+
+#include <mach-o/dyld.h>
+
+
+/* Mac appends a `_' before C function names */
+#undef POF
+#define POF "_" LUA_POF
+
+
+static void pusherror (lua_State *L) {
+ const char *err_str;
+ const char *err_file;
+ NSLinkEditErrors err;
+ int err_num;
+ NSLinkEditError(&err, &err_num, &err_file, &err_str);
+ lua_pushstring(L, err_str);
+}
+
+
+static const char *errorfromcode (NSObjectFileImageReturnCode ret) {
+ switch (ret) {
+ case NSObjectFileImageInappropriateFile:
+ return "file is not a bundle";
+ case NSObjectFileImageArch:
+ return "library is for wrong CPU type";
+ case NSObjectFileImageFormat:
+ return "bad format";
+ case NSObjectFileImageAccess:
+ return "cannot access file";
+ case NSObjectFileImageFailure:
+ default:
+ return "unable to load library";
+ }
+}
+
+
+static void ll_unloadlib (void *lib) {
+ NSUnLinkModule((NSModule)lib, NSUNLINKMODULE_OPTION_RESET_LAZY_REFERENCES);
+}
+
+
+static void *ll_load (lua_State *L, const char *path) {
+ NSObjectFileImage img;
+ NSObjectFileImageReturnCode ret;
+ /* this would be a rare case, but prevents crashing if it happens */
+ if(!_dyld_present()) {
+ lua_pushliteral(L, "dyld not present");
+ return NULL;
+ }
+ ret = NSCreateObjectFileImageFromFile(path, &img);
+ if (ret == NSObjectFileImageSuccess) {
+ NSModule mod = NSLinkModule(img, path, NSLINKMODULE_OPTION_PRIVATE |
+ NSLINKMODULE_OPTION_RETURN_ON_ERROR);
+ NSDestroyObjectFileImage(img);
+ if (mod == NULL) pusherror(L);
+ return mod;
+ }
+ lua_pushstring(L, errorfromcode(ret));
+ return NULL;
+}
+
+
+static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) {
+ NSSymbol nss = NSLookupSymbolInModule((NSModule)lib, sym);
+ if (nss == NULL) {
+ lua_pushfstring(L, "symbol " LUA_QS " not found", sym);
+ return NULL;
+ }
+ return (lua_CFunction)NSAddressOfSymbol(nss);
+}
+
+/* }====================================================== */
+
+
+
+#else
+/*
+** {======================================================
+** Fallback for other systems
+** =======================================================
+*/
+
+#undef LIB_FAIL
+#define LIB_FAIL "absent"
+
+
+#define DLMSG "dynamic libraries not enabled; check your Lua installation"
+
+
+static void ll_unloadlib (void *lib) {
+ (void)lib; /* to avoid warnings */
+}
+
+
+static void *ll_load (lua_State *L, const char *path) {
+ (void)path; /* to avoid warnings */
+ lua_pushliteral(L, DLMSG);
+ return NULL;
+}
+
+
+static lua_CFunction ll_sym (lua_State *L, void *lib, const char *sym) {
+ (void)lib; (void)sym; /* to avoid warnings */
+ lua_pushliteral(L, DLMSG);
+ return NULL;
+}
+
+/* }====================================================== */
+#endif
+
+
+
+static void **ll_register (lua_State *L, const char *path) {
+ void **plib;
+ lua_pushfstring(L, "%s%s", LIBPREFIX, path);
+ lua_gettable(L, LUA_REGISTRYINDEX); /* check library in registry? */
+ if (!lua_isnil(L, -1)) /* is there an entry? */
+ plib = (void **)lua_touserdata(L, -1);
+ else { /* no entry yet; create one */
+ lua_pop(L, 1);
+ plib = (void **)lua_newuserdata(L, sizeof(const void *));
+ *plib = NULL;
+ luaL_getmetatable(L, "_LOADLIB");
+ lua_setmetatable(L, -2);
+ lua_pushfstring(L, "%s%s", LIBPREFIX, path);
+ lua_pushvalue(L, -2);
+ lua_settable(L, LUA_REGISTRYINDEX);
+ }
+ return plib;
+}
+
+
+/*
+** __gc tag method: calls library's `ll_unloadlib' function with the lib
+** handle
+*/
+static int gctm (lua_State *L) {
+ void **lib = (void **)luaL_checkudata(L, 1, "_LOADLIB");
+ if (*lib) ll_unloadlib(*lib);
+ *lib = NULL; /* mark library as closed */
+ return 0;
+}
+
+
+static int ll_loadfunc (lua_State *L, const char *path, const char *sym) {
+ void **reg = ll_register(L, path);
+ if (*reg == NULL) *reg = ll_load(L, path);
+ if (*reg == NULL)
+ return ERRLIB; /* unable to load library */
+ else {
+ lua_CFunction f = ll_sym(L, *reg, sym);
+ if (f == NULL)
+ return ERRFUNC; /* unable to find function */
+ lua_pushcfunction(L, f);
+ return 0; /* return function */
+ }
+}
+
+
+static int ll_loadlib (lua_State *L) {
+ const char *path = luaL_checkstring(L, 1);
+ const char *init = luaL_checkstring(L, 2);
+ int stat = ll_loadfunc(L, path, init);
+ if (stat == 0) /* no errors? */
+ return 1; /* return the loaded function */
+ else { /* error; error message is on stack top */
+ lua_pushnil(L);
+ lua_insert(L, -2);
+ lua_pushstring(L, (stat == ERRLIB) ? LIB_FAIL : "init");
+ return 3; /* return nil, error message, and where */
+ }
+}
+
+
+
+/*
+** {======================================================
+** 'require' function
+** =======================================================
+*/
+
+
+static int readable (const char *filename) {
+ FILE *f = fopen(filename, "r"); /* try to open file */
+ if (f == NULL) return 0; /* open failed */
+ fclose(f);
+ return 1;
+}
+
+
+static const char *pushnexttemplate (lua_State *L, const char *path) {
+ const char *l;
+ while (*path == *LUA_PATHSEP) path++; /* skip separators */
+ if (*path == '\0') return NULL; /* no more templates */
+ l = strchr(path, *LUA_PATHSEP); /* find next separator */
+ if (l == NULL) l = path + strlen(path);
+ lua_pushlstring(L, path, l - path); /* template */
+ return l;
+}
+
+
+static const char *findfile (lua_State *L, const char *name,
+ const char *pname) {
+ const char *path;
+ name = luaL_gsub(L, name, ".", LUA_DIRSEP);
+ lua_getfield(L, LUA_ENVIRONINDEX, pname);
+ path = lua_tostring(L, -1);
+ if (path == NULL)
+ luaL_error(L, LUA_QL("package.%s") " must be a string", pname);
+ lua_pushliteral(L, ""); /* error accumulator */
+ while ((path = pushnexttemplate(L, path)) != NULL) {
+ const char *filename;
+ filename = luaL_gsub(L, lua_tostring(L, -1), LUA_PATH_MARK, name);
+ lua_remove(L, -2); /* remove path template */
+ if (readable(filename)) /* does file exist and is readable? */
+ return filename; /* return that file name */
+ lua_pushfstring(L, "\n\tno file " LUA_QS, filename);
+ lua_remove(L, -2); /* remove file name */
+ lua_concat(L, 2); /* add entry to possible error message */
+ }
+ return NULL; /* not found */
+}
+
+
+static void loaderror (lua_State *L, const char *filename) {
+ luaL_error(L, "error loading module " LUA_QS " from file " LUA_QS ":\n\t%s",
+ lua_tostring(L, 1), filename, lua_tostring(L, -1));
+}
+
+
+static int loader_Lua (lua_State *L) {
+ const char *filename;
+ const char *name = luaL_checkstring(L, 1);
+ filename = findfile(L, name, "path");
+ if (filename == NULL) return 1; /* library not found in this path */
+ if (luaL_loadfile(L, filename) != 0)
+ loaderror(L, filename);
+ return 1; /* library loaded successfully */
+}
+
+
+static const char *mkfuncname (lua_State *L, const char *modname) {
+ const char *funcname;
+ const char *mark = strchr(modname, *LUA_IGMARK);
+ if (mark) modname = mark + 1;
+ funcname = luaL_gsub(L, modname, ".", LUA_OFSEP);
+ funcname = lua_pushfstring(L, POF"%s", funcname);
+ lua_remove(L, -2); /* remove 'gsub' result */
+ return funcname;
+}
+
+
+static int loader_C (lua_State *L) {
+ const char *funcname;
+ const char *name = luaL_checkstring(L, 1);
+ const char *filename = findfile(L, name, "cpath");
+ if (filename == NULL) return 1; /* library not found in this path */
+ funcname = mkfuncname(L, name);
+ if (ll_loadfunc(L, filename, funcname) != 0)
+ loaderror(L, filename);
+ return 1; /* library loaded successfully */
+}
+
+
+static int loader_Croot (lua_State *L) {
+ const char *funcname;
+ const char *filename;
+ const char *name = luaL_checkstring(L, 1);
+ const char *p = strchr(name, '.');
+ int stat;
+ if (p == NULL) return 0; /* is root */
+ lua_pushlstring(L, name, p - name);
+ filename = findfile(L, lua_tostring(L, -1), "cpath");
+ if (filename == NULL) return 1; /* root not found */
+ funcname = mkfuncname(L, name);
+ if ((stat = ll_loadfunc(L, filename, funcname)) != 0) {
+ if (stat != ERRFUNC) loaderror(L, filename); /* real error */
+ lua_pushfstring(L, "\n\tno module " LUA_QS " in file " LUA_QS,
+ name, filename);
+ return 1; /* function not found */
+ }
+ return 1;
+}
+
+
+static int loader_preload (lua_State *L) {
+ const char *name = luaL_checkstring(L, 1);
+ lua_getfield(L, LUA_ENVIRONINDEX, "preload");
+ if (!lua_istable(L, -1))
+ luaL_error(L, LUA_QL("package.preload") " must be a table");
+ lua_getfield(L, -1, name);
+ if (lua_isnil(L, -1)) /* not found? */
+ lua_pushfstring(L, "\n\tno field package.preload['%s']", name);
+ return 1;
+}
+
+
+static const int sentinel_ = 0;
+#define sentinel ((void *)&sentinel_)
+
+
+static int ll_require (lua_State *L) {
+ const char *name = luaL_checkstring(L, 1);
+ int i;
+ lua_settop(L, 1); /* _LOADED table will be at index 2 */
+ lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED");
+ lua_getfield(L, 2, name);
+ if (lua_toboolean(L, -1)) { /* is it there? */
+ if (lua_touserdata(L, -1) == sentinel) /* check loops */
+ luaL_error(L, "loop or previous error loading module " LUA_QS, name);
+ return 1; /* package is already loaded */
+ }
+ /* else must load it; iterate over available loaders */
+ lua_getfield(L, LUA_ENVIRONINDEX, "loaders");
+ if (!lua_istable(L, -1))
+ luaL_error(L, LUA_QL("package.loaders") " must be a table");
+ lua_pushliteral(L, ""); /* error message accumulator */
+ for (i=1; ; i++) {
+ lua_rawgeti(L, -2, i); /* get a loader */
+ if (lua_isnil(L, -1))
+ luaL_error(L, "module " LUA_QS " not found:%s",
+ name, lua_tostring(L, -2));
+ lua_pushstring(L, name);
+ lua_call(L, 1, 1); /* call it */
+ if (lua_isfunction(L, -1)) /* did it find module? */
+ break; /* module loaded successfully */
+ else if (lua_isstring(L, -1)) /* loader returned error message? */
+ lua_concat(L, 2); /* accumulate it */
+ else
+ lua_pop(L, 1);
+ }
+ lua_pushlightuserdata(L, sentinel);
+ lua_setfield(L, 2, name); /* _LOADED[name] = sentinel */
+ lua_pushstring(L, name); /* pass name as argument to module */
+ lua_call(L, 1, 1); /* run loaded module */
+ if (!lua_isnil(L, -1)) /* non-nil return? */
+ lua_setfield(L, 2, name); /* _LOADED[name] = returned value */
+ lua_getfield(L, 2, name);
+ if (lua_touserdata(L, -1) == sentinel) { /* module did not set a value? */
+ lua_pushboolean(L, 1); /* use true as result */
+ lua_pushvalue(L, -1); /* extra copy to be returned */
+ lua_setfield(L, 2, name); /* _LOADED[name] = true */
+ }
+ return 1;
+}
+
+/* }====================================================== */
+
+
+
+/*
+** {======================================================
+** 'module' function
+** =======================================================
+*/
+
+
+static void setfenv (lua_State *L) {
+ lua_Debug ar;
+ if (lua_getstack(L, 1, &ar) == 0 ||
+ lua_getinfo(L, "f", &ar) == 0 || /* get calling function */
+ lua_iscfunction(L, -1))
+ luaL_error(L, LUA_QL("module") " not called from a Lua function");
+ lua_pushvalue(L, -2);
+ lua_setfenv(L, -2);
+ lua_pop(L, 1);
+}
+
+
+static void dooptions (lua_State *L, int n) {
+ int i;
+ for (i = 2; i <= n; i++) {
+ lua_pushvalue(L, i); /* get option (a function) */
+ lua_pushvalue(L, -2); /* module */
+ lua_call(L, 1, 0);
+ }
+}
+
+
+static void modinit (lua_State *L, const char *modname) {
+ const char *dot;
+ lua_pushvalue(L, -1);
+ lua_setfield(L, -2, "_M"); /* module._M = module */
+ lua_pushstring(L, modname);
+ lua_setfield(L, -2, "_NAME");
+ dot = strrchr(modname, '.'); /* look for last dot in module name */
+ if (dot == NULL) dot = modname;
+ else dot++;
+ /* set _PACKAGE as package name (full module name minus last part) */
+ lua_pushlstring(L, modname, dot - modname);
+ lua_setfield(L, -2, "_PACKAGE");
+}
+
+
+static int ll_module (lua_State *L) {
+ const char *modname = luaL_checkstring(L, 1);
+ int loaded = lua_gettop(L) + 1; /* index of _LOADED table */
+ lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED");
+ lua_getfield(L, loaded, modname); /* get _LOADED[modname] */
+ if (!lua_istable(L, -1)) { /* not found? */
+ lua_pop(L, 1); /* remove previous result */
+ /* try global variable (and create one if it does not exist) */
+ if (luaL_findtable(L, LUA_GLOBALSINDEX, modname, 1) != NULL)
+ return luaL_error(L, "name conflict for module " LUA_QS, modname);
+ lua_pushvalue(L, -1);
+ lua_setfield(L, loaded, modname); /* _LOADED[modname] = new table */
+ }
+ /* check whether table already has a _NAME field */
+ lua_getfield(L, -1, "_NAME");
+ if (!lua_isnil(L, -1)) /* is table an initialized module? */
+ lua_pop(L, 1);
+ else { /* no; initialize it */
+ lua_pop(L, 1);
+ modinit(L, modname);
+ }
+ lua_pushvalue(L, -1);
+ setfenv(L);
+ dooptions(L, loaded - 1);
+ return 0;
+}
+
+
+static int ll_seeall (lua_State *L) {
+ luaL_checktype(L, 1, LUA_TTABLE);
+ if (!lua_getmetatable(L, 1)) {
+ lua_createtable(L, 0, 1); /* create new metatable */
+ lua_pushvalue(L, -1);
+ lua_setmetatable(L, 1);
+ }
+ lua_pushvalue(L, LUA_GLOBALSINDEX);
+ lua_setfield(L, -2, "__index"); /* mt.__index = _G */
+ return 0;
+}
+
+
+/* }====================================================== */
+
+
+
+/* auxiliary mark (for internal use) */
+#define AUXMARK "\1"
+
+static void setpath (lua_State *L, const char *fieldname, const char *envname,
+ const char *def) {
+ const char *path = getenv(envname);
+ if (path == NULL) /* no environment variable? */
+ lua_pushstring(L, def); /* use default */
+ else {
+ /* replace ";;" by ";AUXMARK;" and then AUXMARK by default path */
+ path = luaL_gsub(L, path, LUA_PATHSEP LUA_PATHSEP,
+ LUA_PATHSEP AUXMARK LUA_PATHSEP);
+ luaL_gsub(L, path, AUXMARK, def);
+ lua_remove(L, -2);
+ }
+ setprogdir(L);
+ lua_setfield(L, -2, fieldname);
+}
+
+
+static const luaL_Reg pk_funcs[] = {
+ {"loadlib", ll_loadlib},
+ {"seeall", ll_seeall},
+ {NULL, NULL}
+};
+
+
+static const luaL_Reg ll_funcs[] = {
+ {"module", ll_module},
+ {"require", ll_require},
+ {NULL, NULL}
+};
+
+
+static const lua_CFunction loaders[] =
+ {loader_preload, loader_Lua, loader_C, loader_Croot, NULL};
+
+
+LUALIB_API int luaopen_package (lua_State *L) {
+ int i;
+ /* create new type _LOADLIB */
+ luaL_newmetatable(L, "_LOADLIB");
+ lua_pushcfunction(L, gctm);
+ lua_setfield(L, -2, "__gc");
+ /* create `package' table */
+ luaL_register(L, LUA_LOADLIBNAME, pk_funcs);
+#if defined(LUA_COMPAT_LOADLIB)
+ lua_getfield(L, -1, "loadlib");
+ lua_setfield(L, LUA_GLOBALSINDEX, "loadlib");
+#endif
+ lua_pushvalue(L, -1);
+ lua_replace(L, LUA_ENVIRONINDEX);
+ /* create `loaders' table */
+ lua_createtable(L, 0, sizeof(loaders)/sizeof(loaders[0]) - 1);
+ /* fill it with pre-defined loaders */
+ for (i=0; loaders[i] != NULL; i++) {
+ lua_pushcfunction(L, loaders[i]);
+ lua_rawseti(L, -2, i+1);
+ }
+ lua_setfield(L, -2, "loaders"); /* put it in field `loaders' */
+ setpath(L, "path", LUA_PATH, LUA_PATH_DEFAULT); /* set field `path' */
+ setpath(L, "cpath", LUA_CPATH, LUA_CPATH_DEFAULT); /* set field `cpath' */
+ /* store config information */
+ lua_pushliteral(L, LUA_DIRSEP "\n" LUA_PATHSEP "\n" LUA_PATH_MARK "\n"
+ LUA_EXECDIR "\n" LUA_IGMARK);
+ lua_setfield(L, -2, "config");
+ /* set field `loaded' */
+ luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 2);
+ lua_setfield(L, -2, "loaded");
+ /* set field `preload' */
+ lua_newtable(L);
+ lua_setfield(L, -2, "preload");
+ lua_pushvalue(L, LUA_GLOBALSINDEX);
+ luaL_register(L, NULL, ll_funcs); /* open lib into global table */
+ lua_pop(L, 1);
+ return 1; /* return 'package' table */
+}
+
diff --git a/lib/lua/src/lobject.c b/lib/lua/src/lobject.c
new file mode 100644
index 000000000..4ff50732a
--- /dev/null
+++ b/lib/lua/src/lobject.c
@@ -0,0 +1,214 @@
+/*
+** $Id: lobject.c,v 2.22.1.1 2007/12/27 13:02:25 roberto Exp $
+** Some generic functions over Lua objects
+** See Copyright Notice in lua.h
+*/
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define lobject_c
+#define LUA_CORE
+
+#include "lua.h"
+
+#include "ldo.h"
+#include "lmem.h"
+#include "lobject.h"
+#include "lstate.h"
+#include "lstring.h"
+#include "lvm.h"
+
+
+
+const TValue luaO_nilobject_ = {{NULL}, LUA_TNIL};
+
+
+/*
+** converts an integer to a "floating point byte", represented as
+** (eeeeexxx), where the real value is (1xxx) * 2^(eeeee - 1) if
+** eeeee != 0 and (xxx) otherwise.
+*/
+int luaO_int2fb (unsigned int x) {
+ int e = 0; /* expoent */
+ while (x >= 16) {
+ x = (x+1) >> 1;
+ e++;
+ }
+ if (x < 8) return x;
+ else return ((e+1) << 3) | (cast_int(x) - 8);
+}
+
+
+/* converts back */
+int luaO_fb2int (int x) {
+ int e = (x >> 3) & 31;
+ if (e == 0) return x;
+ else return ((x & 7)+8) << (e - 1);
+}
+
+
+int luaO_log2 (unsigned int x) {
+ static const lu_byte log_2[256] = {